Phase 7: Cosmos deployment

- Dockerfile with a check target (typecheck and tests) and one image per
  service on a pinned Node 22.20 Alpine base, each with only its own
  production dependencies and a health check.
- deploy/compose.yaml: gk-content, gk-admin and gk-site on homelab_network
  with no host ports, read-only root filesystems, no capabilities, the vault's
  Site folder mounted into the content service, and backup.dump=sqlite on it.
- deploy/deploy.sh builds and checks the commit, restarts the stack, and goes
  back to the last good tag if it doesn't come up healthy; rollback.sh swaps
  tags. .gitea/workflows/deploy.yml runs it on a host runner on Cosmos.
- docs/deploy.md runbook (settings, runner, mirror, NPM hosts, Turnstile,
  Healthchecks, backups, cutover) and the stack's inventory note in vault-kit.
- Site: trusts X-Forwarded-Host/Proto for gregkilmer.com hosts so form posts
  pass Astro's origin check behind the proxy; rate limits read
  CF-Connecting-IP; site URL set for absolute links.
- Content service: the held-change webhook carries a Discord-ready message;
  .env.example lists the core settings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
Claude
2026-10-12 00:43:03 +00:00
parent f9a8fb1acd
commit 754c6482be
17 changed files with 472 additions and 5 deletions
+93
View File
@@ -0,0 +1,93 @@
# Images for the GregKilmer.com stack on Cosmos. One file, one target per service:
#
# docker build --target check . runs `npm run check` (typecheck and tests)
# docker build --target content . the content service and MCP server
# docker build --target admin . the admin page (tailnet only)
# docker build --target site . the public site
#
# The Gitea Actions workflow builds check first, so a red check stops a deploy.
# See docs/deploy.md.
# Pinned by version (Node 22.20, released 2025-09). Override with --build-arg to
# pull through a mirror.
ARG NODE_IMAGE=node:22.20.0-alpine
# ---------- package manifests, then every workspace's dependencies ----------
FROM ${NODE_IMAGE} AS manifests
WORKDIR /app
COPY package.json package-lock.json ./
COPY packages/schema/package.json packages/schema/
COPY packages/ui/package.json packages/ui/
COPY services/content/package.json services/content/
COPY services/admin/package.json services/admin/
COPY services/site/package.json services/site/
COPY tools/migrate/package.json tools/migrate/
FROM manifests AS deps
RUN npm ci --no-audit --no-fund
# ---------- typecheck and tests ----------
FROM deps AS check
COPY . .
RUN npm run check
# ---------- the site's server build ----------
FROM deps AS site-build
COPY packages packages
COPY services/site services/site
RUN npm run build -w @gk/site
# ---------- each service's production dependencies only ----------
FROM manifests AS content-deps
RUN npm ci --omit=dev --no-audit --no-fund -w @gk/content && npm cache clean --force
FROM manifests AS admin-deps
RUN npm ci --omit=dev --no-audit --no-fund -w @gk/admin && npm cache clean --force
FROM manifests AS site-deps
RUN npm ci --omit=dev --no-audit --no-fund -w @gk/site && npm cache clean --force
# ---------- shared runtime base ----------
FROM ${NODE_IMAGE} AS runtime
WORKDIR /app
ENV NODE_ENV=production HOST=0.0.0.0
COPY package.json ./
COPY packages/schema/package.json packages/schema/
COPY packages/schema/src packages/schema/src
# ---------- content service ----------
FROM runtime AS content
COPY --from=content-deps /app/node_modules node_modules
COPY services/content/package.json services/content/
COPY services/content/src services/content/src
# The vault's Site folder and the data folder (SQLite and saved form attachments).
ENV PORT=4310 SITE_DIR=/vault/Site DATA_DIR=/data
EXPOSE 4310
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD wget -qO- http://127.0.0.1:4310/health >/dev/null || exit 1
USER node
CMD ["node", "--disable-warning=ExperimentalWarning", "--import", "tsx", "services/content/src/main.ts"]
# ---------- admin page ----------
FROM runtime AS admin
COPY --from=admin-deps /app/node_modules node_modules
COPY services/admin/package.json services/admin/
COPY services/admin/src services/admin/src
COPY services/admin/public services/admin/public
ENV PORT=4320
EXPOSE 4320
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD wget -qO- http://127.0.0.1:4320/health >/dev/null || exit 1
USER node
CMD ["node", "--import", "tsx", "services/admin/src/main.ts"]
# ---------- public site ----------
FROM runtime AS site
COPY --from=site-deps /app/node_modules node_modules
COPY --from=site-build /app/services/site/dist services/site/dist
ENV PORT=4321
EXPOSE 4321
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD wget -qO- http://127.0.0.1:4321/ >/dev/null || exit 1
USER node
CMD ["node", "services/site/dist/server/entry.mjs"]