3.9 KiB
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, 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 TODOs
(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 setsOTEL_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, but the alignment is coincidental, not enforced.
- Configuration and secrets live in an uncommitted
.env.prodon the host, so production configuration cannot be reviewed in Git. postgresandqdrantare deliberately absent from the workflow'sup -dlist, 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 TODOs
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.