Wheels 4.0 ships wheels deploy — a Kamal port, no Ruby required #2438
bpamiri
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Wheels 4.0 introduces a new built-in command:
wheels deploy. It is a port of Basecamp's Kamal — the container-based deployer — into the Wheels CLI. This post is for anyone currently running ad-hoc deploy scripts (or running Ruby Kamal alongside Wheels) who wants to understand what's shipping and what the contract is.Why a port, not a plugin
Wheels ships as a LuCLI binary (CFML + Java). Asking users to
gem install kamaladds a Ruby runtime dependency and a second CLI to learn. Kamal's proxy component (kamal-proxy) is already a standalone Go binary — what's Ruby-specific is only the developer-side orchestrator that opens SSH connections, uploads config, and runsdockercommands. So we ported the orchestrator, leftkamal-proxyuntouched, and kept the on-server state byte-compatible.What's byte-compatible
Everything in this list matches the Kamal 2.4.0 contract exactly:
<service>-<role>-<version>service=,role=,destination=,version=kamalbasecamp/kamal-proxy:v0.8.6/home/<user>/.config/kamal-proxy//tmp/kamal_deploy_lock_<service>/tmp/kamal-audit.log.kamal/hooks/KAMAL_*(notWHEELS_*— so existing Kamal hook scripts work unchanged).kamal/secrets,.kamal/secrets.<destination>A server managed by Ruby Kamal can be taken over by
wheels deployduring evaluation, and vice versa. You can run both tools against the same host while you decide.The one divergence
config/deploy.ymldoes not support ERB. ERB is Ruby template code; rendering it from a CFML CLI would require embedding a Ruby runtime, which defeats the purpose of the port. What Wheels keeps unchanged is Kamal's other built-in interpolation —${UPPER_SNAKE}env-var tokens — so most ERB-using configs convert mechanically:${VAR}references resolve through the same lookup chain Kamal uses (.kamal/secrets→ process environment variables → empty string). For ERB blocks that did real logic — conditionals, ternaries, computed values — the resolution moves into.kamal/secrets(or a.kamal/secrets.<destination>overlay) and the result is referenced back through${VAR}.Net effect: there is no new syntax to learn. The single change is a removal — ERB out, everything else identical.
Subcommand surface
Mirrors Kamal's top-level verb structure:
init,setup,rollback,config,version,details,audit,remove,docsapp boot | start | stop | details | containers | images | logs | live | maintenance | removeproxy boot | reboot | start | stop | restart | details | logs | removeaccessory boot | reboot | start | stop | restart | details | logs | remove— for sidecars (database, cache, search)build deliver | push | pull | create | remove | details | devregistry setup | login | logout | removeserver exec | bootstrapprune all | images | containerslock acquire | release | status(normal deploys auto-lock)secrets fetch | extract | printwith adapters for 1Password, Bitwarden, AWS Secrets Manager, LastPass, DopplerEvery verb supports
--dry-run— prints the exact shell commands that would run remotely without opening an SSH connection. The commands-layer test suite runs offline, no Docker, no sshd.What
wheels deployis notdockerremotely over SSH. For k8s, use your normal pipeline.wheels deployadds value at two or more servers.?reload=truestory is separate..kamal/hooks/work unchanged; the Ruby-plugin extension API is Ruby-specific.Docs
Question for the thread
If you're currently deploying a Wheels app to production, what does your setup look like today — and what would stop you from trying
wheels deploy? The design space is still open for 4.0.x polish.Beta Was this translation helpful? Give feedback.
All reactions