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:
47
docker/entrypoint.sh
Normal file
47
docker/entrypoint.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
# FlorianNetz Container-Entrypoint.
|
||||
# Ablauf: auf Postgres warten -> Migrationen anwenden -> (optional) seeden ->
|
||||
# App-Server starten. Idempotent: Migration + Seed nutzen Journal/Upserts.
|
||||
set -eu
|
||||
|
||||
echo "[entrypoint] FlorianNetz startet ..."
|
||||
|
||||
if [ -z "${DATABASE_URL:-}" ]; then
|
||||
echo "[entrypoint] FEHLER: DATABASE_URL ist nicht gesetzt." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- 1) Auf Postgres warten ---------------------------------------------------
|
||||
# pg_isready akzeptiert die DATABASE_URL direkt; bis ~60 s pollen.
|
||||
echo "[entrypoint] Warte auf Postgres ..."
|
||||
ATTEMPTS=0
|
||||
MAX_ATTEMPTS="${DB_WAIT_RETRIES:-60}"
|
||||
until pg_isready -d "${DATABASE_URL}" >/dev/null 2>&1; do
|
||||
ATTEMPTS=$((ATTEMPTS + 1))
|
||||
if [ "${ATTEMPTS}" -ge "${MAX_ATTEMPTS}" ]; then
|
||||
echo "[entrypoint] FEHLER: Postgres nach ${MAX_ATTEMPTS} Versuchen nicht erreichbar." >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "[entrypoint] Postgres ist erreichbar."
|
||||
|
||||
# --- 2) Migrationen anwenden (idempotent über das Drizzle-Journal) ------------
|
||||
echo "[entrypoint] Wende Migrationen an ..."
|
||||
node docker/migrate.mjs
|
||||
|
||||
# --- 3) Optionaler Seed -------------------------------------------------------
|
||||
# RUN_SEED=true füllt den NÖ-Katalog (idempotente Upserts). Setzt das gebündelte
|
||||
# Seed-Skript voraus (docker/seed.mjs); fehlt es, wird der Schritt übersprungen.
|
||||
if [ "${RUN_SEED:-false}" = "true" ]; then
|
||||
if [ -f docker/seed.mjs ]; then
|
||||
echo "[entrypoint] Führe Katalog-Seed aus ..."
|
||||
node docker/seed.mjs
|
||||
else
|
||||
echo "[entrypoint] RUN_SEED=true, aber docker/seed.mjs fehlt — Seed übersprungen." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 4) App-Server starten ----------------------------------------------------
|
||||
echo "[entrypoint] Starte Anwendung: $*"
|
||||
exec node server.js
|
||||
28
docker/migrate.mjs
Normal file
28
docker/migrate.mjs
Normal file
@@ -0,0 +1,28 @@
|
||||
// Migrations-Runner für das Laufzeit-Image (plain ESM, ohne tsx/drizzle-kit).
|
||||
// Wendet die Drizzle-Migrationen aus ./drizzle idempotent über das Journal an.
|
||||
// Liest DATABASE_URL direkt aus der Umgebung (keine Next.js-Env-Validierung),
|
||||
// analog zu scripts/migrate.ts.
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||
import pg from "pg";
|
||||
|
||||
const { Pool } = pg;
|
||||
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
if (!connectionString) {
|
||||
console.error("DATABASE_URL ist nicht gesetzt.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const pool = new Pool({ connectionString, max: 1 });
|
||||
const db = drizzle(pool);
|
||||
|
||||
try {
|
||||
await migrate(db, { migrationsFolder: "./drizzle" });
|
||||
console.log("Migrationen erfolgreich angewandt.");
|
||||
} catch (err) {
|
||||
console.error("Migration fehlgeschlagen:", err);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
Reference in New Issue
Block a user