Kamal Deploy
Servers need Docker, SSH access, and ports 22/80/443 open. Provision with Ansible or cloud-init.
Configuration: config/deploy.yml
Minimal Setup
service: myapp
image: myapp
servers:
web:
- 203.0.113.10
proxy:
ssl: true
host: myapp.com
registry:
username: username
password:
- KAMAL_REGISTRY_PASSWORD
env:
clear:
RAILS_ENV: production
RAILS_LOG_TO_STDOUT: "true"
secret:
- RAILS_MASTER_KEY
Multi-Role Setup (Web + Job Worker)
service: myapp
image: myapp
servers:
web:
- 203.0.113.10
job:
hosts:
- 203.0.113.10
cmd: bin/jobs start
proxy:
ssl: true
host: myapp.com
registry:
username: username
password:
- KAMAL_REGISTRY_PASSWORD
env:
clear:
RAILS_ENV: production
SOLID_QUEUE_IN_PUMA: false
secret:
- RAILS_MASTER_KEY
Job worker notes:
cmd: bin/jobs startruns Solid Queue in a separate container- Set
SOLID_QUEUE_IN_PUMA: falseto disable in-process queue - Job role has no proxy, only web role serves HTTP traffic
- Fiber workers (
fibers:inconfig/queue.yml) make the separate container the safer default: in-Puma mode forcesisolation_level = :fiberon the web process too
With Local Registry
Eliminates Docker Hub dependency, rate limits, and external costs:
registry:
server: localhost:5555
username: ignored
password:
- KAMAL_REGISTRY_PASSWORD
Deploy the registry as an accessory:
accessories:
registry:
image: registry:2
host: 203.0.113.10
port: "5555:5000"
volumes:
- registry_data:/var/lib/registry
With Accessories
accessories:
db:
image: postgres:16
host: 203.0.113.10
port: 5432
env:
clear:
POSTGRES_DB: myapp_production
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
options:
shm-size: 256m
redis:
image: redis:7-alpine
host: 203.0.113.10
port: 6379
directories:
- data:/data
cmd: redis-server --appendonly yes
Docker Volumes for Persistence
For SQLite + ActiveStorage apps, mount a named volume:
servers:
web:
hosts:
- 203.0.113.10
volumes:
- myapp_storage:/rails/storage
labels:
docker-volume-backup.stop-during-backup: "true"
job:
hosts:
- 203.0.113.10
cmd: bin/jobs start
volumes:
- myapp_storage:/rails/storage
Both web and job containers share the same volume for database access.
Proxy Configuration (kamal-proxy)
Kamal 2 uses kamal-proxy (not Traefik). It handles SSL termination, routing, and zero-downtime deploys.
Basic SSL
proxy:
ssl: true
host: myapp.com
Automatic Let's Encrypt certificate provisioning, no manual cert management.
Custom Port
proxy:
ssl: true
host: myapp.com
app_port: 3000
Multiple Hosts
proxy:
ssl: true
hosts:
- myapp.com
- www.myapp.com
Health Check
proxy:
ssl: true
host: myapp.com
healthcheck:
path: /up
interval: 3
timeout: 3
Response Timeout
proxy:
ssl: true
host: myapp.com
response_timeout: 30
Detailed Reference
Load detailed-reference.md for extended examples, templates, and advanced patterns.