77 lines
3 KiB
Markdown
77 lines
3 KiB
Markdown
# 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/<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
|