Deployment
Deployment & Operations
A Wheels app in production is a Java webapp running on a CFML engine. That one fact determines everything that follows — your deployment target is anything that can host a JVM process and terminate HTTP in front of it. What’s new in 4.0 is that you don’t have to assemble the glue yourself: wheels deploy ships a first-party, zero-downtime rolling deploy orchestrator that takes your app from laptop to production Linux hosts in one command.
You’ll learn:
- What
wheels deployis, and the lineage it inherits from Basecamp’s Kamal - When to use
wheels deployversus a plaindocker composeor VM-based path - How zero-downtime rollover works through
kamal-proxy - Exactly what the framework ships versus what production infrastructure you provide
What wheels deploy is
Section titled “What wheels deploy is”wheels deploy is a port of Kamal, Basecamp’s container-based deployer, into the Wheels CLI. It orchestrates from your laptop or CI runner over plain SSH — there is no agent on the target hosts. Commands like wheels deploy, wheels deploy rollback, and wheels deploy app logs fan out to your servers, drive docker remotely, and stream prefixed output back so you can see what each host is doing.
The port is byte-compatible with Ruby Kamal on the server side. Container names, labels, Docker network, lock-file paths, and the .kamal/ directory layout all match exactly — a host managed by Ruby Kamal can be taken over by wheels deploy during evaluation, and vice versa. You don’t need Ruby, a gem install, or a second CLI. The mirrored Kamal version is 2.4.0; the kamal-proxy image is pinned at v0.8.6.
There are two deliberate schema divergences. deploy.yml does not support ERB — where Ruby Kamal writes <%= ENV["APP_NAME"] %>, Wheels writes ${APP_NAME}, the same ${VAR} env-var interpolation Kamal supports natively. And Kamal top-level keys the port hasn’t implemented (boot, logging, retain_containers, …) are rejected by the validator rather than silently ignored. See Migrating from Kamal for the full story.
When to reach for wheels deploy
Section titled “When to reach for wheels deploy”wheels deploy targets the common production shape — one to a few dozen Linux servers, one or more Docker images, a handful of sidecar services (a database, a cache), and a requirement for zero-downtime rollover. If that’s your target, it is the shortest path.
Reach for something else when:
- Your deploy target is Kubernetes.
wheels deploytalks todockerremotely, not to a Kubernetes API. Use your normal k8s pipeline; the Docker Deployment page covers image hygiene for that world. - You’re deploying locally with Docker Compose only. For single-host, developer-centric Compose setups, Docker Deployment covers the Compose path end-to-end.
wheels deployadds value once you have two or more servers to coordinate. - Your target is a VM without containers. If you run CommandBox or a standalone servlet container under systemd, stay on that path — VM and Bare-metal Deployment covers it.
wheels deployis Docker-only on the server side; it does not grow a systemd mode. - Your servers run Windows. Kamal doesn’t support Windows servers and
wheels deployinherits that limitation. Windows developer workstations are best-effort.
If none of those apply, start with Your First Deploy.
Zero-downtime rollover
Section titled “Zero-downtime rollover”Every wheels deploy invocation follows the same sequence:
- Acquire a per-service lock on one host so two operators don’t trip over each other.
- Build and push the new image to your registry.
- Pull the image on every target host in parallel.
- Boot
kamal-proxyif it isn’t already running (it’s a singleton per host). - For each host in sequence: start the new container, then ask
kamal-proxyto switch traffic. The proxy drains in-flight requests from the old container and routes fresh ones to the new one. If health checks on the new container fail, the proxy refuses to cut over and the old container stays authoritative. - Release the lock.
The load-bearing hand-off is kamal-proxy deploy <service> --target <container>:<port>. That’s a Go binary — not a Ruby one — and it runs on your hosts as a long-lived container. wheels deploy produces the docker exec that invokes it; kamal-proxy itself does the actual traffic switch. See Proxy for the operational surface.
What Wheels provides vs. what you provide
Section titled “What Wheels provides vs. what you provide”The framework is narrow about production. Wheels gives you a CFML app that boots on any supported engine, configures itself from environment variables, and exposes observability hooks. wheels deploy packages, ships, and rolls out that app. Everything else is still yours.
Wheels provides:
- A CFML application that runs on Lucee 6/7 or Adobe ColdFusion 2023/2025. The 4.0 reference engine for core tests is Lucee 7 on Java 21.
wheels deploy— orchestration, dry-run, rollback, accessories, secrets adapters, hooks. See Your First Deploy.- Environment-aware configuration (
config/environment.cfm,config/environments/*.cfm) that selects behavior byWHEELS_ENV. - A migrator (
wheels dbmigrate) and seeder (wheels seed) that run inside the deployed container against your production database. - Built-in middleware for request IDs, security headers, CORS, and rate limiting — wired in
config/settings.cfm. - A reload endpoint (
?reload=true&password=...) and a health-check surfacekamal-proxypolls.
You provide:
- The Linux hosts. Bare VMs, cloud instances, it doesn’t matter — Docker is the only hard requirement.
wheels deploy server bootstrapinstalls Docker on a fresh host. - The Docker image. Wheels doesn’t ship a canonical base image; your
Dockerfilelives in your app repo. - The container registry. Docker Hub, GHCR, ECR, or self-hosted —
wheels deploylogs in, pushes, and pulls; the registry itself is yours to operate. - TLS certificates.
kamal-proxywill request Let’s Encrypt certs automatically whenproxy.ssl: true, but if you need wildcards, custom CAs, or static certs, you configure the proxy. - Secrets storage.
.kamal/secretsresolves$(op read ...),$(aws secretsmanager ...), and equivalents — the vault itself is yours. - The database, its backups, and its failover story. Accessories can host one if you want Wheels to manage it, or you can point at an existing managed database.
- Logs, metrics, and traces. See Observability & Logging.