build: Dockerfile and compose for VM103 behind traefik

One image for both processes (node:24-slim pinned by digest, Node 24.21 / SQLite 3.53 as in development).
compose: server on the proxy network with traefik labels in the house convention (web-secured, certresolver http,
ipwhitelist LAN only), driver with no network; both non-root, read-only rootfs, no capabilities, no host port,
no Docker socket. Data in /srv/appdata/homelab-upgrade/data.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
homelab-updaterandClaude Sonnet 5 committed 2026-09-19 00:02:48 +02:00
1 parent 26fc787ae7
commit 089b014edc
3 files changed
+96

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
.git
node_modules
dist
coverage
data
docs
tools
deploy
.env
.env.*
*.log
*.sqlite*
src/testing
**/*.test.ts
**/*.test.tsx
+23
View File
@@ -0,0 +1,23 @@
# One image, two processes (server and driver); the compose file picks the command.
# Backend runs TypeScript natively on Node 24 (type stripping): no build step, only the frontend is bundled.
ARG NODE_IMAGE=node:24-slim@sha256:2fe369e969550cde8e867afc3fe370b260140cab4a23d467074295b42163d553
FROM ${NODE_IMAGE} AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json vite.config.ts ./
COPY src ./src
RUN npm run build:web
FROM ${NODE_IMAGE} AS runtime
ARG REVISION=unknown
LABEL org.opencontainers.image.title="homelab-upgrade" org.opencontainers.image.revision="${REVISION}"
ENV NODE_ENV=production HOST=0.0.0.0 PORT=8080 DB_PATH=/data/queue.sqlite WEB_DIR=/app/dist/web
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist/web ./dist/web
COPY src ./src
USER node
CMD ["node", "src/main/server.ts"]
+58
View File
@@ -0,0 +1,58 @@
# Target: /opt/homelab/compose/homelab-upgrade/compose.yaml on VM103.
# Image is built on the VM from this repo (see docs/DEPLOY.md); data lives in /srv/appdata/homelab-upgrade/data (uid 1000).
name: homelab-upgrade
x-common: &common
image: homelab-upgrade:0.1.0
restart: unless-stopped
user: "1000:1000"
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
tmpfs: [/tmp]
volumes:
- /srv/appdata/homelab-upgrade/data:/data
services:
# The web app: reads the database and inserts rows; executes nothing. No Docker socket, no host port.
server:
<<: *common
command: ["node", "src/main/server.ts"]
environment:
TZ: Europe/Warsaw
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8080/api/health').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
networks: [default, proxy]
labels:
traefik.enable: "true"
traefik.docker.network: "proxy"
traefik.http.routers.homelab-upgrade.rule: 'Host(`upgrade.mariuszpacek.pl`)'
traefik.http.routers.homelab-upgrade.entrypoints: "web-secured"
traefik.http.routers.homelab-upgrade.priority: "100"
traefik.http.routers.homelab-upgrade.middlewares: "homelab-upgrade-lan"
traefik.http.routers.homelab-upgrade.tls: "true"
traefik.http.routers.homelab-upgrade.tls.certresolver: "http"
traefik.http.routers.homelab-upgrade.service: "homelab-upgrade"
traefik.http.services.homelab-upgrade.loadbalancer.server.port: "8080"
# No login yet: LAN only.
traefik.http.middlewares.homelab-upgrade-lan.ipwhitelist.sourcerange: "192.168.0.1/24,10.0.0.0/16"
# The driver: takes queued steps one at a time. Only fake demo handlers exist so far (DEMO=1),
# so it needs no network, no Docker socket and no secrets.
driver:
<<: *common
command: ["node", "src/main/driver.ts"]
environment:
TZ: Europe/Warsaw
DEMO: "1"
DRIVER_IDLE_MS: "5000"
network_mode: none
networks:
proxy:
name: proxy
external: true