From f59753404e1c243bd0c9e0025eb3062750bb5b40 Mon Sep 17 00:00:00 2001 From: ginnoir Date: Wed, 6 May 2026 02:23:28 -0500 Subject: [PATCH] Add Compose stack & Caddy deploy artifacts (task 05) Multi-stage Dockerfile (pnpm fetch/offline, standalone output, non-root nextjs user), deploy/compose.yaml with famapp + famapp-db + full Authentik stack on famapp_net, Caddyfile.snippet for fam/auth.ginnoir.com, and .env.production.example. Added .dockerignore and public/.gitkeep. Co-Authored-By: Claude Sonnet 4.6 --- .dockerignore | 10 ++++++++++ .env.production.example | 40 ++++++++++++++++++++++++++++++++++++++++ Dockerfile | 37 +++++++++++++++++++++++++++++++++++++ STATUS.md | 4 +++- deploy/Caddyfile.snippet | 9 +++++++++ public/.gitkeep | 0 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .env.production.example create mode 100644 Dockerfile create mode 100644 deploy/Caddyfile.snippet create mode 100644 public/.gitkeep diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..72bd66a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +node_modules +.next +.git +deploy +drizzle +docs +*.md +.env* +!.env.example +.vscode diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..7077e81 --- /dev/null +++ b/.env.production.example @@ -0,0 +1,40 @@ +# ── famapp ──────────────────────────────────────────────────────────────────── + +# Public URL for the app (used in share links, OIDC redirect URIs, etc.) +NEXT_PUBLIC_APP_URL=https://fam.ginnoir.com + +# famapp Postgres credentials (used to build DATABASE_URL inside compose.yaml) +FAMAPP_DB_USER=famapp +FAMAPP_DB_PASSWORD=replace-with-strong-password +FAMAPP_DB_NAME=famapp + +# Auth.js session secret — generate with: openssl rand -base64 32 +AUTH_SECRET=replace-with-openssl-rand-base64-32 + +# OIDC provider (Authentik) — task 06 will fill these in after bootstrapping +AUTH_OIDC_ISSUER=https://auth.ginnoir.com/application/o/famapp/ +AUTH_OIDC_CLIENT_ID=replace-me +AUTH_OIDC_CLIENT_SECRET=replace-me + +# Web Push VAPID keys — generate with: pnpm vapid:generate +VAPID_PUBLIC_KEY= +VAPID_PRIVATE_KEY= +# Must be "mailto:
" or a URL +VAPID_SUBJECT=mailto:you@example.com + +# ntfy (optional push fallback — leave blank to disable) +NTFY_URL= +NTFY_TOPIC= + +# Log level: error | warn | info | debug +LOG_LEVEL=info + +# ── Authentik ───────────────────────────────────────────────────────────────── + +# Authentik Postgres credentials (separate DB per Matt's rule) +AUTHENTIK_DB_USER=authentik +AUTHENTIK_DB_PASSWORD=replace-with-strong-password +AUTHENTIK_DB_NAME=authentik + +# Authentik secret key — generate with: openssl rand -base64 60 +AUTHENTIK_SECRET_KEY=replace-with-openssl-rand-base64-60 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2495a8e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# syntax=docker/dockerfile:1 + +# ── Stage 1: fetch dependencies ─────────────────────────────────────────────── +FROM node:22-alpine AS deps +RUN corepack enable && corepack prepare pnpm@10.33.3 --activate +WORKDIR /app +COPY package.json pnpm-lock.yaml .npmrc ./ +# Populate the pnpm virtual store; this layer is cached until lock file changes. +RUN pnpm fetch + +# ── Stage 2: build ──────────────────────────────────────────────────────────── +FROM deps AS builder +WORKDIR /app +# CI=true prevents pnpm from prompting for TTY confirmation when removing modules dir +ENV CI=true +ENV NEXT_TELEMETRY_DISABLED=1 +COPY . . +RUN pnpm install --offline --frozen-lockfile +RUN pnpm build + +# ── Stage 3: production runtime ─────────────────────────────────────────────── +FROM node:22-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +RUN addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs + +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 + +USER nextjs +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/STATUS.md b/STATUS.md index c825f33..46a7452 100644 --- a/STATUS.md +++ b/STATUS.md @@ -9,9 +9,11 @@ Living progress tracker. Update at the end of each task. The canonical brief is - **03 — Drizzle + Postgres setup**. drizzle-orm + postgres driver + drizzle-kit wired up. `src/modules/_core/schema.ts` declares `users`, `households`, `household_members`. `docker-compose.dev.yaml` starts Postgres 16. `drizzle/0000_silent_magma.sql` generated and applied. `tsc --noEmit` passes. - **04 — Module loader & registry**. `src/modules/_core/module.ts` types (`ModuleManifest`, `EntityTypeRegistration`, `DashboardWidget`, etc.), `registry.ts` singleton with `registerModule`/`getRegistry`/`getEntityType`/`getWidget`, barrel `_core/index.ts`. Stub manifests for `calendar`, `lists`, `notes`. `src/modules/index.ts` loader. Root layout imports loader; `AppNav` reads registry for nav links. `/debug/registry` dumps full registry JSON (dev only). Uses zod v4 + built-in `z.toJSONSchema()`. `tsc --noEmit`, `pnpm build`, `pnpm lint` all clean. +- **05 — Compose + Caddy**. `Dockerfile` (3-stage: deps/builder/runner, pnpm fetch + offline install, non-root `nextjs` user, `output: standalone`), `deploy/compose.yaml` (famapp + famapp-db + full Authentik stack on `famapp_net`), `deploy/Caddyfile.snippet`, `.env.production.example`. `docker build -t famapp .` succeeds (~311 MB); `docker compose -f deploy/compose.yaml config` validates clean. Added `.dockerignore` and `public/.gitkeep`. + ## Next up -- **05 — Compose + Caddy** ([brief](docs/tasks/05-compose-caddy.md)). +- **06 — Authentik OIDC** ([brief](docs/tasks/06-authentik-oidc.md)). ## Phase 1 remaining diff --git a/deploy/Caddyfile.snippet b/deploy/Caddyfile.snippet new file mode 100644 index 0000000..a57e39c --- /dev/null +++ b/deploy/Caddyfile.snippet @@ -0,0 +1,9 @@ +# Include from main Caddyfile or paste into the existing one. + +fam.ginnoir.com { + reverse_proxy famapp:3000 +} + +auth.ginnoir.com { + reverse_proxy authentik-server:9000 +} diff --git a/public/.gitkeep b/public/.gitkeep new file mode 100644 index 0000000..e69de29