Migrate

From kamal

Swap the gem, keep the config — existing kamal deployments upgrade in place.

The short version#

dash began as a fork of basecamp/kamal and made a clean break in 2026 — it ships the proxy features upstream wouldn't merge and versions independently (the 3.x line). Migration is deliberately boring:

gem uninstall kamal
gem install dash        # or: swap `gem "kamal"` for `gem "dash"` in your Gemfile
dash deploy

The executable is dash instead of kamal — update CI pipelines and scripts that call the CLI by name. Everything else is designed to keep working.

What keeps working#

ArtifactStatus
config/deploy.ymlKeys unchanged — dash's config is a superset of kamal 2.x; every dash-only key is optional. ERB that calls into the gem needs Kamal:: renamed to Dash:: (see below).
.kamal/secretsStill read — the default is now .dash/secrets, but dash falls back to .kamal/ when that is the only directory present. Run dash migrate to move it.
.kamal/ on the serversRenamed to .dash/ in 3.4 — dash moves it for you, in place, the first time it takes a lock on the host. Locks, audit log, proxy boot files and app env all come along; nothing is rebooted.
kamal-proxy containerRenamed to dash-proxy in 4.0, along with the kamal network and the kamal-proxy-config volume. dash migrates each host on the next deploy: it copies the config volume so certificates are not re-issued, joins everything on the kamal network to dash, then replaces the container. Replacing it costs a short outage on that host — the old container has to release ports 80/443 before the new one can bind them.
KAMAL_* env vars & secretsStill set — dash writes DASH_* and KAMAL_* side by side, so existing hooks and apps keep working. The KAMAL_* names go away in dash 5.0.
Hooks (.kamal/hooks/)Still read — same hook names, same environment, with .dash/hooks/ as the new default location.

There is nothing for you to do on the servers — a host deployed with kamal is a host dash can deploy to, and the one server-side rename (.kamal/.dash/) happens by itself on the next locked command. In your own repo, .kamal/ keeps working as-is; dash migrate moves it to .dash/ when you are ready.

The proxy switches image, not identity#

dash uses its own proxy build, dash-proxy (ghcr.io/zoolutions/dash-proxy) — a superset of kamal-proxy that adds load balancing, SAN batching, wildcard certs, caching, and traffic shaping. From 4.0 the container is named dash-proxy; dash renames an existing kamal-proxy container for you on the next deploy.

dash reads the running proxy's version from its image tag and compares it with the minimum version this gem requires. On your first dash deploy, the drift is detected and the proxy is rebooted automatically — one host at a time — onto the dash-proxy image (details). Set proxy: reboot_on_deploy: false to opt out; dash then warns and leaves the proxy alone until you run dash proxy reboot yourself.

A proxy reboot restarts the proxy container. Without run/port_holder enabled there is a brief window while the new container binds the ports — schedule the first deploy accordingly.

Behavior changes to review#

  • Load balancing auto-activates for multi-host primary roles. When your primary (web) role has more than one host, dash fronts the fleet with a load balancer on the first web host. Kamal instead expects you to bring your own. Opt out with proxy: loadbalancer: false if you already have external load balancing.
  • dash doctor gates readiness. New diagnostic command — run it before the first deploy to check servers, registry, proxy, ports, DNS, and certificates.
  • Version lineage. dash releases its own 3.x line; kamal version pins in scripts or Gemfiles don't translate. dash upgrade still exists for legacy Kamal 1.x → 2.0 server layouts.
  • The Ruby namespace is Dash::. The gem's constants renamed from Kamal:: to Dash:: — the servers are untouched, but Ruby that names them is not. The common case is ERB in deploy.yml: <%= Kamal::Utils.docker_arch %> becomes <%= Dash::Utils.docker_arch %>. Same for anything that requires the gem directly (require "kamal"require "dash"). There is no compatibility alias — a stale reference raises NameError at config load, before dash touches a server.

Step by step#

# 1. Swap the gem
bundle remove kamal && bundle add dash && bundle binstubs dash

# 2. Sanity-check the config parses and the servers are ready
dash config
dash doctor

# 3. Deploy — reboots the proxy onto dash-proxy, then ships the app
dash deploy

# 4. Optional, any time: move .kamal/ to .dash/ in your own repo
dash migrate

Upstream kamal's docs at kamal-deploy.org still cover the shared basics; the Configuration reference here is generated from the dash gem itself and includes every dash-only key.