# 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/// a stack that runs on exactly one host server/shared// a payload used by more than one host or instance server/shared/components/ single files that belong to another stack's directory ``` 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 six 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 `.env`. | | `shared/metrics/` | `metrics.yml` | Prometheus and Grafana, identical on both hosts, so it lives here rather than being duplicated per host. | | `shared/components/banner.html` | `banner.yml` | A single file that belongs to the reverse proxy's directory on every host. | ## 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. Ship a `.env.example` listing the names with no values; the role never syncs `.env` itself. **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.