first commit

This commit is contained in:
Kurt Ferreira 2026-04-11 13:57:02 +02:00
commit db5ca09c1b
5 changed files with 189 additions and 0 deletions

25
.woodpecker.yml Normal file
View File

@ -0,0 +1,25 @@
steps:
- name: build-and-push
image: woodpeckerci/plugin-docker-buildx
settings:
repo: "registry.af-east-1.dc.koldsoftware.com/${CI_REPO_OWNER}/nodejs-app"
registry: "registry.af-east-1.dc.koldsoftware.com"
tags:
- "${CI_COMMIT_SHA:0:8}"
- latest
username:
from_secret: registry_username
password:
from_secret: registry_password
- name: deploy
image: hashicorp/nomad:latest
commands:
- nomad job run
-var="version=${CI_COMMIT_SHA:0:8}"
-var="domain=af-east-1.dc.koldsoftware.com"
job.hcl
environment:
NOMAD_ADDR: "http://192.168.2.106:4646"
when:
branch: main

14
Dockerfile Normal file
View File

@ -0,0 +1,14 @@
# Stage 1: install production dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
# Stage 2: final image — no dev tools, no npm cache
FROM node:20-alpine
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY src/ ./src/
EXPOSE 3000
USER node
CMD ["node", "src/index.js"]

65
job.hcl Normal file
View File

@ -0,0 +1,65 @@
variables {
version = "latest"
domain = "homelab.local"
count = 1
}
job "nodejs-app" {
datacenters = ["*"]
type = "service"
update {
max_parallel = 1
min_healthy_time = "10s"
healthy_deadline = "3m"
auto_revert = true
}
group "nodejs-app" {
count = var.count
network {
port "http" { to = 3000 }
}
task "nodejs-app" {
driver = "docker"
config {
image = "registry.${var.domain}/kurt/nodejs-app:${var.version}"
ports = ["http"]
}
env {
NODE_ENV = "production"
APP_VERSION = var.version
PORT = "3000"
}
resources {
cpu = 256
memory = 256
}
}
service {
name = "nodejs-app"
port = "http"
provider = "nomad"
tags = [
"traefik.enable=true",
"traefik.http.routers.nodejs-app.rule=Host(`nodejs-app.${var.domain}`)",
"traefik.http.routers.nodejs-app.tls=true",
"traefik.http.routers.nodejs-app.tls.certresolver=letsencrypt",
]
check {
type = "http"
path = "/health"
interval = "10s"
timeout = "2s"
}
}
}
}

20
package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "nodejs-app",
"version": "0.1.0",
"description": "Example Node.js app for homelab-dc",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
},
"dependencies": {
"express": "^4.19.2",
"prom-client": "^15.1.0"
},
"devDependencies": {
"nodemon": "^3.1.0"
},
"engines": {
"node": ">=20"
}
}

65
src/index.js Normal file
View File

@ -0,0 +1,65 @@
'use strict'
const express = require('express')
const promClient = require('prom-client')
const app = express()
const PORT = process.env.PORT || 3000
// Prometheus metrics
const collectDefaultMetrics = promClient.collectDefaultMetrics
collectDefaultMetrics({ prefix: 'nodejs_app_' })
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
})
const httpRequestTotal = new promClient.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status'],
})
// Metrics middleware
app.use((req, res, next) => {
const end = httpRequestDuration.startTimer()
res.on('finish', () => {
const route = req.route ? req.route.path : req.path
const labels = { method: req.method, route, status: res.statusCode }
end(labels)
httpRequestTotal.inc(labels)
})
next()
})
app.use(express.json())
app.get('/metrics', async (_req, res) => {
res.set('Content-Type', promClient.register.contentType)
res.end(await promClient.register.metrics())
})
app.get('/health', (_req, res) => {
res.json({ status: 'ok' })
})
app.get('/', (_req, res) => {
res.json({
message: 'Hello from Node.js!',
env: process.env.NODE_ENV || 'development',
version: process.env.APP_VERSION || 'unknown',
})
})
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server listening on port ${PORT}`)
})
// Graceful shutdown — important for Nomad rolling deploys
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully')
process.exit(0)
})