Kamal 2 Deployment
Kamal is a deployment tool by 37signals that deploys containerized applications to any Linux server using Docker, SSH, and Kamal Proxy. It offers zero-downtime deploys, rolling restarts, automatic SSL/TLS certificates, and auxiliary services management.
Quick Start
# Install gem install kamal # Initialize in your project kamal init # Creates: config/deploy.yml, .kamal/secrets, .kamal/hooks/ # First deploy (bootstraps servers + deploys) kamal setup # Subsequent deploys kamal deploy
Key Concepts
- •Service: Your application name, used to uniquely configure containers
- •Server: A virtual or physical host running your application image
- •Role: A server group running the same command (e.g.,
web,job) - •Accessory: A long-running auxiliary service (database, Redis) with independent lifecycle
- •Kamal Proxy: Reverse proxy for zero-downtime deploys and SSL termination on each server
- •Kamal 2 provides its own private network called
kamal- do NOT add a custom private network in your config
Configuration Reference
For complete deploy.yml configuration, see configuration.md.
Workflows
Initial Setup
- •Run
kamal initto generate config files - •Configure
config/deploy.yml(see configuration.md) - •Set up
.kamal/secretswith registry credentials and app secrets - •Ensure your app has a Dockerfile exposing port 80 with a
/uphealth check - •Set
config.assume_ssl = trueandconfig.force_ssl = trueinproduction.rb - •Exclude
/upfrom host authorization in Rails 7+:rubyconfig.host_authorization = { exclude: ->(request) { request.path == "/up" } } - •Run
kamal setupfor the first deployment
Deploying Updates
kamal deploy # Full deploy (build + push + deploy) kamal deploy --skip-push # Deploy existing image from registry kamal deploy --version=VERSION # Deploy specific version
Managing Accessories
kamal accessory boot all # Boot all accessories kamal accessory boot postgres # Boot specific accessory kamal accessory reboot redis # Reboot an accessory kamal accessory remove postgres # Remove an accessory kamal accessory details postgres kamal accessory logs postgres
Maintenance & Debugging
# Logs kamal app logs # Application logs kamal app logs -f # Follow logs kamal app logs -r web # Logs for specific role kamal proxy logs # Proxy logs # Console access kamal app exec -i 'bin/rails console' kamal app exec -i --reuse bash # Shell into running container kamal app exec --primary "bin/rails about" # Rollback kamal app containers # List available versions kamal rollback [VERSION] # Rollback to version # Redeploy (skip bootstrap, proxy setup, pruning, registry login) kamal redeploy # Locks kamal lock status # Check deploy lock kamal lock acquire -m "reason" # Prevent deploys kamal lock release # Allow deploys again # Server management kamal server bootstrap # Bootstrap servers with Docker kamal details # Show details about all containers kamal audit # Show audit log from servers kamal prune # Prune old images and containers kamal config # Show combined config (includes secrets!) kamal docs [SECTION] # Show Kamal configuration docs # Cleanup kamal remove # Remove everything from servers
Global Flags
| Flag | Description |
|---|---|
-v, --verbose | Detailed logging |
-q, --quiet | Minimal logging |
--version=VERSION | Run against specific app version |
-p, --primary | Primary host only |
-h, --hosts=HOSTS | Comma-separated hosts (supports wildcards) |
-r, --roles=ROLES | Comma-separated roles (supports wildcards) |
-d, --destination | Deployment destination |
-H, --skip-hooks | Skip hook execution |
Database Operations
# Run migrations via entrypoint (recommended) # bin/docker-entrypoint handles db:prepare automatically # Or via pre-deploy hook kamal app exec -p -q -d $KAMAL_DESTINATION --version $KAMAL_VERSION "rails db:migrate" # Database backup (with S3 backup accessory) kamal accessory exec s3_backup "sh backup.sh" kamal accessory exec s3_backup "sh restore.sh"
Architecture Patterns
For complete examples of single-server and multi-server setups, see examples.md.
Single Server (Rails + PostgreSQL + Redis + Sidekiq)
All services on one VPS with Let's Encrypt SSL. Best for small-to-medium apps.
Multi-Server (Scaled)
Separate servers for web, job, database, and cache behind a load balancer on a private network. Best for high-traffic apps.
Hooks
Custom scripts in .kamal/hooks/ that run at deployment points. If a hook returns non-zero, the command aborts.
Available: docker-setup, pre-connect, pre-build, pre-deploy, post-deploy, pre-app-boot, post-app-boot, pre-proxy-reboot, post-proxy-reboot.
For details, see configuration.md.
Upgrading from Kamal 1
kamal upgrade # In-place upgrade from Kamal 1 to 2 kamal upgrade --rolling # Zero-downtime upgrade kamal downgrade # Reverse if needed
Common Gotchas
- •Kamal 2 creates its own
kamalnetwork - remove any custom private network from your config - •Always set
config.assume_ssl = trueinproduction.rbwhen using SSL - •Do NOT use your domain name as the VM hostname - it overrides
/etc/resolv.conf - •Docker port exposure bypasses UFW - closing ports in UFW is not enough, Docker rules are higher in iptables
- •Short
deploy_timeoutcauses failures on underpowered servers - increase it if deploys fail - •Asset bridging must be explicitly configured with
asset_path- it's not automatic - •Accessories must be removed before moving to a new destination
- •Kamal 2 doesn't support ERB in
config/deploy.yml(unlike Kamal 1) - •Set
config.reload_routes = falsein Devise initializer to fix ActionController::RoutingError - •Enable
forward_headers: truein proxy config when behind Cloudflare to preserve real client IPs
CI/CD
For GitHub Actions deployment workflows, see cicd.md.
Backups
For database backup strategies with S3, see backups.md.
Server Hardening
For production server security setup, see server-hardening.md.
Logging & Monitoring
For Vector log aggregation and structured logging, see logging.md.