Render stack secrets from OpenBao in compose_stack
stack_secrets reads KV v2 on the target host and writes the stack's .env at 0600, failing loudly on a sealed vault, a bad token, a missing field or an unsafe value. Adds an example OpenBao stack on app and moves webapp onto it; metrics keeps a hand-placed .env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
792f7cde48
commit
665ac7d52f
24 files changed
+834
-87
No files matched your search
@@ -16,11 +16,23 @@ files and one button. `AGENTS.md` holds the conventions and the full
|
||||
restructure them. Never commit a `.env`, a key, or a secret written inline in
|
||||
a compose file — read `docs/secrets.md` before you decide something is fine to
|
||||
commit.
|
||||
|
||||
If the stack needs a credential, put it in OpenBao now and ship a
|
||||
`.env.example` listing the variable names. Pipe the value in on stdin rather
|
||||
than passing `<field>=<value>`, which lands it in the host's process table
|
||||
and your shell history, and strip the trailing newline — `<field>=-` stores
|
||||
stdin byte for byte and the role refuses a value containing one:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32 | tr -d '\n' \
|
||||
| docker exec -i openbao bao kv put kv/stacks/<stack> <field>=-
|
||||
```
|
||||
2. **Playbook.** Create `ansible/playbooks/<stack>.yml`. Copy `static-site.yml`;
|
||||
most stacks are a name and a network. Tag the play with the stack name.
|
||||
3. **Register it.** Add an `import_playbook` entry to `ansible/playbooks/site.yml`
|
||||
in dependency order — the reverse proxy owns `proxy-net` and the vhost
|
||||
configuration, so it stays first.
|
||||
configuration, so it stays first, and `openbao` must come before any stack
|
||||
that sets `stack_secrets`.
|
||||
4. **Give it a button.** All three CI configurations need the stack by name, and
|
||||
none of them can generate the list:
|
||||
- `.github/workflows/deploy.yml` — add it to the `stack` input's `options:`.
|
||||
@@ -35,6 +47,7 @@ files and one button. `AGENTS.md` holds the conventions and the full
|
||||
pipeline triggered with a different one.
|
||||
|
||||
Delete whichever platforms you are not using rather than letting them rot.
|
||||
Leave a stack out only if it must never deploy from CI — see the table below.
|
||||
5. **Verify statically.** `make check` from the repository root. It must pass;
|
||||
this is the definition of done for the playbook.
|
||||
6. **Deploy.** Run the check mode, read the diff, then deploy. Never run a
|
||||
@@ -46,16 +59,20 @@ files and one button. `AGENTS.md` holds the conventions and the full
|
||||
| If | Do | Example |
|
||||
| --- | --- | --- |
|
||||
| It is not a Compose stack — one file into another stack's directory, host preparation, or a system service | Write plain tasks. Do not force the role | `banner.yml`, `bootstrap.yml` |
|
||||
| Something must happen between the payload landing and the stack starting (backup, migration) | Use the two entry points, `tasks_from: sync` then `tasks_from: up`, and gate the middle on `pull \| default('policy') == 'always'` | `webapp.yml` |
|
||||
| Something must happen between the payload landing and the stack starting (backup, migration, a precondition) | Use the two entry points, `tasks_from: sync` then `tasks_from: up`, and gate the middle on `pull \| default('policy') == 'always'` or `compose_stack_synced` | `webapp.yml` |
|
||||
| It is replacing a hand-deployed instance whose data must be copied in first | `-e start=false`, copy the data into the bind mounts, then deploy normally. Write the steps into the playbook's header comment | none — see "Deploying without starting" in `AGENTS.md` |
|
||||
| The payload is bind-mounted config, which Compose never recreates a container for | Add a `post_tasks` reload keyed on `compose_stack_synced` | `reverse-proxy.yml`, `metrics.yml` |
|
||||
| …but a restart is itself disruptive (a vault that seals, a runner that kills its own job) | Report on `compose_stack_synced` instead of restarting, and let a person choose when | `openbao.yml` |
|
||||
| The compose file is not named `docker-compose.yml`, or there are several | `stack_files` | `webapp.yml` |
|
||||
| The same payload serves several hosts, or several instances | `stack_src`, and `stack_dest` for the second instance | `metrics.yml`, `webapp-staging.yml` |
|
||||
| The stack needs a `.env` that is deliberately never shipped | `stat` + `assert` that it exists on the host first | `webapp.yml` |
|
||||
| The stack needs a credential | `stack_secrets`, naming the vault path and field for each variable. Put the value in OpenBao first | `webapp.yml` — see the contract in `AGENTS.md` and `docs/secrets.md` |
|
||||
| The stack needs a `.env` placed by hand, on a host with no vault | `stat` + `assert` that it exists on the host first | `metrics.yml` |
|
||||
| It needs an external Docker network | `stack_networks` | any of them |
|
||||
| A bind mount would otherwise be created root-owned by Docker | `stack_dirs`; omit `mode` so an existing directory is left alone | `reverse-proxy.yml` |
|
||||
| A bind mount must be owned by a uid from inside the container | `stack_dirs` with `owner`/`group` and `recurse`, and no `mode` | `metrics.yml` |
|
||||
| A bind mount must be owned by a uid from inside the container | `stack_dirs` with `owner`/`group` and `recurse`, and no `mode`. The host needs `bootstrap.yml -e deploy_sudo=true` | `metrics.yml`, `openbao.yml` |
|
||||
| The image is built from a Dockerfile in the payload | `stack_build`, set to `always` only on an update run | `webapp.yml` |
|
||||
| Hosts differ in some small way | A dict keyed by `inventory_hostname`, looked up in the role vars | `metrics.yml` |
|
||||
| Deploying it from CI would kill the CI job — the runner itself, or whatever the runner depends on | Leave it out of `site.yml` and the deploy form, and assert in `pre_tasks` that `GITHUB_ACTIONS`, `GITEA_ACTIONS` and `GITLAB_CI` are not `true` — the dispatch API accepts any stack with a playbook | none here |
|
||||
|
||||
Extend the role's variable contract rather than adding a second role or loose
|
||||
tasks. If a stack needs something the contract cannot express, say so instead of
|
||||
@@ -76,7 +93,10 @@ working around it — that is information about the contract.
|
||||
every parent it creates — leaving the payload sync unable to write into the
|
||||
stack's own directory.
|
||||
- **Expecting `.env` to reach the host.** It is excluded from every sync by
|
||||
design. Secrets live on the host.
|
||||
design. Use `stack_secrets`, or place it on the host by hand.
|
||||
- **Storing a vault value with a trailing newline.** `bao kv put ... field=-`
|
||||
keeps the newline from `echo` or `openssl rand`, and the role refuses to
|
||||
render it. Pipe through `tr -d '\n'`.
|
||||
- **A reload task with no `when: compose_stack_synced`.** Every run then reports
|
||||
a change, the playbook is never idempotent, and `changed=0` stops meaning
|
||||
anything.
|
||||
|
||||
Reference in new issue
Block a user