Skip to content

Commit cef8816

Browse files
committed
docs(config): add Deploy Quick Reference to CLAUDE.md and surface wheels_deploy MCP tool
1 parent a499b23 commit cef8816

1 file changed

Lines changed: 83 additions & 1 deletion

File tree

CLAUDE.md

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,88 @@ wheels jobs monitor # live dashboard
738738

739739
Requires migration: `20260221000001_createwheels_jobs_table.cfc`. Run with `wheels dbmigrate latest`.
740740

741+
## Deploy Quick Reference
742+
743+
`wheels deploy` ships your Dockerized Wheels app to production Linux servers via SSH. Ported from Basecamp Kamal's developer CLI — same `config/deploy.yml` schema, same on-server conventions (container names, labels, network, lock path), invokes the same `kamal-proxy` Go binary for zero-downtime rollover. No Ruby runtime required.
744+
745+
wheels deploy init # scaffold config/deploy.yml + .kamal/secrets
746+
wheels deploy setup # one-time server bootstrap + first deploy
747+
wheels deploy # rolling deploy
748+
wheels deploy --dry-run # print commands without executing
749+
wheels deploy rollback v1 # roll back to a previous version
750+
wheels deploy config # print resolved config as YAML
751+
wheels deploy version # show Kamal version this port mirrors
752+
753+
### Subcommands
754+
755+
```cfm
756+
wheels deploy app <verb> // boot/start/stop/details/containers/images/logs/live/maintenance/remove
757+
wheels deploy proxy <verb> // boot/reboot/start/stop/restart/details/logs/remove
758+
wheels deploy accessory <verb> // boot/reboot/start/stop/restart/details/logs/remove (sidecars: db/redis/search)
759+
wheels deploy build <verb> // deliver/push/pull/create/remove/details/dev
760+
wheels deploy registry <verb> // setup/login/logout/remove
761+
wheels deploy server <verb> // exec/bootstrap
762+
wheels deploy prune <verb> // all/images/containers [--keep=N]
763+
wheels deploy lock <verb> // acquire/release/status (manual — normal deploys auto-lock)
764+
wheels deploy secrets <verb> // fetch/extract/print (adapters: op/bitwarden/aws/lastpass/doppler)
765+
wheels deploy audit // tail /tmp/kamal-audit.log on each server
766+
wheels deploy details // aggregate app + proxy + accessory status
767+
wheels deploy remove --confirm // teardown all app/proxy/accessory containers
768+
wheels deploy docs [section] // in-terminal config reference
769+
```
770+
771+
### On-server parity contract (byte-compatible with Ruby Kamal)
772+
773+
- Container names: `<service>-<role>-<version>`
774+
- Labels: `service=`, `role=`, `destination=`, `version=`
775+
- Docker network: `kamal`
776+
- Lock file: `/tmp/kamal_deploy_lock_<service>`
777+
- Proxy config: `/home/<user>/.config/kamal-proxy/`
778+
- Hook env prefix: `KAMAL_*` (never `WHEELS_*` — user hooks migrate unchanged)
779+
780+
A server managed by Ruby Kamal can be taken over by `wheels deploy` without cleanup.
781+
782+
### Architecture
783+
784+
```
785+
cli/lucli/services/deploy/
786+
├── cli/*.cfc DeployMainCli + Deploy<App|Proxy|Accessory|Build|Registry|Server|Prune|Lock|Secrets>Cli
787+
├── commands/*.cfc Base + Docker/App/Proxy/Builder/Registry/Auditor/Lock/Hook/Accessory/PruneCommands
788+
├── config/*.cfc Config + Role/Env/Builder/Proxy/Registry/Ssh/Accessory/Validator/ConfigLoader
789+
├── lib/*.cfc JarLoader/Mustache/Yaml/SshClient/SshPool/FakeSshPool/Output/SecretResolver
790+
└── secrets/*.cfc BaseAdapter + OnePassword/Bitwarden/AwsSecrets/LastPass/Doppler adapters
791+
792+
cli/lucli/lib/deploy/*.jar jmustache, snakeyaml, sshj + BouncyCastle transitives (URLClassLoader-isolated)
793+
cli/lucli/templates/deploy/ Mustache templates for `wheels deploy init` output
794+
```
795+
796+
Commands-are-strings invariant: every `*Commands.cfc` method returns a shell-command string; only `*Cli.cfc` and the orchestrator execute them. That's why `--dry-run` is trivial and unit tests run without network.
797+
798+
### Critical gotchas
799+
800+
1. **Kamal-compatible schema, ONE divergence.** ERB in `deploy.yml` is NOT supported. Use the restricted Mustache context `{{env.FOO}}`, `{{destination}}`, `{{hostname}}` instead. Everything else in `config/deploy.yml` is byte-identical to Kamal 2.4.0.
801+
2. **Hook env prefix is `KAMAL_`, not `WHEELS_`.** This is deliberate — it means Ruby Kamal users' existing `.kamal/hooks/` scripts work unchanged.
802+
3. **`app live` / `app maintenance` use a marker file** (`/tmp/kamal-maintenance-<svc>`) rather than kamal-proxy native maintenance mode. Phase 2 simplification; Phase 3 follow-up will align with Kamal's proxy-native semantics.
803+
4. **`wheels deploy remove` is destructive and requires `--confirm`.** Bare `wheels deploy remove` throws without touching anything.
804+
5. **Lucee reserved scope names in subagent-authored deploy code.** `client`, `session`, `application` — use `ssh`/`sc`, `sess`, `app` instead. Bit us multiple times during the port.
805+
6. **No `--dry-run` flag in Ruby Kamal 2.8.2.** The `tools/deploy-config-diff.sh` harness compares config-layer output only. Byte-identical command-string parity is aspirational; see `tools/deploy-dry-run-diff.sh` for the plan.
806+
807+
### Testing
808+
809+
`cli/lucli/tests/specs/deploy/` extends `wheels.wheelstest.system.BaseSpec`. Run with:
810+
811+
bash tools/test-cli-local.sh
812+
813+
Fixtures at `cli/lucli/tests/_fixtures/deploy/configs/` (`minimal.yml`, `full.yml`, `with-accessories.yml`, `invalid/*.yml`). `FakeSshPool.cfc` records every command for offline assertions; no sshd needed for unit tests. `SshClientSpec` + `SshPoolSpec` exercise real SSH via the fixture at `cli/lucli/tests/_fixtures/deploy/sshd/` (brought up by `tools/deploy-sshd-up.sh`).
814+
815+
### Reference docs
816+
817+
- User guides: `docs/src/working-with-wheels/deployment/` (first-deploy, config-reference, accessories, secrets, hooks, migrating-from-kamal)
818+
- Per-verb CLI reference: `docs/src/command-line-tools/commands/deploy/`
819+
- Design spec: `docs/superpowers/specs/2026-04-20-wheels-deploy-kamal-port-design.md`
820+
- Implementation plan: `docs/superpowers/plans/2026-04-20-wheels-deploy-kamal-port.md`
821+
- Retrospective: `docs/superpowers/plans/2026-04-21-phase1-retrospective.md`
822+
741823
## Server-Sent Events (SSE) Quick Reference
742824

743825
```cfm
@@ -874,7 +956,7 @@ The project name is **Wheels** (not "CFWheels"). The rebrand happened at v3.0. A
874956

875957
Or run `wheels mcp setup` to generate `.mcp.json` + `.opencode.json` automatically.
876958

877-
Tools are auto-discovered from `cli/lucli/Module.cfc` public functions, prefixed with the module name (`wheels_generate`, `wheels_migrate`, `wheels_test`, `wheels_reload`, `wheels_seed`, `wheels_analyze`, `wheels_validate`, `wheels_routes`, `wheels_info`, `wheels_destroy`, `wheels_doctor`, `wheels_stats`, `wheels_notes`, `wheels_db`, `wheels_upgrade`, `wheels_create`). CLI-only tools (`mcp`, `d`, `new`, `console`, `start`, `stop`, `browser`) are hidden from MCP `tools/list` via `mcpHiddenTools()`.
959+
Tools are auto-discovered from `cli/lucli/Module.cfc` public functions, prefixed with the module name (`wheels_generate`, `wheels_migrate`, `wheels_test`, `wheels_reload`, `wheels_seed`, `wheels_analyze`, `wheels_validate`, `wheels_routes`, `wheels_info`, `wheels_destroy`, `wheels_doctor`, `wheels_stats`, `wheels_notes`, `wheels_db`, `wheels_upgrade`, `wheels_create`, `wheels_deploy`). CLI-only tools (`mcp`, `d`, `new`, `console`, `start`, `stop`, `browser`) are hidden from MCP `tools/list` via `mcpHiddenTools()`.
878960

879961
Workflow orchestration (multi-step planning, feature development) is not a framework concern — use your preferred Claude Code plugin (Superpowers, feature-dev, etc.). The framework ships deterministic Wheels operations via MCP; the model orchestrates.
880962

0 commit comments

Comments
 (0)