One shared script installed into /srv/stacks on every host, with per-host groups in server/<host>/stack-groups. Tested in check.sh. Also two new common mistakes in the adding-a-stack skill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
3.6 KiB
Markdown
60 lines
3.6 KiB
Markdown
# server/ — the payloads
|
|
|
|
Everything under this directory is **data**. These files are copied to the hosts
|
|
verbatim: no templating, no variable substitution by Ansible, no generation.
|
|
|
|
```
|
|
server/<host>/<stack>/ a stack that runs on exactly one host
|
|
server/shared/<stack>/ a payload used by more than one host or instance
|
|
server/shared/components/ single files that belong to another stack's directory,
|
|
or to the stack root itself
|
|
server/<host>/stack-groups that host's stackctl groups; a file, not a stack
|
|
```
|
|
|
|
The directory names under `server/` must match the inventory host names, because
|
|
the role resolves a payload as `server/{{ inventory_hostname }}/{{ stack_name }}`
|
|
unless a playbook overrides `stack_src`.
|
|
|
|
`server/` is excluded from `ansible-lint` and `yamllint`. A compose file is not
|
|
Ansible content, and most of these come from upstream projects that format them
|
|
their own way — linting them produces noise and pressure to reformat files you
|
|
want to be able to diff against upstream.
|
|
|
|
## The examples
|
|
|
|
All seven are fictional. `example.com` is reserved by RFC 2606 and can never
|
|
resolve to a real service. Replace them with your own; the framework around them
|
|
is the reusable part.
|
|
|
|
| Payload | Used by | Notes |
|
|
| --- | --- | --- |
|
|
| `edge/reverse-proxy/` | `reverse-proxy.yml` | nginx-proxy + acme-companion. Owns `proxy-net`. Its `proxy-data/` subdirectories are bind mounts holding plain config files. |
|
|
| `app/static-site/` | `static-site.yml` | One container, one network, no state. The simplest thing that works. |
|
|
| `app/webapp/` | `webapp.yml`, `webapp-staging.yml` | Postgres plus an app built from `app/Dockerfile`. A second compose file holds maintenance-only services. Every credential comes from OpenBao, via `stack_secrets`. |
|
|
| `app/openbao/` | `openbao.yml` | The vault, on `127.0.0.1:8200` only. `config/openbao.hcl` is bind-mounted; a change needs a manual restart and unseal. |
|
|
| `shared/metrics/` | `metrics.yml` | Prometheus and Grafana, identical on both hosts, so it lives here rather than being duplicated per host. Its `.env` is placed on each host by hand. |
|
|
| `shared/components/banner.html` | `banner.yml` | A single file that belongs to the reverse proxy's directory on every host. |
|
|
| `shared/components/stackctl`, `<host>/stack-groups` | `stackctl.yml` | The by-hand stack runner, installed into `/srv/stacks` on every host, with each host's groups beside it. Not stacks. |
|
|
|
|
## Conventions worth copying
|
|
|
|
**Take secrets from `.env`, and require them.** Every credential in these
|
|
payloads is written as `${VAR:?message}`. Compose then refuses to start and names
|
|
the missing variable, instead of expanding it to an empty string and starting the
|
|
service with no password. The `.env` itself is never synced: the role renders
|
|
it on the host from OpenBao for a stack that sets `stack_secrets`, and otherwise
|
|
leaves one placed by hand alone. Ship a `.env.example` listing the names with no
|
|
values as documentation. See `docs/secrets.md`.
|
|
|
|
**Pin image tags.** `nginx:1.27-alpine`, not `nginx:latest`. A `latest` tag means
|
|
a plain deploy will not pick up a new build (Compose keeps the image it has) and
|
|
an update run picks up whatever was published this morning. Both are surprises.
|
|
|
|
**Declare shared networks external.** A network two stacks need is created by the
|
|
playbook through `stack_networks`, not by whichever compose file happened to run
|
|
first. That is what lets every playbook stand on its own.
|
|
|
|
**Do not put host paths in the compose file.** Use `${PWD}` if a sibling
|
|
container needs one — the role exports it as the stack's directory on the host,
|
|
which is the only value that is correct from inside a deploy.
|