docs(web/guides): align deploy config-reference + architecture with verified CLI behavior - #3097
Conversation
…erified CLI behavior Guide-behavioral-audit batch 2 (p1-16-deploy, cfg-*/arch-* claims). Both pages now describe what the released/develop CLI actually does and link the tracking issues for everything broken-but-unfixed instead of documenting aspirational behavior. config-reference.mdx: - Flag every accepted-but-ignored key (boot, logging, retain_containers, minimum_version, asset_path, require_destination, allow_empty_roles, run_directory, readiness_delay, hooks.path, builder.arch/args/remote, ssh.proxy/keys_only, proxy.forward_headers/buffering, role env/options/ labels, accessory files) and link #3088. - env: remove the nonexistent per-role precedence table; env.secret callout (dropped on 4.0.3, EnvSecretUnsupported on develop) citing #2956. - proxy: app_port default is 80, init writes 8080, deploy/rollback hardcode the proxy target to :3000 (#3089). - ssh: user defaults to root; ~/.ssh/config is not read (known_hosts is). - servers: every role currently gets a kamal-proxy deploy call (#2957); host validator colon-count gap (#3086). - Interpolation: drop the nonexistent CLI --env step; secrets resolve from config/.kamal/secrets (#3084). Overlays: config verb ignores --destination (#3085). Validation errors: absolute-path prefix, never line-scoped, plus the fourth invalid-name class (#3008). architecture.mdx: - Annotate aspirational byte-compat rows (audit log never written, kamal network never created, /home/root proxy dir) pending #2957. - Same --env removal + config/.kamal/secrets note (#3084). - init scaffolding renders four templates, not two. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
…n byte-compat aside Ruby Kamal v2.4.0 boots kamal-proxy with a named Docker volume (kamal-proxy-config), not a host home path under /root, so the prior clause was a factual error. State the named-volume behavior and note the bind mount diverges from Kamal either way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
There was a problem hiding this comment.
Wheels Bot — Reviewer
TL;DR: This PR aligns the two deployment guide pages (config-reference.mdx, architecture.mdx) with what the wheels deploy CLI actually does, flagging every accepted-but-ignored config key and linking tracking issues for known defects. I independently verified every load-bearing factual claim in the diff against the deploy source at this head — all of them check out — and all eight referenced issues/PRs exist with matching titles. Verdict: approve.
Verification performed
Each corrected claim was checked against cli/lucli/services/deploy/ source rather than taken on faith:
| Doc claim | Evidence |
|---|---|
ssh.user defaults to root |
config/Ssh.cfc:18-20 (variables.raw.user ?: "root") |
~/.ssh/config not read; known_hosts honored |
lib/SshClient.cfc:67 calls loadKnownHosts(); no OpenSSH client-config parsing anywhere in lib/ |
app_port default 80 / init writes 8080 / proxy target hardcoded :3000 |
config/Proxy.cfc:27-31, templates/deploy/init/deploy.yml.mustache:14, cli/DeployMainCli.cfc:97 and :148 |
Every role receives the kamal-proxy deploy cutover |
DeployMainCli.cfc:92-101 (deploy) and :143-153 (rollback) loop proxy.deploy(role, …) over all roles |
| Host validator under-counts adjacent colons | config/Validator.cfc:72 — listToArray(host, ":", false, …) drops empty fields, so '::1:22' counts one colon and passes |
No CLI --env interpolation step |
cli/DeployArgsParser.cfc has no --env flag; envOverride is a constructor-injected test shim only (ConfigLoader.cfc:23-26) |
Secrets resolve relative to deploy.yml's directory |
ConfigLoader.cfc:55 — projectRoot: getDirectoryFromPath(arguments.path) |
wheels deploy config ignores --destination; details resolves it |
DeployMainCli.cfc:40 loads with no destination opt; :217-222 passes it |
| Validation errors prefixed with absolute path, never line-scoped | Validator.cfc $raise() emits #filePath#: #message#; Module.cfc:2179 sets configPath = expandPath(…) |
Fourth invalid <kind> name: error class (develop only) |
Validator.cfc:84-91; shipped in merged PR #3008 |
env.secret fail-fast on develop |
commands/Base.cfc:55-67 throws Wheels.Deploy.EnvSecretUnsupported |
Role env: / options: / labels: ignored |
AppCommands.cfc — $labelArgs emits exactly the four built-in labels; $envArgs reads only top-level env().clear(); no options flags emitted |
builder.arch/args/remote ignored |
no --platform / --build-arg / remote endpoint in commands/BuilderCommands.cfc |
boot / logging / retain_containers / minimum_version / asset_path / require_destination / allow_empty_roles / run_directory / readiness_delay / hooks.path / ssh.proxy / ssh.keys_only / proxy.forward_headers / proxy.buffering / accessory files: ignored |
each appears only in the Validator.cfc allowlist; accessors (Ssh.proxy(), Ssh.keysOnly(), Accessory.files()) have zero runtime callers |
prune keeps 5 unless --keep |
cli/DeployPruneCli.cfc:30 (opts.keep ?: 5) |
Audit log never written; docker network create never issued; proxy config dir /home/root |
AuditorCommands.record() has no call sites; DockerCommands.create_network() has no callers while --network kamal is used in AppCommands.run; ProxyCommands.cfc:30 builds /home/#ssh().user()#/.config/kamal-proxy |
| Four init templates | cli/lucli/templates/deploy/init/ contains deploy.yml.mustache, secrets.mustache, Dockerfile.mustache, dockerignore.mustache |
Healthcheck defaults /up, 30 |
config/Proxy.cfc:36 |
All eight cross-references (#3084, #3085, #3086, #3088, #3089, #2956, #2957, PR #3008) exist and their titles match the claims they're cited for.
Docs
No findings. Docs-type change, so no changelog.d/ fragment is required. The internal reference .ai/wheels/deploy.md doesn't cover these config knobs, so nothing there contradicts the corrections. The "Deploy guides" CI job passed, confirming both MDX files build (the new <Aside> blocks compile against the existing Starlight import).
Commits
Single commit a1fa9db62 — docs(web/guides): align deploy config-reference + architecture with verified CLI behavior (91-char header, valid docs type, DCO Signed-off-by matching the author). Body explains the why per claim. No findings.
One residual the diff takes on the author's stated live verification: the released-4.0.3 behaviors (silently dropped env.secret, minimum_version: "99.0.0" passing) can't be confirmed from this checkout's source, but develop source is consistent with both — the fail-fast was added post-4.0.3 by #3008, and no version gate exists anywhere in the deploy tree.
There was a problem hiding this comment.
Wheels Bot — Reviewer
TL;DR: This PR aligns the two deployment guide pages (config-reference.mdx, architecture.mdx) with verified wheels deploy CLI behavior — flagging every accepted-but-ignored config key, correcting defaults (ssh.user → root, app_port → 80), and documenting known defects with tracking issues. The head adds one commit over the previously approved revision: a single-line factual correction to the byte-compat aside (Ruby Kamal mounts the named volume kamal-proxy-config, not a host home path). I re-verified the load-bearing claims against the CLI source and all of them hold. Verdict: approve.
Verification (claims spot-checked against source at this head)
- Delta commit (
db875fbe1) — the corrected aside line is accurate on both sides:cli/lucli/services/deploy/commands/ProxyCommands.cfc:30builds--volume /home/<ssh user>/.config/kamal-proxy:..., which resolves to/home/rootfor the defaultrootuser; and Ruby Kamal 2.x boots kamal-proxy with the named volumekamal-proxy-config(itslib/kamal/commands/proxy.rbrun command), so "the bind mount diverges from Kamal either way" is the right framing. This fixes a genuine factual error in the previous revision, which claimed Kamal puts the config dir under/root. ssh.userdefaults to root —cli/lucli/services/deploy/config/Ssh.cfc:18-20(variables.raw.user ?: "root").- Proxy cutover hardcoded to
:3000, issued for every role —cli/lucli/services/deploy/cli/DeployMainCli.cfc:97and:148append& ":3000"insidefor (var role in cfg.roles())loops, confirming both the #3089 aside and the #2957 "every role getskamal-proxy deploy" correction. app_portdefault 80; init scaffolds 8080 —cli/lucli/services/deploy/config/Proxy.cfc:8,27-31;cli/lucli/templates/deploy/init/deploy.yml.mustache:14(app_port: 8080).- Healthcheck defaults
/up, 30 —Proxy.cfc:36({path: "/up", interval: 1, timeout: 30}). - Colon under-count (
'::1:22'passes) —cli/lucli/services/deploy/config/Validator.cfc:72:listToArray(host, ":", false, ...)drops empty fields between adjacent colons, so the count comes out at 1 and the string slips through (#3086). - Fourth validation class on develop —
Validator.cfc:85-88raisesinvalid <kind> name: '<name>' (must match [a-zA-Z0-9][a-zA-Z0-9_.-]*). env.secrethard-fails on develop —cli/lucli/services/deploy/commands/Base.cfc:59throwsWheels.Deploy.EnvSecretUnsupported.- Interpolation chain: no CLI
--envleg; secrets resolved besidedeploy.yml—envOverrideappears nowhere incli/lucli/services/deploy/cli/*.cfc(it's a programmatic test shim only, so removing it from user docs is correct), andConfigLoader.cfc:52confirms ".kamal/secretsalongsidedeploy.yml" resolution (#3084). wheels deploy configignores--destination—DeployMainCli.cfc:39-40callsloader.load(configPath)with no destination struct, whiledetails()at:217-222passes it — so the doc's "usedetails --destination=... --dry-run" workaround is the right pointer (#3085).- Aspirational byte-compat rows —
record(has zero call sites outside its definition inAuditorCommands.cfc;--network kamalis emitted in three command builders butdocker network createis never issued anywhere undercli/lucli/services/deploy/(#2957). - Ignored keys —
retain_containersappears only in the Validator allowlist (Validator.cfc:18), never read by the runtime; hooks are hardcoded to.kamal/hooks(DeployMainCli.cfc:365), sohooks.pathis indeed dead (#3088). - Four init templates —
cli/lucli/templates/deploy/init/contains exactlydeploy.yml.mustache,secrets.mustache,Dockerfile.mustache,dockerignore.mustache, matching the corrected Mustache aside and jmustache dependency note.
Conventions / Commits / Docs
- Both commits conform to commitlint (
docs(web/guides): ..., subjects under 100 chars, DCO sign-off matching the author). The delta commit's body explains the why (factual error vs. Kamal v2.4.0 behavior) — good. - All literal
<...>placeholders in the new prose (<path to deploy.yml>,<container>:3000,<name>) are backtick-fenced, so the MDX compiles; both files already importAside. - Docs-only
docs-type PR: no changelog fragment or test spec required. - PR body documents
pnpm verify:docspassing for both files.
No correctness, convention, cross-engine, or security findings. The previous wheels-bot review approved head a1fa9db6; this head only improves on it by removing an inaccurate claim about Ruby Kamal's proxy config dir.
Summary
Guide-behavioral-audit batch 2, work item p1-16-deploy — the
deployment/config-reference.mdxanddeployment/architecture.mdxslice (claimscfg-01–cfg-16,arch-02–arch-06). Every correction below describes current CLI behavior, verified live against the released 4.0.3 CLI and develop source, and links the tracking issue for anything broken-but-unfixed — no aspirational behavior is documented.config-reference.mdx
boot.*,logging.*,retain_containers,minimum_version,asset_path,require_destination,allow_empty_roles,run_directory,readiness_delay,hooks.path,builder.arch/args/remote,ssh.proxy/keys_only,proxy.forward_headers/buffering, roleenv:/options:/labels:, accessoryfiles:minimum_version: "99.0.0"passes,retain_containers: 10still prunes to 5, role env/options absent fromdocker run)env: removed the per-role precedence table (machinery doesn't exist);env.secretcallout — silently dropped on 4.0.3, hard-failsWheels.Deploy.EnvSecretUnsupportedon developproxy:app_portdefault is 80 (not 3000), init template writes 8080, and deploy/rollback hardcode the proxy target to:3000ignoringapp_port(verifiedDeployMainCli.cfc:97/148)ssh:userdefaults to root (not the local user,Ssh.cfc:18-20);~/.ssh/configis not read (sshj doesn't parse OpenSSH client config;known_hostsis honored)servers: every role currently receives thekamal-proxy deploycutover call, not justweb(observed for ajobrole in dry-run)'::1:22'passes,Validator.cfc:72)--envoverrides" lookup step (no such flag inDeployArgsParser); noted${VAR}secrets resolve fromconfig/.kamal/secrets(relative to deploy.yml's directory), not the project rootwheels deploy configignores--destination; pointed readers atdetails --destination=... --dry-runinvalid <kind> name:class shipped on developarchitecture.mdx
record()has no call sites),docker network create kamalnever issued, proxy config dir resolves to/home/rootfor the default root user--envremoval +config/.kamal/secretsresolution notewheels deploy initscaffolding renders four templates (deploy.yml,secrets,Dockerfile,dockerignore— verified incli/lucli/templates/deploy/init/), not twoVerification
pnpm verify:docs src/content/docs/v4-0-0/deployment/config-reference.mdx src/content/docs/v4-0-0/deployment/architecture.mdx→1 passed, 0 failed, exit 0Ssh.cfc,Proxy.cfc,Validator.cfc,DeployMainCli.cfc, init templates)🤖 Generated with Claude Code