Help us improve
Share bugs, ideas, or general feedback.
From basics
Guides Kamal deployments of Dockerized apps to servers with zero-downtime, proxy/SSL setup, and accessories like DB/Redis. For Rails/web apps to Hetzner, DigitalOcean, AWS.
npx claudepluginhub nityeshaga/claude-code-essentials --plugin basicsHow this skill is triggered — by the user, by Claude, or both
Slash command
/basics:kamal-deployThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Expert guidance for deploying applications with Kamal - DHH's zero-downtime deployment tool from 37signals.
Deploys containerized web apps to Linux servers using Kamal 2. Assists with setup, deploy.yml config, zero-downtime deploys, rollbacks, accessories (databases, Redis), troubleshooting, CI/CD, multi-env setups.
Guides Rails app deployment with Kamal 2: configure deploy.yml for web/job roles, kamal-proxy SSL, local registry, accessories like Postgres/Redis, Docker volumes/secrets.
Provisions and hardens a Hetzner Cloud VPS with Docker, Nginx/Caddy, SSL, and monitoring. Recommends instance types based on workload and traffic.
Share bugs, ideas, or general feedback.
Expert guidance for deploying applications with Kamal - DHH's zero-downtime deployment tool from 37signals.
BEFORE answering ANY Kamal question, you MUST use the WebFetch tool to get current documentation. The docs below may be outdated - always fetch fresh docs first.
Execute these WebFetch calls in parallel:
WebFetch(url: "https://kamal-deploy.org/docs/installation/", prompt: "Extract complete installation and setup guide")
WebFetch(url: "https://kamal-deploy.org/docs/configuration/overview/", prompt: "Extract all configuration options and deploy.yml structure")
WebFetch(url: "https://kamal-deploy.org/docs/commands/view-all-commands/", prompt: "Extract all Kamal commands and usage")
WebFetch(url: "https://kamal-deploy.org/docs/configuration/proxy/", prompt: "Extract proxy, SSL, and health check configuration")
Fetch these additional docs based on user's question:
https://kamal-deploy.org/docs/configuration/servers/https://kamal-deploy.org/docs/configuration/accessories/https://kamal-deploy.org/docs/configuration/environment-variables/https://kamal-deploy.org/docs/configuration/builders/https://kamal-deploy.org/docs/hooks/overview/https://kamal-deploy.org/docs/upgrading/overview/Only after fetching fresh docs, use the reference material below as supplementary context.
Kamal deploys containerized apps to any server via SSH + Docker. Created by 37signals (DHH's company) to deploy Basecamp, HEY, and other apps.
Core architecture:
Mental model: Hetzner/DigitalOcean = the computer, Kamal = deploys your app to it
Check these first to avoid common friction:
Kamal version - Run kamal version. If on 1.x, upgrade with gem install kamal. Config syntax changed significantly (1.x uses traefik, 2.x uses proxy).
Local Docker situation - Ask the user if they have Docker working locally. If not (or if Docker Desktop is problematic on macOS), configure a remote builder:
builder:
arch: amd64
remote: ssh://root@SERVER_IP
This builds on the target server and avoids local Docker entirely.
37signals open-source repos - If deploying Campfire, HEY, or other 37signals apps, immediately delete .env.erb - it uses their internal 1Password setup and will fail with op: command not found.
Registry access - Confirm the user has a container registry (Docker Hub, GHCR) and knows their credentials before writing config.
# Install (or upgrade)
gem install kamal
# Initialize in project
kamal init
# First deploy (installs Docker, proxy, deploys app)
kamal setup
# Subsequent deploys
kamal deploy
| Command | Purpose |
|---|---|
kamal setup | First deploy - installs Docker, proxy, deploys |
kamal deploy | Deploy new version |
kamal rollback | Revert to previous version |
kamal app logs | View application logs |
kamal app exec -i bash | SSH into running container |
kamal accessory boot <name> | Start accessory (db, redis) |
kamal proxy reboot | Restart kamal-proxy |
kamal remove | Remove everything from servers |
service: my-app
image: username/my-app
servers:
- 123.45.67.89
registry:
username: username
password:
- KAMAL_REGISTRY_PASSWORD
proxy:
ssl: true
host: myapp.com
env:
secret:
- RAILS_MASTER_KEY
- DATABASE_URL
Secrets live in .kamal/secrets:
# .kamal/secrets
KAMAL_REGISTRY_PASSWORD=ghp_xxxxxxxxxxxx
RAILS_MASTER_KEY=abc123def456
DATABASE_URL=postgres://user:pass@db:5432/app
Reference in deploy.yml:
env:
clear:
RAILS_ENV: production
secret:
- RAILS_MASTER_KEY
- DATABASE_URL
servers:
web:
hosts:
- 123.45.67.89
- 123.45.67.90
workers:
hosts:
- 123.45.67.91
cmd: bin/jobs
proxy: false # Workers don't need proxy
accessories:
db:
image: postgres:16
host: 123.45.67.89
port: 5432
env:
clear:
POSTGRES_DB: app_production
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
redis:
image: redis:7
host: 123.45.67.89
port: 6379
directories:
- data:/data
Automatic (Let's Encrypt):
proxy:
ssl: true
host: myapp.com # Must point to server IP
Custom certificate:
proxy:
ssl:
certificate_pem:
- SSL_CERTIFICATE
private_key_pem:
- SSL_PRIVATE_KEY
proxy:
healthcheck:
interval: 3
path: /up
timeout: 3
App must return 200 on /up (Rails default) or configured path.
Create config/deploy.staging.yml:
servers:
- staging.myapp.com
proxy:
host: staging.myapp.com
Deploy: kamal deploy -d staging
Secrets: .kamal/secrets.staging
Place in .kamal/hooks/ (no file extension):
Available hooks:
pre-connect, pre-build, pre-deploy, post-deploypre-app-boot, post-app-bootpre-proxy-reboot, post-proxy-rebootExample .kamal/hooks/post-deploy:
#!/bin/bash
curl -X POST "https://api.honeybadger.io/v1/deploys" \
-d "deploy[revision]=$KAMAL_VERSION"
Kamal needs a Dockerfile. For Rails:
FROM ruby:3.3-slim
WORKDIR /app
# Install dependencies
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev
COPY Gemfile* ./
RUN bundle install
COPY . .
RUN bundle exec rails assets:precompile
EXPOSE 80
CMD ["bin/rails", "server", "-b", "0.0.0.0", "-p", "80"]
Note: Kamal 2.x defaults to port 80 (not 3000).
"Container not healthy"
/up endpoint returns 200deploy_timeout if app boots slowlykamal app logs"Permission denied"
ssh-add ~/.ssh/id_rsaRegistry auth failed
KAMAL_REGISTRY_PASSWORD in .kamal/secretswrite:packages"Address already in use"
kamal proxy reboot or check docker ps on server| Kamal | Kubernetes | Heroku | |
|---|---|---|---|
| Complexity | Low | High | None |
| Cost | VPS only | VPS + overhead | $$$ |
| Control | Full | Full | Limited |
| Zero-downtime | Yes | Yes | Yes |
| SSL | Auto | Manual | Auto |
| Learning curve | Hours | Weeks | Minutes |
docker build . && docker run -p 3000:80 <image>.kamal/secrets in .gitignoreasset_path: /app/public/assetsFor detailed configuration options, see: