# ADR-004 — Docker and Compose service model ## Context All services run as Docker containers managed via Docker Compose. This document defines how services are structured, deployed, and maintained. ## Core principles - **No hand-edited files on hosts**: all Compose files are rendered by Ansible from Jinja2 templates. If a file exists on a host, it was put there by Ansible. - **Compose per service**: each service (or tightly coupled service group) gets its own Compose file and directory under a standard path. - **Variables drive differences**: the same template renders differently per host via `group_vars` and `host_vars`. No host-specific templates. ## Directory layout on hosts ``` /opt/services/ ├── servicename/ │ ├── docker-compose.yml # rendered by Ansible, never edited manually │ ├── .env # rendered by Ansible from vault variables │ └── data/ # persistent volumes (bind mounts) │ └── ... ``` All services live under `/opt/services/`. The path is defined in `group_vars/all/vars.yml` as `services__base_dir`. ## Compose file delivery Each service has a corresponding Ansible role (or is managed by a shared role with per-service variables). The role: 1. Creates `/opt/services/servicename/` directory 2. Renders `docker-compose.yml` from `templates/docker-compose.yml.j2` 3. Renders `.env` from `templates/env.j2` (pulling secrets from vault variables) 4. Runs `docker compose up -d --remove-orphans` via `ansible.builtin.command` 5. Optionally runs `docker compose pull` before up (controlled by variable) ## Docker daemon configuration Managed by the `docker_host` role. Key settings: - `"log-driver": "json-file"` with size limits (prevents disk exhaustion) - `"iptables": false` — firewall managed entirely by nftables (see ADR-002) - TCP socket disabled — Unix socket only (`/var/run/docker.sock`) - User namespace remapping: evaluated per use case, not enabled by default ## Networking - Each service Compose file defines its own named network(s) - Services that need to communicate are placed on a shared named network defined in a dedicated `docker-compose.networks.yml` (if cross-service networking is needed on a host) - External port publishing is explicit and matches nftables rules ## Image management - Images are always pinned to a specific digest or tag in templates - `latest` is never used in production Compose files - Image updates are a deliberate operation: update the tag variable, run deploy ## Persistent data - Bind mounts preferred over named volumes for data that must be backed up - All bind mount paths are under `/opt/services//data/` - Backup strategy is defined separately (not in scope of this repo) ## Decision Docker Compose was chosen over Kubernetes/Swarm because: - Appropriate complexity level for 2–5 hosts with independent service sets - Compose files are human-readable and easily auditable - No distributed state to manage - Straightforward to back up and restore