| name | kamal-deployment |
| description | Deploy containerized applications (especially Rails) to VPS using Kamal 2. Covers deploy.yml configuration, accessories (PostgreSQL, Redis, Sidekiq), SSL/TLS, secrets management, CI/CD with GitHub Actions, database backups, server hardening, debugging, and scaling. Use when setting up Kamal, configuring deployments, troubleshooting deploy issues, or managing production infrastructure with Kamal. |
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
gem install kamal
kamal init
kamal setup
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 init to generate config files
- Configure
config/deploy.yml (see configuration.md)
- Set up
.kamal/secrets with registry credentials and app secrets
- Ensure your app has a Dockerfile exposing port 80 with a
/up health check
- Set
config.assume_ssl = true and config.force_ssl = true in production.rb
- Exclude
/up from host authorization in Rails 7+:
config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
- Run
kamal setup for the first deployment
Deploying Updates
kamal deploy
kamal deploy --skip-push
kamal deploy --version=VERSION
Managing Accessories
kamal accessory boot all
kamal accessory boot postgres
kamal accessory reboot redis
kamal accessory remove postgres
kamal accessory details postgres
kamal accessory logs postgres
Maintenance & Debugging
kamal app logs
kamal app logs -f
kamal app logs -r web
kamal proxy logs
kamal app exec -i 'bin/rails console'
kamal app exec -i --reuse bash
kamal app exec --primary "bin/rails about"
kamal app containers
kamal rollback [VERSION]
kamal redeploy
kamal lock status
kamal lock acquire -m "reason"
kamal lock release
kamal server bootstrap
kamal details
kamal audit
kamal prune
kamal config
kamal docs [SECTION]
kamal remove
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
kamal app exec -p -q -d $KAMAL_DESTINATION --version $KAMAL_VERSION "rails db:migrate"
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
kamal upgrade --rolling
kamal downgrade
Common Gotchas
- Kamal 2 creates its own
kamal network - remove any custom private network from your config
- Always set
config.assume_ssl = true in production.rb when 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_timeout causes 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 = false in Devise initializer to fix ActionController::RoutingError
- Enable
forward_headers: true in 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.