Files

63 lines
2.7 KiB
Docker

# rfc-app container image — the Cloud Run keystone for per-PR preview
# environments (flotilla SPEC §15). NOT used by production: prod still deploys
# via the pin-based on-VM gesture (flotilla §8). This image exists so flotilla
# `preview up --pr=N` can build a PR's tree and run it as an ephemeral,
# scale-to-zero Cloud Run service with a SEEDED SYNTHETIC database and ZERO real
# secrets (test-secret env only — flotilla §15 / §3 invariant 1).
#
# Single container, single port: nginx serves the built SPA on $PORT (Cloud Run
# injects it) and reverse-proxies /api/ + /auth/ to a single-process uvicorn on
# 127.0.0.1:8000 — mirroring the prod nginx + systemd split
# (deploy/nginx/ohm.wiggleverse.org.conf, deploy/systemd/rfc-app.service), so a
# preview behaves like prod minus the secrets. Single uvicorn process + single
# SQLite file, per §4.2 (never scale workers).
#
# Build context is the repo root: docker build -t <image> .
# ---- stage 1: build the Vite SPA -------------------------------------------
FROM node:20-slim AS web
WORKDIR /app/frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY frontend/ ./
# VITE_* values are baked into the bundle at build time (intentionally public —
# §8 phase-5 note). VITE_APP_NAME is REQUIRED by vite.config.js (the framework
# ships no default — each deployment names itself); previews self-name. Turnstile
# uses Cloudflare's always-pass test SITE key so the widget renders + auto-passes.
ARG VITE_APP_NAME="RFC App (preview)"
ARG VITE_TURNSTILE_SITE_KEY=1x00000000000000000000AA
ARG VITE_AMPLITUDE_API_KEY=
RUN VITE_APP_NAME="$VITE_APP_NAME" \
VITE_TURNSTILE_SITE_KEY="$VITE_TURNSTILE_SITE_KEY" \
VITE_AMPLITUDE_API_KEY="$VITE_AMPLITUDE_API_KEY" \
npm run build
# ---- stage 2: runtime (backend + nginx) ------------------------------------
FROM python:3.12-slim AS runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends nginx gettext-base sqlite3 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt/rfc-app
# Backend deps first for layer caching.
COPY backend/requirements.txt backend/requirements.txt
RUN pip install --no-cache-dir -r backend/requirements.txt
COPY backend/ backend/
COPY deploy/preview/ deploy/preview/
# health.py reads VERSION at parents[2] (== /opt/rfc-app/VERSION).
COPY VERSION ./
COPY --from=web /app/frontend/dist/ frontend/dist/
RUN chmod +x deploy/preview/entrypoint.sh
# Cloud Run injects $PORT (default 8080); the entrypoint renders nginx against
# it. The synthetic preview DB lives on the container's ephemeral filesystem and
# dies with the instance (zero create/seed/drop lifecycle — §15).
ENV PORT=8080 \
DATABASE_PATH=/opt/rfc-app/backend/data/rfc-app.db
EXPOSE 8080
ENTRYPOINT ["deploy/preview/entrypoint.sh"]