Document docker-compose.<host>.yml fallback for list-replacement deltas

This commit is contained in:
2026-05-03 20:22:41 +02:00
parent e3325d5332
commit 0e33c66fe7
+45 -19
View File
@@ -46,38 +46,64 @@ Some stacks run on **multiple hosts** with the same image but slightly different
```
Charlie/<stack>/
├── docker-compose.yml # the "what" — same on every host
├── docker-compose.yml # the "what" — default, used by hosts without an override
├── docker-compose.<host>.yml # full per-host compose (when the delta is structural)
├── envs/
│ ├── morgott.env # per-host env (Ansible host_vars equivalent)
│ ├── melina.env
│ ├── mohg.env
│ └── miquella.env
├── overrides/ # only when env can't express it (volumes, services)
│ └── miquella.yml # Kustomize overlay equivalent — usually absent
├── deploy.sh # picks env + override based on `hostname -s`
├── overrides/ # additive overlay deltas (when env can't express it)
│ └── <host>.yml
├── deploy.sh # picks env + compose file based on `hostname -s`
└── README.md
```
### Rules
### Resolution order in `deploy.sh` (per host)
- `docker-compose.yml` is **canonical** and works on every host as long as the right env file is loaded. Use `${VAR:-default}` liberally.
- `envs/<host>.env` is tracked in git (no secrets). Secrets-bearing values come from a separate gitignored `.env` (host-local) merged in at deploy time, OR from runtime secret sourcing (vaultwarden, deferred).
- `overrides/<host>.yml` only exists when env vars genuinely can't bridge the difference (e.g. extra volume bind, service add/remove, network change). Aim for zero override files; one or two is acceptable.
- `deploy.sh` is the host-side entrypoint. Always:
```bash
HOST=$(hostname -s)
args=(--env-file "envs/$HOST.env" -f docker-compose.yml)
1. If `docker-compose.<host>.yml` exists → use **only** that (full per-host compose).
2. Else `docker-compose.yml` + (if present) `overrides/<host>.yml` layered on top.
3. If `envs/<host>.env` exists → loaded with `--env-file` in either case.
Hostname is lowercased (`hostname -s | tr '[:upper:]' '[:lower:]'`).
### When to use which
| Delta type | Use |
|---|---|
| Different scalar value (port, env var, URL) | `envs/<host>.env` |
| Add an extra service or scalar field | `overrides/<host>.yml` (compose merges additively) |
| **Replace** a list (volumes, command, networks) | `docker-compose.<host>.yml` (full file) |
| Different image | `docker-compose.<host>.yml` |
Compose's `!override` and `!reset` YAML tags exist for list-replacement, but support is uneven across compose versions (notably broken on Synology DSM's bundled compose). Prefer the full-file approach when replacing lists.
### Canonical `deploy.sh`
```bash
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
HOST=$(hostname -s | tr '[:upper:]' '[:lower:]')
if [ -f "docker-compose.$HOST.yml" ]; then
args=(-f "docker-compose.$HOST.yml")
else
args=(-f docker-compose.yml)
[ -f "overrides/$HOST.yml" ] && args+=(-f "overrides/$HOST.yml")
sudo docker compose "${args[@]}" "$@" # forward `up -d` / `pull` / `down` / etc.
```
fi
[ -f "envs/$HOST.env" ] && args=(--env-file "envs/$HOST.env" "${args[@]}")
exec sudo docker compose "${args[@]}" "$@"
```
### Mental model
- "What" = `docker-compose.yml`
- "Where" = the host running `./deploy.sh` (no inventory file needed; the host knows its own name)
- Per-host vars = `envs/<host>.env` (Ansible `host_vars`)
- Per-host structural deltas = `overrides/<host>.yml` (Kustomize overlay)
- Group defaults = compose env-var defaults (`${VAR:-default}`)
- "What" = `docker-compose.yml` (or per-host file).
- "Where" = the host running `./deploy.sh` (no inventory file; the host knows its own name).
- Per-host vars = `envs/<host>.env`.
- Per-host structural deltas = `overrides/<host>.yml` for additive, `docker-compose.<host>.yml` for replacement.
- Group defaults = compose env-var defaults (`${VAR:-default}`).
### Once runners exist (Phase 2)