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: