86 lines
3.9 KiB
Markdown
86 lines
3.9 KiB
Markdown
# ADR 0010: Single-host Docker Compose as the interim deployment
|
|
|
|
## Status
|
|
|
|
Accepted. **Recorded retrospectively** during the 2026-08-12 documentation pass.
|
|
Does **not** supersede [ADR 0002](0002-argocd-gitops.md), whose own status line
|
|
says it remains the target:
|
|
|
|
> **Accepted — still the target, not yet implemented.** Not superseded by the
|
|
> current production setup.
|
|
|
|
## Context
|
|
|
|
ADR 0002 chose GitOps on the team's ArgoCD instance. A complete Helm chart
|
|
(`infra/helm/medical-chatbot/`) and three ArgoCD `Application` manifests exist.
|
|
Neither has been applied: each `Application` carries three unresolved `TODO`s
|
|
(project/RBAC scope, repo URL, target cluster), `infra/k8s/base|overlays/` hold
|
|
only `.gitkeep`, and no image registry is configured anywhere.
|
|
|
|
Meanwhile the product is live at `https://realvuxbaro.me`.
|
|
|
|
## Decision
|
|
|
|
Run production as Docker Compose on a single EC2 host, with Caddy terminating
|
|
TLS, and deploy by SSH from GitHub Actions.
|
|
|
|
Verifiable from the repository:
|
|
|
|
- `infra/docker/docker-compose.prod.yml` — postgres, qdrant, ai-service, web,
|
|
caddy, with named volumes.
|
|
- `infra/docker/docker-compose.observability.yml` — the OTel/Prometheus/Tempo/
|
|
Grafana overlay, which also sets `OTEL_ENABLED=true`.
|
|
- `infra/docker/Caddyfile` — `realvuxbaro.me` → `web:3000`, `/grafana/*` →
|
|
`grafana:3000`.
|
|
- `.github/workflows/deploy.yml` — `appleboy/ssh-action`, `git reset --hard`,
|
|
`docker compose up -d --build`, `caddy reload`, `python -m migrate`, then ~18
|
|
assertions.
|
|
|
|
## Consequences
|
|
|
|
**Accepted trade-offs**
|
|
|
|
- Images are built on the production host and are untagged, so there is **no
|
|
artifact to roll back to**; recovery is a revert commit plus a rebuild.
|
|
- Deploys are in-place, with brief per-service downtime.
|
|
- No horizontal scaling. That happens to align with the in-process agent state
|
|
described in [02-system-architecture.md](../02-system-architecture.md#the-stateful-detail-that-constrains-scaling),
|
|
but the alignment is coincidental, not enforced.
|
|
- Configuration and secrets live in an uncommitted `.env.prod` on the host, so
|
|
production configuration cannot be reviewed in Git.
|
|
- `postgres` and `qdrant` are deliberately absent from the workflow's `up -d`
|
|
list, so a code deploy never restarts the stateful services — and changes to
|
|
their service definitions do not take effect until someone restarts them.
|
|
|
|
**Preserved despite the simpler runtime**
|
|
|
|
The deploy script asserts far more than a Compose deploy usually does: service
|
|
health, a **real grounded answer** from the real corpus (`decision=answerable`
|
|
with a `chi_dinh` citation), both Grafana datasources, the provisioned
|
|
dashboard, public reachability of `/grafana/login`, and end-to-end trace
|
|
propagation by asserting that a specific `X-Trace-ID` becomes retrievable from
|
|
Tempo. That verification block is what makes the simpler runtime defensible.
|
|
|
|
**Migration path**
|
|
|
|
The Helm chart already maps every setting in `config.py` to a ConfigMap, mounts
|
|
`POSTGRES_DSN` from a Secret, and configures readiness/liveness/startup probes
|
|
against the same `/ready` and `/health` endpoints Compose uses. Moving to
|
|
Kubernetes therefore needs: an image registry and tagging, a corpus-load or
|
|
snapshot-restore step (the chart provisions an **empty** Qdrant, against which
|
|
`ai-service`'s manifest check refuses to start), the three ArgoCD `TODO`s
|
|
resolved, and the `bump-image-tag` workflow that
|
|
`infra/ci/github-actions/README.md` describes but does not contain.
|
|
|
|
## Rationale
|
|
|
|
**Decision observed; rationale not fully recoverable from the repository.** The
|
|
Compose header comment records one constraint —
|
|
|
|
> No GPU, no team k3s — Bedrock calls go out over the instance's IAM role … so
|
|
> no AWS access keys live in this file or its env files.
|
|
|
|
— and ADR 0002 remaining un-superseded shows the Kubernetes target was not
|
|
abandoned. Beyond that, whether the driver was cost, cluster access, or time to
|
|
first deployment is not determinable from the code.
|