From f42aead5caff0fd32a57934946ee980897cca08b Mon Sep 17 00:00:00 2001 From: linus Date: Thu, 10 Sep 2026 12:08:43 +0200 Subject: [PATCH] build: Docker setup (compose: postgres + all-in-one api image) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dockerfile: 3-stage — Flutter web build, NestJS build, slim node runtime. Runtime copies dist + node_modules + prisma + the web bundle (WEB_CLIENT_DIR=/app/web), runs `prisma migrate deploy` then `node dist/main.js`. One container serves client + API on :3000. - docker-compose.yml: postgres:16-alpine with a healthcheck + the api service; config from backend/.env (Compose v2 strips quotes), DATABASE_URL + GOOGLE_APPLICATION_CREDENTIALS overridden for the container, serviceAccount.json bind-mounted read-only. - .dockerignore keeps node_modules/build/secrets out of the context. Not run here (no Docker on this box); the stack also runs natively against the local Postgres. Co-Authored-By: Claude Sonnet 5 --- .dockerignore | 10 ++++++++++ Dockerfile | 34 ++++++++++++++++++++++++++++++++++ README.md | 15 +++++++++++++++ docker-compose.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0715d0b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +**/node_modules +**/dist +**/build +**/.dart_tool +**/coverage +.git +**/*.log +# Secrets: passed at runtime via env_file / bind mount, never baked in. +backend/.env +backend/serviceAccount.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a8261e9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# syntax=docker/dockerfile:1 + +# --- 1. Flutter web build ------------------------------------------------- +FROM ghcr.io/cirruslabs/flutter:stable AS web +WORKDIR /src +COPY client/app/pubspec.yaml client/app/pubspec.lock ./ +RUN flutter pub get +COPY client/app/ ./ +RUN flutter build web --release + +# --- 2. Backend build -------------------------------------------------------- +FROM node:20-bookworm-slim AS api-build +WORKDIR /src +COPY backend/package.json backend/package-lock.json ./ +RUN npm ci +COPY backend/ ./ +RUN npx prisma generate && npm run build + +# --- 3. Runtime ------------------------------------------------------------ +FROM node:20-bookworm-slim AS runtime +ENV NODE_ENV=production +WORKDIR /app +# Prisma needs OpenSSL at runtime. +RUN apt-get update && apt-get install -y --no-install-recommends openssl \ + && rm -rf /var/lib/apt/lists/* +COPY --from=api-build /src/node_modules ./node_modules +COPY --from=api-build /src/dist ./dist +COPY --from=api-build /src/prisma ./prisma +# The Flutter web build; app.module reads WEB_CLIENT_DIR. +COPY --from=web /src/build/web ./web +ENV WEB_CLIENT_DIR=/app/web +EXPOSE 3000 +# Apply pending migrations, then boot. +CMD ["sh", "-c", "npx prisma migrate deploy && node dist/main.js"] diff --git a/README.md b/README.md index 5e0b069..2229e9a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,21 @@ events (KCs), replacing the WordPress plugin "Workshop-Wahlen". See [plan-kcAppMultiTenantPlatform.prompt.md](plan-kcAppMultiTenantPlatform.prompt.md) for the full architecture and phased roadmap. +## Run with Docker + +```bash +cp backend/.env.example backend/.env # fill in the secrets +# put the Firebase service account at backend/serviceAccount.json (optional; push) +docker compose up --build +``` + +`docker-compose.yml` starts PostgreSQL 16 and one `api` container (multi-stage +`Dockerfile`: Flutter web build → NestJS build → slim runtime). The container +runs `prisma migrate deploy` on start and serves the whole app — Flutter web +client + REST API — on . Requires Docker Compose v2. +Secrets are read from `backend/.env` and the service-account JSON is bind- +mounted read-only; neither is baked into the image. + ## Structure - `backend/` — NestJS API (Prisma/PostgreSQL, Authentik OIDC as resource diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..31c6633 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +services: + db: + image: postgres:16-alpine + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: kcapp + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d kcapp"] + interval: 5s + timeout: 5s + retries: 10 + + api: + build: + context: . + dockerfile: Dockerfile + depends_on: + db: + condition: service_healthy + # All non-DB config comes from backend/.env (needs Docker Compose v2, + # which strips surrounding quotes). DATABASE_URL and the FCM credential + # path are overridden below for the container. + env_file: + - backend/.env + environment: + DATABASE_URL: postgresql://postgres:postgres@db:5432/kcapp?schema=public + PORT: "3000" + GOOGLE_APPLICATION_CREDENTIALS: /app/serviceAccount.json + APP_BASE_URL: http://localhost:3000 + ports: + - "3000:3000" + volumes: + # Firebase service account — kept out of the image, mounted read-only. + - ./backend/serviceAccount.json:/app/serviceAccount.json:ro + +volumes: + pgdata: