Kamal Deployment Skill
Kamal is an open-source tool (by Basecamp/37signals) that deploys any Dockerized app to bare-metal servers or cloud VMs with zero downtime. It builds a Docker image, pushes it to a registry, pulls it on your servers, and hot-swaps traffic via a built-in proxy — no Kubernetes, no complex PaaS needed.
Version Detection — Do This First
Before generating any config or running any command, determine which version is in play:
kamal version # prints e.g. "2.10.1" or "1.9.2"
If Kamal isn't installed yet, infer from the repo:
traefik: key in config/deploy.yml + .env/.env.erb file → Kamal 1.x → read references/kamal-v1.md
proxy: key in config/deploy.yml + .kamal/secrets file → Kamal 2.x
- No existing config → default to Kamal 2.x (current; install with
gem install kamal)
Key v1 → v2 differences at a glance:
| Area |
Kamal 1.9.x |
Kamal 2.x |
| Proxy |
Traefik (traefik: block, raw labels/args) |
kamal-proxy (proxy: block, host/ssl/app_port) |
| SSL |
Manual Traefik ACME config or Cloudflare |
Native Let's Encrypt (ssl: true) |
| Secrets/env |
.env / .env.erb + kamal env push |
.kamal/secrets + pushed on every deploy |
| Apps per host |
Single-app oriented |
Multiple apps on one server |
| Networking |
Host network |
Isolated custom Docker network |
| Default app port |
3000 |
80 |
| Hooks |
pre/post-traefik-reboot |
pre/post-proxy-reboot + pre/post-app-boot |
| Key commands |
kamal traefik, kamal env push, kamal envify |
kamal proxy, kamal secrets |
→ If working with Kamal 1.x: read references/kamal-v1.md for all v1-specific syntax.
Reference Files (read these on demand)
| Task |
Read |
Writing or editing config/deploy.yml |
references/configuration.md |
| Full CLI command list + flags |
references/commands.md |
| First-time setup, deploy, redeploy, rollback, rolling deploys |
references/workflows.md |
.kamal/secrets, vault adapters, deploy hooks |
references/secrets-and-hooks.md |
proxy: block, Let's Encrypt SSL, custom certs |
references/proxy-and-ssl.md |
| Build strategies, remote builder, multiarch, asset bridging |
references/builders.md |
| Healthcheck failures, lock errors, logs, debugging |
references/troubleshooting.md |
| Everything Kamal 1.9.x + upgrading to v2 |
references/kamal-v1.md |
Copyable starter templates live in assets/:
assets/deploy.yml — annotated v2 starter config
assets/deploy.v1.yml — annotated v1 starter config
assets/secrets.example — .kamal/secrets template
assets/hooks/ — sample pre-connect, pre-deploy, post-deploy hooks
First-Time Setup (Kamal 2)
# 1. Install
gem install kamal # requires Ruby 3.0+
# 2. Scaffold
kamal init # creates config/deploy.yml, .kamal/secrets, .kamal/hooks/
# 3. Edit config/deploy.yml — see assets/deploy.yml for an annotated starter
# Minimum required: service, image, servers, registry
# 4. Fill in .kamal/secrets — see assets/secrets.example
echo "KAMAL_REGISTRY_PASSWORD=your-token" >> .kamal/secrets
# NEVER commit .kamal/secrets — add to .gitignore:
echo ".kamal/secrets*" >> .gitignore
echo "!.kamal/secrets.example" >> .gitignore
# 5. Validate merged config (shows resolved values including secrets — careful in shared terminals)
kamal config
# 6. Bootstrap servers + first deploy (installs Docker if needed, boots accessories, deploys app)
kamal setup
Server prerequisites: SSH access (key-based, root or sudo user), curl/wget, internet access. Kamal installs Docker automatically via kamal setup or kamal server bootstrap.
Core Management Cheatsheet
# Deploy (build + push + zero-downtime swap)
kamal deploy
kamal deploy --skip-push # skip build, use existing image
kamal deploy -r web # target a specific role
kamal deploy -h 192.168.0.1 # target a specific host
kamal redeploy # faster: skips proxy start, prune, registry login
# Rollback
kamal app containers # list versions (current is marked)
kamal rollback <VERSION> # e.g. kamal rollback abc123d
# Logs
kamal app logs -f # follow live logs
kamal app logs --since 30m # last 30 minutes
kamal app logs --grep ERROR # filter by pattern
# Run commands in the container
kamal app exec -i "bash" # interactive shell
kamal app exec -i --reuse "bash" # reuse existing container (faster)
kamal app exec "db-migrate-command"
# App status
kamal app version # currently running version
kamal app containers # containers on all hosts
kamal details # all containers + proxy details
# Accessories (databases, redis, etc.)
kamal accessory boot mysql
kamal accessory reboot mysql # apply config/image changes
kamal accessory logs mysql -f
kamal accessory exec mysql -i "mysql -u root -p"
# Proxy
kamal proxy logs # proxy logs
kamal proxy reboot # restart kamal-proxy (brief interruption)
kamal proxy details
# Deploy lock (release if stuck after a crash)
kamal lock status
kamal lock release
# Audit & history
kamal audit # deployment history from servers
Critical Gotchas
Never commit secrets. Add .kamal/secrets* (and allow !.kamal/secrets.example) to .gitignore. The file holds plaintext credentials.
password: is always an array of secret names — not a string.
# ✅ correct
registry:
password:
- KAMAL_REGISTRY_PASSWORD
# ❌ wrong — will fail
registry:
password: "my-token"
Bind accessory ports to localhost. Use "127.0.0.1:5432:5432" not "5432:5432" — the latter exposes your database to the internet.
SSL + forward_headers. When ssl: true, the proxy does NOT forward X-Forwarded-For / X-Forwarded-Proto by default. Add forward_headers: true or your app won't see the real client IP / will think it's on HTTP.
Rollback requires the old container to exist. Old containers are retained by default (5 most recent). Once pruned, you can't roll back to that version without rebuilding.
ECR tokens expire every 12 hours. Use the ERB trick in registry config (see references/configuration.md → Registry).
kamal setup vs kamal deploy. setup is for first time — it installs Docker, boots accessories, then deploys. deploy is for subsequent releases and assumes infra is ready.
Disable buffering for streaming/WebSockets. Add buffering: { requests: false, responses: false } under proxy: for SSE/WebSocket endpoints.
v1 users: The default app port in v1 was 3000; v2 defaults to 80. When upgrading, update your Dockerfile/proxy.app_port accordingly.
1---2name: kamal3description: Sets up, runs, and troubleshoots Kamal deployments (v1 and v2) — zero-downtime Docker deploys to VPS or bare-metal servers. Use when the user mentions Kamal, kamal-proxy, or Traefik, edits config/deploy.yml or .kamal/secrets, or asks to deploy an app to their own server, roll back, add accessories like Postgres or Redis, fix a failed deploy or healthcheck, or upgrade from Kamal 1 to 2.4---56# Kamal Deployment Skill78Kamal is an open-source tool (by Basecamp/37signals) that deploys any Dockerized app to bare-metal servers or cloud VMs with zero downtime. It builds a Docker image, pushes it to a registry, pulls it on your servers, and hot-swaps traffic via a built-in proxy — no Kubernetes, no complex PaaS needed.910## Version Detection — Do This First1112Before generating any config or running any command, determine which version is in play:1314```bash15kamal version # prints e.g. "2.10.1" or "1.9.2"16```1718If Kamal isn't installed yet, infer from the repo:19- `traefik:` key in `config/deploy.yml` + `.env`/`.env.erb` file → **Kamal 1.x** → read `references/kamal-v1.md`20- `proxy:` key in `config/deploy.yml` + `.kamal/secrets` file → **Kamal 2.x**21- No existing config → **default to Kamal 2.x** (current; install with `gem install kamal`)2223**Key v1 → v2 differences at a glance:**2425| Area | Kamal 1.9.x | Kamal 2.x |26|---|---|---|27| Proxy | Traefik (`traefik:` block, raw labels/args) | kamal-proxy (`proxy:` block, `host`/`ssl`/`app_port`) |28| SSL | Manual Traefik ACME config or Cloudflare | Native Let's Encrypt (`ssl: true`) |29| Secrets/env | `.env` / `.env.erb` + `kamal env push` | `.kamal/secrets` + pushed on every deploy |30| Apps per host | Single-app oriented | Multiple apps on one server |31| Networking | Host network | Isolated custom Docker network |32| Default app port | `3000` | `80` |33| Hooks | `pre/post-traefik-reboot` | `pre/post-proxy-reboot` + `pre/post-app-boot` |34| Key commands | `kamal traefik`, `kamal env push`, `kamal envify` | `kamal proxy`, `kamal secrets` |3536→ **If working with Kamal 1.x:** read `references/kamal-v1.md` for all v1-specific syntax.3738---3940## Reference Files (read these on demand)4142| Task | Read |43|---|---|44| Writing or editing `config/deploy.yml` | `references/configuration.md` |45| Full CLI command list + flags | `references/commands.md` |46| First-time setup, deploy, redeploy, rollback, rolling deploys | `references/workflows.md` |47| `.kamal/secrets`, vault adapters, deploy hooks | `references/secrets-and-hooks.md` |48| `proxy:` block, Let's Encrypt SSL, custom certs | `references/proxy-and-ssl.md` |49| Build strategies, remote builder, multiarch, asset bridging | `references/builders.md` |50| Healthcheck failures, lock errors, logs, debugging | `references/troubleshooting.md` |51| Everything Kamal 1.9.x + upgrading to v2 | `references/kamal-v1.md` |5253Copyable starter templates live in `assets/`:54- `assets/deploy.yml` — annotated v2 starter config55- `assets/deploy.v1.yml` — annotated v1 starter config 56- `assets/secrets.example` — `.kamal/secrets` template57- `assets/hooks/` — sample pre-connect, pre-deploy, post-deploy hooks5859---6061## First-Time Setup (Kamal 2)6263```bash64# 1. Install65gem install kamal # requires Ruby 3.0+6667# 2. Scaffold68kamal init # creates config/deploy.yml, .kamal/secrets, .kamal/hooks/6970# 3. Edit config/deploy.yml — see assets/deploy.yml for an annotated starter71# Minimum required: service, image, servers, registry7273# 4. Fill in .kamal/secrets — see assets/secrets.example74echo "KAMAL_REGISTRY_PASSWORD=your-token" >> .kamal/secrets75# NEVER commit .kamal/secrets — add to .gitignore:76echo ".kamal/secrets*" >> .gitignore77echo "!.kamal/secrets.example" >> .gitignore7879# 5. Validate merged config (shows resolved values including secrets — careful in shared terminals)80kamal config8182# 6. Bootstrap servers + first deploy (installs Docker if needed, boots accessories, deploys app)83kamal setup84```8586**Server prerequisites:** SSH access (key-based, root or sudo user), curl/wget, internet access. Kamal installs Docker automatically via `kamal setup` or `kamal server bootstrap`.8788---8990## Core Management Cheatsheet9192```bash93# Deploy (build + push + zero-downtime swap)94kamal deploy95kamal deploy --skip-push # skip build, use existing image96kamal deploy -r web # target a specific role97kamal deploy -h 192.168.0.1 # target a specific host98kamal redeploy # faster: skips proxy start, prune, registry login99100# Rollback101kamal app containers # list versions (current is marked)102kamal rollback <VERSION> # e.g. kamal rollback abc123d103104# Logs105kamal app logs -f # follow live logs106kamal app logs --since 30m # last 30 minutes107kamal app logs --grep ERROR # filter by pattern108109# Run commands in the container110kamal app exec -i "bash" # interactive shell111kamal app exec -i --reuse "bash" # reuse existing container (faster)112kamal app exec "db-migrate-command"113114# App status115kamal app version # currently running version116kamal app containers # containers on all hosts117kamal details # all containers + proxy details118119# Accessories (databases, redis, etc.)120kamal accessory boot mysql121kamal accessory reboot mysql # apply config/image changes122kamal accessory logs mysql -f123kamal accessory exec mysql -i "mysql -u root -p"124125# Proxy126kamal proxy logs # proxy logs127kamal proxy reboot # restart kamal-proxy (brief interruption)128kamal proxy details129130# Deploy lock (release if stuck after a crash)131kamal lock status132kamal lock release133134# Audit & history135kamal audit # deployment history from servers136```137138---139140## Critical Gotchas1411421. **Never commit secrets.** Add `.kamal/secrets*` (and allow `!.kamal/secrets.example`) to `.gitignore`. The file holds plaintext credentials.1431442. **`password:` is always an array of secret names — not a string.**145 ```yaml146 # ✅ correct147 registry:148 password:149 - KAMAL_REGISTRY_PASSWORD150 # ❌ wrong — will fail151 registry:152 password: "my-token"153 ```1541553. **Bind accessory ports to localhost.** Use `"127.0.0.1:5432:5432"` not `"5432:5432"` — the latter exposes your database to the internet.1561574. **SSL + forward_headers.** When `ssl: true`, the proxy does NOT forward `X-Forwarded-For` / `X-Forwarded-Proto` by default. Add `forward_headers: true` or your app won't see the real client IP / will think it's on HTTP.1581595. **Rollback requires the old container to exist.** Old containers are retained by default (5 most recent). Once pruned, you can't roll back to that version without rebuilding.1601616. **ECR tokens expire every 12 hours.** Use the ERB trick in registry config (see `references/configuration.md` → Registry).1621637. **`kamal setup` vs `kamal deploy`.** `setup` is for first time — it installs Docker, boots accessories, then deploys. `deploy` is for subsequent releases and assumes infra is ready.1641658. **Disable buffering for streaming/WebSockets.** Add `buffering: { requests: false, responses: false }` under `proxy:` for SSE/WebSocket endpoints.1661679. **v1 users:** The default app port in v1 was `3000`; v2 defaults to `80`. When upgrading, update your `Dockerfile`/`proxy.app_port` accordingly.