# CI/CD Target Architecture ## Goal This document captures the intended delivery model for CapaKraken without replacing the currently working manual production setup immediately. The target state is: 1. CI validates every PR. 2. GitHub Actions builds immutable Docker images. 3. Staging and production pull those exact images from a registry. 4. Database migrations run as an explicit deploy step. 5. Traffic is considered safe only after the app answers `GET /api/ready`. ## Core Idea The production host should stop building application code from a Git checkout. Instead, it should only: - pull a versioned `app` image - pull a matching `migrator` image - run Prisma deploy migrations - start the application container - wait for readiness That removes "works on the server but not in CI" drift and makes rollbacks much simpler. ## Delivery Flow ### 1. Pull Request Validation The existing `CI` workflow continues to validate: - typecheck - lint - unit tests - build - E2E This remains the quality gate before merge. ### 2. Image Build The new manual workflow [release-image.yml](/home/hartmut/Documents/Copilot/capakraken/.github/workflows/release-image.yml) builds two images from [Dockerfile.prod](/home/hartmut/Documents/Copilot/capakraken/Dockerfile.prod): - `runner` target as the production app image - `migrator` target as the Prisma migration image Recommended tag format: - `sha-` Example: ```text ghcr.io//capakraken-app:sha-abc123 ghcr.io//capakraken-migrator:sha-abc123 ``` ### 3. Staging Deploy The staging workflow [deploy-staging.yml](/home/hartmut/Documents/Copilot/capakraken/.github/workflows/deploy-staging.yml) is intended to: 1. connect to the staging host over SSH 2. copy the deploy assets 3. export `APP_IMAGE` and `MIGRATOR_IMAGE` 4. run [deploy-compose.sh](/home/hartmut/Documents/Copilot/capakraken/tooling/deploy/deploy-compose.sh) The compose file used for this target flow is [docker-compose.cicd.yml](/home/hartmut/Documents/Copilot/capakraken/docker-compose.cicd.yml). ### 4. Production Promotion The production workflow [deploy-prod.yml](/home/hartmut/Documents/Copilot/capakraken/.github/workflows/deploy-prod.yml) follows the same logic as staging, but the image tag is promoted manually. That means production uses an image that was already built and can already have been exercised in staging. ## Required Infrastructure ### Minimum - GitHub repository with Actions enabled - GHCR or another container registry - 1 Linux host with Docker and Docker Compose - PostgreSQL - Redis - reverse proxy such as nginx - SSH access from GitHub Actions to the host ### Recommended - separate staging and production hosts - GitHub Environments for `staging` and `production` - required reviewer approval for `production` - backup strategy for PostgreSQL volumes - uptime monitoring and error tracking ## Secrets ### GitHub Environment Secrets For `staging`: - `STAGING_SSH_HOST` - `STAGING_SSH_PORT` - `STAGING_SSH_USER` - `STAGING_SSH_KEY` - `STAGING_DEPLOY_PATH` - `STAGING_APP_HOST_PORT` - `STAGING_GHCR_USERNAME` - `STAGING_GHCR_TOKEN` For `production`: - `PROD_SSH_HOST` - `PROD_SSH_PORT` - `PROD_SSH_USER` - `PROD_SSH_KEY` - `PROD_DEPLOY_PATH` - `PROD_APP_HOST_PORT` - `PROD_GHCR_USERNAME` - `PROD_GHCR_TOKEN` ### Host-side Files Each target host should already have: - `.env.production` - Docker installed - network access to the container registry The repository now also contains a small host example at [tooling/deploy/.env.production.example](/home/hartmut/Documents/Copilot/capakraken/tooling/deploy/.env.production.example) and an operator note at [tooling/deploy/README.md](/home/hartmut/Documents/Copilot/capakraken/tooling/deploy/README.md). ### Minimum Host Bootstrap For each target host, create a dedicated deploy directory such as `/opt/capakraken` and place these files there: ```text docker-compose.cicd.yml .env.production tooling/deploy/deploy-compose.sh ``` `.env.production` should hold the long-lived runtime settings, including: ```env POSTGRES_PASSWORD= NEXTAUTH_URL=https://capakraken.example.com NEXTAUTH_SECRET= ``` GitHub Actions only injects the short-lived image references through `deploy.env`. The deploy script then loads both files before calling Docker Compose, so compose interpolation and container runtime env use the same source of truth. ## Database Policy For release environments, use: ```bash pnpm --filter @capakraken/db db:migrate:deploy ``` Do not use `db:push` as the main production deployment mechanism. `db:push` is convenient for local development, but it does not give the release traceability that a migration-based deploy requires. ## Rollback Model Rollback should be image-based: 1. choose the previous good `sha-...` tag 2. run the production deploy workflow again with that tag 3. confirm readiness This is only safe when schema changes follow backwards-compatible expand and contract rules. ## How A Production Update Works The intended production update path is: 1. merge to `main` after the existing CI workflow is green 2. run [release-image.yml](/home/hartmut/Documents/Copilot/capakraken/.github/workflows/release-image.yml) to build immutable `app` and `migrator` images tagged as `sha-` 3. run [deploy-staging.yml](/home/hartmut/Documents/Copilot/capakraken/.github/workflows/deploy-staging.yml) with that exact image tag 4. GitHub Actions uploads the deploy bundle to the staging host and writes a temporary `deploy.env` 5. [deploy-compose.sh](/home/hartmut/Documents/Copilot/capakraken/tooling/deploy/deploy-compose.sh) pulls images, starts PostgreSQL and Redis, runs Prisma deploy migrations, starts the new app container, and waits for `GET /api/ready` 6. after staging is accepted, run [deploy-prod.yml](/home/hartmut/Documents/Copilot/capakraken/.github/workflows/deploy-prod.yml) with the same tag 7. production repeats the same image-based flow, so the running artifact matches staging That means the production host no longer builds from Git. It only receives a versioned image and starts it after migrations complete. ## Current Status The repository now contains the CI/CD scaffolding, but the existing manual production setup remains untouched: - current manual compose flow: [docker-compose.prod.yml](/home/hartmut/Documents/Copilot/capakraken/docker-compose.prod.yml) - current manual runbook: [ci-cd-manual.md](/home/hartmut/Documents/Copilot/capakraken/docs/ci-cd-manual.md) This allows the team to introduce the new path gradually instead of switching production in one step.