Skip to content

Commit 600548e

Browse files
bpamiriclaude
andauthored
docs(web/guides): align deployment guides with verified wheels deploy behavior (audit batch 2) (#3102)
* docs(web/guides): fix deployment guides to match verified wheels deploy behavior Audit batch 2 (p1-16-deploy) corrections across seven deployment guides: broken invocations replaced with the working flat aliases, env.secret delivery marked pending (#2956/#2957), setup==deploy orchestration gap called out (#2957), failure-hook masking documented (#3087), accessory files:/labels corrected (#3088), and all four broken observability examples fixed against a live Lucee 7 harness. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com> * docs(web/guides): version-qualify the docker-login stdin claim in the secrets flow The flow diagram asserted the registry password is fed to docker login over stdin unconditionally, but that is develop-only behavior (post-#3008). The released 4.0.3 CLI still inlines it via -p <password>, exposing it in the remote process table and --dry-run output. Qualify the claim to match the 4.0.3-vs-develop split the rest of the page already uses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com> --------- Signed-off-by: Peter Amiri <peter@alurium.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 5e6adca commit 600548e

7 files changed

Lines changed: 139 additions & 89 deletions

File tree

web/sites/guides/src/content/docs/v4-0-0/deployment/accessories.mdx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ accessories:
4949
5050
`wheels deploy accessory boot redis` on first deploy. Produces a container named `<service>-redis` on the named host, published on 6379. From the app side, connect to `redis://192.0.2.20:6379`.
5151

52-
## Postgres with volume, env, and an init file
52+
## Postgres with volume and env
5353

5454
```yaml title="config/deploy.yml (illustrative — do not type)"
5555
accessories:
@@ -61,21 +61,23 @@ accessories:
6161
clear:
6262
POSTGRES_USER: app
6363
POSTGRES_DB: myapp_production
64-
secret:
65-
- POSTGRES_PASSWORD
6664
volumes:
6765
- /data/pg:/var/lib/postgresql/data
68-
files:
69-
- config/init.sql:/docker-entrypoint-initdb.d/init.sql
7066
```
7167

72-
- `env.secret:` pulls `POSTGRES_PASSWORD` from `.kamal/secrets` — never commit it to `deploy.yml`.
68+
- `env.clear:` values become `docker run -e` flags on the accessory container.
7369
- `volumes:` persists `/var/lib/postgresql/data` to the host so the database survives `docker rm`.
74-
- `files:` uploads local paths to the container filesystem at deploy time. Useful for initialization scripts, client certs, or any read-only payload the container needs.
70+
71+
<Aside type="caution" title="env.secret and files: are not delivered yet">
72+
Two Kamal accessory features are parsed but not implemented in the current Phase 1 CLI:
73+
74+
- `env.secret:` — secret values from `.kamal/secrets` are not delivered to accessory containers ([#2956](https://github.com/wheels-dev/wheels/issues/2956)/[#2957](https://github.com/wheels-dev/wheels/issues/2957)). For something like `POSTGRES_PASSWORD` you currently have to provision the value another way (e.g. a host-managed env file or volume).
75+
- `files:` — Kamal's "upload local paths into the container filesystem" mechanism is accepted by the config validator but no upload ever happens ([#3088](https://github.com/wheels-dev/wheels/issues/3088)). Initialization scripts and certs need to be placed on the host yourself and mounted via `volumes:`.
76+
</Aside>
7577

7678
## Named containers and labels
7779

78-
Accessory containers are named `<service>-<accessory>` — the example above yields `myapp-db` and `myapp-redis`. They carry the same `service=` label as your app containers, so `wheels deploy details` lists them alongside everything else.
80+
Accessory containers are named `<service>-<accessory>` — the example above yields `myapp-db` and `myapp-redis`. Their `service=` label uses that same combined value (`service=myapp-db`), **not** the app containers' bare `service=myapp` — so a `docker ps --filter label=service=myapp` won't catch them. `wheels deploy details` still lists them alongside everything else because it inspects each declared accessory container by name rather than relying on the shared label.
7981

8082
## Multi-host accessories
8183

@@ -114,7 +116,7 @@ wheels deploy accessory boot all
114116
wheels deploy accessory stop all
115117
```
116118

117-
`wheels deploy setup` boots every declared accessory as part of first-run. `wheels deploy remove` tears them down.
119+
`wheels deploy setup` does **not** boot accessories in the current Phase 1 CLI — it's an alias for `wheels deploy`, and full first-run orchestration is tracked in [#2957](https://github.com/wheels-dev/wheels/issues/2957). Run `wheels deploy accessory boot all` explicitly as part of first-run. `wheels deploy remove` does tear them down.
118120

119121
## Accessories and `wheels deploy`
120122

@@ -133,6 +135,6 @@ When you do want to change an accessory — a Redis version bump, a Postgres con
133135
<LinkCard
134136
title="Secrets"
135137
href="/v4-0-0/deployment/secrets/"
136-
description="How .kamal/secrets feeds accessory env.secret lists."
138+
description="How .kamal/secrets works today, and why env.secret delivery is still pending."
137139
/>
138140
</CardGrid>

web/sites/guides/src/content/docs/v4-0-0/deployment/first-deploy.mdx

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ This page takes an existing Wheels app and ships it to one or more Linux servers
1818
- How to verify the rollout, tail logs, and roll back if something breaks
1919

2020
<Aside type="note" title="Assumptions">
21-
You have a Wheels app that already builds into a working Docker image. You have at least one Linux server you can reach over SSH as a user with passwordless `sudo`, or as root. Docker can be installed later with `wheels deploy server bootstrap` — you don't need it preinstalled. You have a container registry (Docker Hub, GHCR, ECR, or self-hosted) you can push to.
21+
You have a Wheels app that already builds into a working Docker image. You have at least one Linux server you can reach over SSH as a user with passwordless `sudo`, or as root. Docker can be installed later with `wheels deploy bootstrap` — you don't need it preinstalled. You have a container registry (Docker Hub, GHCR, ECR, or self-hosted) you can push to.
2222
</Aside>
2323

2424
## Before you start
@@ -27,7 +27,7 @@ Three quick checks before running anything:
2727

2828
- **SSH works.** `ssh deploy@your-host "uname -a"` succeeds without a password prompt.
2929
- **Registry login works.** `docker login <registry>` succeeds locally.
30-
- **Your app has a `Dockerfile`.** It builds and runs locally (`docker build . && docker run <image>`). If not, write the `Dockerfile` first — `wheels deploy` doesn't generate one.
30+
- **Your app has a working `Dockerfile`.** It builds and runs locally (`docker build . && docker run <image>`). If you don't have one yet, `wheels deploy init` generates a starter `Dockerfile` in step 1 (and refuses to overwrite an existing one unless you pass `--force`) — but verify the generated one actually builds your app before deploying.
3131

3232
## Walk-through
3333

@@ -41,10 +41,12 @@ Three quick checks before running anything:
4141
wheels deploy init
4242
```
4343

44-
Creates two files:
44+
Creates four files:
4545

4646
- `config/deploy.yml` — the deploy manifest. Under git.
4747
- `.kamal/secrets` — secret values. **Never under git.** Add `.kamal/secrets` to `.gitignore` immediately.
48+
- `Dockerfile` — a starter production Dockerfile. If one already exists, `init` refuses to overwrite it unless you pass `--force`.
49+
- `.dockerignore` — keeps build context lean.
4850

4951
A `.kamal/hooks/` directory is also created for optional local hook scripts. It's empty and safe to leave that way.
5052

@@ -88,7 +90,8 @@ Three quick checks before running anything:
8890
# Registry — matches registry.password[0] in deploy.yml
8991
KAMAL_REGISTRY_PASSWORD=$(op read op://Production/Registry/password)
9092
91-
# App-level secrets — referenced under env.secret in deploy.yml
93+
# App-level secrets — will feed env.secret once container delivery
94+
# lands (#2956/#2957); harmless to stage here in the meantime
9295
DATABASE_URL=$(op read op://Production/App/database-url)
9396
WHEELS_RELOAD_PASSWORD=$(op read op://Production/App/reload-password)
9497
```
@@ -106,30 +109,43 @@ Three quick checks before running anything:
106109
clear:
107110
WHEELS_ENV: production
108111
WHEELS_DATASOURCE_CLASS: com.mysql.cj.jdbc.Driver
109-
secret:
110-
- DATABASE_URL
111-
- WHEELS_RELOAD_PASSWORD
112112
```
113113

114-
`clear:` values are baked into `deploy.yml` and safe under git. `secret:` names pull from `.kamal/secrets`. At deploy time, `wheels deploy` translates both into `docker run -e` flags.
114+
`clear:` values are baked into `deploy.yml` and safe under git. At deploy time, `wheels deploy` translates them into `docker run -e` flags.
115+
116+
<Aside type="caution" title="env.secret is not yet supported">
117+
Kamal's `env.secret:` block — naming `.kamal/secrets` keys to be passed into the app container — is **not delivered to containers yet**. The released 4.0.3 CLI silently drops `env.secret` entries; CLIs built from `develop` reject the block outright with `Wheels.Deploy.EnvSecretUnsupported` so the gap can't bite silently. Secret delivery to app containers is tracked in [#2956](https://github.com/wheels-dev/wheels/issues/2956) and [#2957](https://github.com/wheels-dev/wheels/issues/2957). Until it lands, get secrets into the container another way (bake non-secrets into the image, or mount an env file you manage yourself). `.kamal/secrets` still works for its other consumer: `registry.password` resolution at `docker login` time.
118+
</Aside>
115119

116120
5. **Bootstrap Docker on the host (first time only).**
117121

118122
If Docker is already installed on the target, skip this step. Otherwise:
119123

120-
```bash {test:cli cmd="wheels deploy server bootstrap"}
121-
wheels deploy server bootstrap
124+
```bash title="Install Docker on every host (requires reachable hosts)"
125+
wheels deploy bootstrap
122126
```
123127

124128
Runs `which docker || curl -fsSL https://get.docker.com | sh` on every host. Idempotent — safe to run on a host that already has Docker. Fails fast if SSH or `sudo` isn't configured correctly.
125129

130+
<Aside type="note" title="Use the flat verb">
131+
Use `wheels deploy bootstrap`, not `wheels deploy server bootstrap`. The CLI runtime registers its own top-level `server` subcommand (Lucee instance management), so the nested form prints that unrelated help text and never reaches the deploy module ([#2677](https://github.com/wheels-dev/wheels/issues/2677)).
132+
</Aside>
133+
126134
6. **Run setup once.**
127135

128136
```bash title="First-time deploy"
129137
wheels deploy setup
130138
```
131139

132-
`setup` is the first-run verb. It runs the full deploy flow *and* boots `kamal-proxy` and any accessories you've declared. On subsequent deploys you use `wheels deploy` (without `setup`) because the proxy and accessories are already running.
140+
`setup` is the first-run verb. In the current Phase 1 CLI it is an alias for `wheels deploy` — it does **not** yet boot `kamal-proxy` or any accessories you've declared (full first-run orchestration is tracked in [#2957](https://github.com/wheels-dev/wheels/issues/2957)). Boot those explicitly before (or right after) your first deploy:
141+
142+
```bash title="First-run orchestration (run explicitly for now)"
143+
wheels deploy proxy boot
144+
wheels deploy accessory boot all
145+
wheels deploy setup
146+
```
147+
148+
On subsequent deploys you use `wheels deploy` — the proxy and accessories are already running.
133149

134150
Expect a few minutes on the first run — Docker pulls the base images for `kamal-proxy`, any accessories, and your app. Output is prefixed with `[host]` so you can see what each server is doing in parallel.
135151

@@ -140,10 +156,10 @@ Three quick checks before running anything:
140156
Check the container state:
141157

142158
```bash title="Check container state"
143-
wheels deploy app details
159+
wheels deploy app containers
144160
```
145161

146-
Prints `docker ps` output filtered by your service label, for every host. You should see one running container per host per role.
162+
Prints `docker ps` output filtered by your service label, for every host. You should see one running container per host per role. (`wheels deploy details` gives the wider view — app, proxy, and accessories. `wheels deploy app details` is a different verb: it requires `--release=<version>` and reports a single container's `docker inspect` status.)
147163

148164
8. **Make a change and redeploy.**
149165

@@ -155,7 +171,7 @@ Three quick checks before running anything:
155171

156172
Same command, no `setup`. The rolling flow kicks in: new image builds, pushes, pulls to every host, then the proxy cuts traffic over host-by-host. Zero downtime — `kamal-proxy` drains in-flight requests to the old container before switching.
157173

158-
The version label is the short git sha by default. Override with `--version=<tag>` if you tag releases differently.
174+
The version label is the short git sha by default. Override with `--release=<tag>` if you tag releases differently. (`--release` rather than `--version` because the CLI runtime's root parser claims `--version` for itself.)
159175

160176
</Steps>
161177

@@ -178,12 +194,12 @@ If something looks wrong, `wheels deploy details` is always the first stop.
178194
Every deploy tags its container with the version label (git sha by default). To roll back, you point `wheels deploy rollback` at a previous version:
179195

180196
```bash title="Roll back to a previous version"
181-
wheels deploy rollback --version=abc1234
197+
wheels deploy rollback abc1234
182198
```
183199

184-
Finds containers tagged `abc1234` on every host, starts them, and asks the proxy to switch traffic back. The old containers usually still exist on-host — `wheels deploy` doesn't prune aggressively — so rollback is fast. If you've pruned, the rollback fails at the "no such container" step; re-deploy from that sha instead.
200+
The version is a positional argument, same as Ruby Kamal's `kamal rollback VERSION`. Don't write `--version=abc1234` — the CLI runtime's root parser intercepts `--version` before the deploy module sees it and the command fails ([#2674](https://github.com/wheels-dev/wheels/issues/2674)).
185201

186-
Scoped rollouts and rollbacks are supported via `--hosts=` and `--role=` flags, useful when you want to test a change on one host before fanning out.
202+
Finds containers tagged `abc1234` on every host, starts them, and asks the proxy to switch traffic back. The old containers usually still exist on-host — `wheels deploy` doesn't prune aggressively — so rollback is fast. If you've pruned, the rollback fails at the "no such container" step; re-deploy from that sha instead.
187203

188204
## Troubleshooting
189205

web/sites/guides/src/content/docs/v4-0-0/deployment/hooks.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,19 @@ You've read [Your First Deploy](/v4-0-0/deployment/first-deploy/). Hooks are use
2727
|------|-------|---------|
2828
| `pre-deploy` | Before any host work starts. | Yes — non-zero exit aborts the deploy before anything on the servers changes. |
2929
| `post-deploy` | After a successful deploy. | Yes — non-zero exit fails the deploy after-the-fact, useful for smoke tests. |
30-
| `post-deploy-failure` | After a deploy that threw an error. | No — this is already a failure path; the exit code is logged but doesn't change what happens. |
30+
| `post-deploy-failure` | After a deploy that threw an error. | Already a failure path — but make the hook itself exit 0. A non-zero exit currently throws and **replaces the original deploy error** in the output, masking the root cause ([#3087](https://github.com/wheels-dev/wheels/issues/3087)). |
3131

3232
All three live under `.kamal/hooks/` and must be executable (`chmod +x`). They run on the control machine — the same machine running `wheels deploy` — not on the target hosts.
3333

3434
## Environment variables every hook receives
3535

36-
`wheels deploy` fires every hook with a `KAMAL_*` env block that matches Ruby Kamal's contract exactly. Keeping the prefix `KAMAL_` (not `WHEELS_`) means any hook written for Ruby Kamal works unchanged — you can port hooks between the two tools without editing.
36+
`wheels deploy` fires every hook with a `KAMAL_*` env block that follows Ruby Kamal's naming. Keeping the prefix `KAMAL_` (not `WHEELS_`) means hooks written for Ruby Kamal that read these variables work unchanged — you can port them between the two tools without editing. The block is a subset of Ruby Kamal's full contract: variables like `KAMAL_ROLE` and `KAMAL_SERVICE` are not set.
3737

3838
| Variable | When available | Value |
3939
|----------|---------------|-------|
4040
| `KAMAL_VERSION` | all events | Version being deployed (git short sha by default). |
4141
| `KAMAL_HOSTS` | all events | Comma-separated list of hosts in the deploy. |
42-
| `KAMAL_PERFORMER` | all events | Local user running `wheels deploy` (`$USER`). |
43-
| `KAMAL_ROLE` | all events | Role being deployed, or empty when the deploy spans roles. |
42+
| `KAMAL_PERFORMER` | all events | `git config user.name`, falling back to `$USER` when unset. |
4443
| `KAMAL_DESTINATION` | all events | Value of `--destination`, or empty. |
4544
| `KAMAL_RUNTIME` | `post-deploy`, `post-deploy-failure` | Seconds elapsed since the deploy started. |
4645
| `KAMAL_ERROR` | `post-deploy-failure` only | Error message that stopped the deploy. |
@@ -83,7 +82,8 @@ The non-zero exit stops `wheels deploy` before it touches production.
8382

8483
```bash title=".kamal/hooks/post-deploy-failure (illustrative — do not type)"
8584
#!/usr/bin/env bash
86-
set -euo pipefail
85+
# Deliberately no `set -e`: exit 0 even if the page fails, so the
86+
# original deploy error stays visible (see #3087).
8787

8888
curl -sS -X POST https://events.pagerduty.com/v2/enqueue \
8989
-H 'Content-Type: application/json' \
@@ -98,7 +98,7 @@ curl -sS -X POST https://events.pagerduty.com/v2/enqueue \
9898
}
9999
}
100100
EOF
101-
)"
101+
)" || true
102102
```
103103

104104
## Output
@@ -112,7 +112,7 @@ Hook stdout and stderr are merged and prefixed with `[hook:<name>]` in the deplo
112112

113113
A non-zero exit from `pre-deploy` aborts the deploy before any server work. A non-zero exit from `post-deploy` marks the deploy as failed after-the-fact — the containers are already rolled over, so this is useful for integration smoke tests that gate "did the deploy actually work" rather than "can I start the new container."
114114

115-
`post-deploy-failure` never changes the exit code; it runs best-effort on an already-failed path.
115+
`post-deploy-failure` runs on an already-failed path, so the overall exit stays non-zero either way — but a non-zero exit from the hook itself currently throws and **replaces the original deploy error** in the output ([#3087](https://github.com/wheels-dev/wheels/issues/3087)). Until that's fixed, write `post-deploy-failure` hooks to always exit 0 (swallow notification failures with `|| true`) so the real error survives.
116116

117117
## Debugging hooks
118118

@@ -141,7 +141,7 @@ KAMAL_PERFORMER=$USER \
141141
<LinkCard
142142
title="Secrets"
143143
href="/v4-0-0/deployment/secrets/"
144-
description="Hooks can shell out to wheels deploy secrets fetch."
144+
description="Hooks can shell out to wheels deploy fetch-secrets."
145145
/>
146146
<LinkCard
147147
title="Migrating from Kamal"

0 commit comments

Comments
 (0)