Workstream 10: Deployment (Docker + externes Traefik) (Phase 7)
Liefert das reproduzierbare Compose-Setup hinter EXTERNEM Traefik: - Dockerfile (multi-stage deps/builder/runner, Next.js standalone, non-root UID/GID 1001, HEALTHCHECK gegen /api/health). - docker/entrypoint.sh: wartet via pg_isready auf Postgres, wendet Migrationen idempotent an (docker/migrate.mjs, plain ESM ohne tsx/drizzle-kit), optionaler Seed (RUN_SEED), dann exec node server.js. - docker-compose.yml: genau vier Services (app, postgres, osrm, nominatim), KEIN Proxy-Service; externes traefik-Netz + internes Netz; Traefik-Labels (Host, websecure, tls.certresolver, Security-Header-Middleware); Postgres-/App-Healthchecks; AUTH_URL/AUTH_TRUST_HOST/Forwarded-Header. - docker-compose.override.yml.example: lokal :3000 ohne TLS (http AUTH_URL). - .dockerignore, Makefile (build/up/down/logs/deploy/data/config). - .env.example: voller Vertrag inkl. APP_HOST, TRAEFIK_*, POSTGRES_*, RUN_SEED. - docs/reference/deployment-traefik.md: externes Netz, Authentik-Redirect-URI https://${APP_HOST}/api/auth/callback/authentik, Forwarded-Header/Cookies, /api/health-Allowlist. - tests/unit/deployment.test.ts (TDD): statische Offline-Verifikation der Artefakte; vitest.config.ts nimmt tests/unit/** auf. Offline verifiziert: tsc --noEmit sauber; vitest run grün (200 passed, 7 db-roundtrip skipped); next build erzeugt .next/standalone/server.js; sh -n docker/entrypoint.sh ok; make -n deploy zeigt build->up. Deferred (kein Docker/Postgres in der Sandbox): docker build/run id -u=1001, docker compose config --services, /api/health anonym 200, End-to-End Traefik. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
73
Dockerfile
Normal file
73
Dockerfile
Normal file
@@ -0,0 +1,73 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# FlorianNetz — multi-stage Build des Next.js-Standalone-Servers.
|
||||
# Stufen: deps (Abhängigkeiten) -> builder (Build) -> runner (schlankes Laufzeit-Image).
|
||||
# Läuft non-root (UID/GID 1001). Migration + optionaler Seed laufen im Entrypoint
|
||||
# vor dem App-Start (siehe docker/entrypoint.sh).
|
||||
|
||||
ARG NODE_VERSION=22
|
||||
|
||||
# --- deps: Produktions- und Build-Abhängigkeiten installieren -----------------
|
||||
FROM node:${NODE_VERSION}-alpine AS deps
|
||||
WORKDIR /app
|
||||
# Nur Manifeste kopieren -> Layer-Cache bleibt stabil, solange sich Deps nicht ändern.
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# --- builder: Next.js im Standalone-Modus bauen -------------------------------
|
||||
FROM node:${NODE_VERSION}-alpine AS builder
|
||||
WORKDIR /app
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
# next.config.ts setzt output:"standalone" -> erzeugt .next/standalone/server.js.
|
||||
RUN npm run build
|
||||
|
||||
# --- runner: minimales Laufzeit-Image ----------------------------------------
|
||||
FROM node:${NODE_VERSION}-alpine AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
|
||||
# Postgres-Client (pg_isready/psql) für die Wait-on-DB-Probe im Entrypoint.
|
||||
RUN apk add --no-cache postgresql-client
|
||||
|
||||
# Non-root-Benutzer (feste UID/GID 1001, wie im Plan gefordert).
|
||||
RUN addgroup --system --gid 1001 nodejs \
|
||||
&& adduser --system --uid 1001 nextjs
|
||||
|
||||
# Standalone-Server + statische Assets.
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||
|
||||
# Migration zur Laufzeit: Drizzle-Journal + Migrator + pg, plus die Migrationen.
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./drizzle
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/drizzle-orm ./node_modules/drizzle-orm
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg ./node_modules/pg
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg-pool ./node_modules/pg-pool
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg-protocol ./node_modules/pg-protocol
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg-types ./node_modules/pg-types
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg-connection-string ./node_modules/pg-connection-string
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pgpass ./node_modules/pgpass
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/postgres-array ./node_modules/postgres-array
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/postgres-bytea ./node_modules/postgres-bytea
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/postgres-date ./node_modules/postgres-date
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/postgres-interval ./node_modules/postgres-interval
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/split2 ./node_modules/split2
|
||||
|
||||
# Migrations-Runner (plain ESM, ohne tsx) + Entrypoint.
|
||||
COPY --chown=nextjs:nodejs docker/migrate.mjs ./docker/migrate.mjs
|
||||
COPY --chown=nextjs:nodejs docker/entrypoint.sh ./docker/entrypoint.sh
|
||||
RUN chmod +x ./docker/entrypoint.sh
|
||||
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
|
||||
# Liveness-Probe (öffentlich, ohne Fachdaten).
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=5 \
|
||||
CMD wget -q -O - http://127.0.0.1:3000/api/health || exit 1
|
||||
|
||||
ENTRYPOINT ["./docker/entrypoint.sh"]
|
||||
CMD ["node", "server.js"]
|
||||
Reference in New Issue
Block a user