kamal-deployment
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+:config.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.