# ADR 0008 — CD: SSH compose deploy, staging/prod split, rollback

- Status: Accepted
- Date: 2026-08-19

## Context

Phase 10 adds continuous deployment on top of the CI image (ADR-0007): deploy to
a single Linux VM over SSH + docker compose, with a protected prod environment and
a rollback path.

## Decisions

- **One reusable `deploy.yml`, two callers.** `ci.yml` calls it for **staging**
  (auto on `main`, deploys `latest`) and **prod** (on a `vX.Y.Z` tag, `image_tag`
  = the tag). Prod uses the protected `production` environment, so GitHub requires
  **manual approval** before it runs. Deploy = SSH → `compose … pull && up -d` →
  wait for container health → smoke test (`/actuator/health` + login→`/users/me`).
- **Secrets provisioned at deploy time, never baked in.** The workflow writes a
  mode-600 `.env` on the host from repo secrets/vars and `scp`s it; nothing
  sensitive enters an image, a committed file, or the logs (GitHub masks the
  values, and a guard step fails fast if a required one is missing). Non-sensitive
  config (paths, hosts, URLs) are repo **vars**; passwords/keys are **secrets**.
  GHCR uses the built-in `GITHUB_TOKEN` — no long-lived registry secret.
- **Rollback by recorded tag.** `deploy.sh` writes `deployed-tag.txt` and rotates
  the prior value into `previous-tag.txt` on the host. The manual `rollback.yml`
  (`workflow_dispatch`) redeploys the previous tag (or an explicit one) and re-runs
  the smoke test. The same `rollback.sh` works by hand on the host.
- **Forward-only migrations.** Flyway runs at startup in `validate` mode; applied
  migrations are immutable (guarded by `FlywayMigrationIT` and Flyway's checksum
  validation). Because rollback can run an **older image against a newer schema**,
  migrations must stay backward-compatible for one release (additive, nullable/
  defaulted) — documented, with a failed-migration recovery runbook, in
  `docs/deployment.md`.

## Consequences

- Scripts (`deploy.sh`, `smoke-test.sh`, `rollback.sh`) are the reusable core,
  validated with `bash -n`; the three workflows are valid YAML. End-to-end deploy
  can only be exercised against a real host, so it is not part of the local gate.
- Newman replaces/extends the bash smoke as the staging post-deploy check in
  Phase 12.
