3 KiB
3 KiB
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_varsandhost_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:
- Creates
/opt/services/servicename/directory - Renders
docker-compose.ymlfromtemplates/docker-compose.yml.j2 - Renders
.envfromtemplates/env.j2(pulling secrets from vault variables) - Runs
docker compose up -d --remove-orphansviaansible.builtin.command - Optionally runs
docker compose pullbefore 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
latestis 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/<name>/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