Description: A comprehensive guide to deploying Rails 7.1+ applications using the Kamal gem and Digital Ocean. This guide covers installation, configuration, and deployment steps with detailed instructions and example configuration files.
With Rails 7.1, Docker files come by default.
bundle add kamal && bundle install
Run the following command to initialize the Kamal config file:
kamal init
These two files are crucial: .env
and config/deploy.yml
.
Add the following variables to your .env
file:
RAILS_MASTER_KEY=XXXXXXXXXX
KAMAL_REGISTRY_PASSWORD=XXXXXXXXX
KAMAL_REGISTRY_PASSWORD
is your Docker Registry password. If you can't find it, go to hub.docker.com, log in, and create a new token under Account Settings > Security > New Access Token.
Purchase a VPS and add its IP to the relevant field in deploy.yml
. Update deploy.yml
according to your project credentials.
# Name of your application. Used to uniquely configure containers.
service: myapp
# Docker image
image: username/myapp
# Main server configuration
servers:
web:
hosts:
- 192.168.0.1
labels:
traefik.http.routers.messi-web.rule: Host(`intellecta.app`)
traefik.http.routers.messi-web.tls: true
traefik.http.routers.messi-web.entrypoints: websecure
traefik.http.routers.messi-web.tls.certresolver: letsencrypt
job:
hosts:
- 192.168.0.1
cmd: bundle exec sidekiq -q high_priority -q default -q mailers
# Credentials for your image host.
registry:
# Specify the registry server if you're not using Docker Hub
# server: registry.digitalocean.com / ghcr.io / ...
username: shahzaib
password:
- KAMAL_REGISTRY_PASSWORD
env:
clear:
HOSTNAME: 192.168.0.1
DB_HOST: 192.168.0.1
RAILS_SERVE_STATIC_FILES: true
RAILS_LOG_TO_STDOUT: true
secret:
- KAMAL_REGISTRY_PASSWORD
- RAILS_MASTER_KEY
- POSTGRES_PASSWORD
- REDIS_URL
accessories:
db:
image: postgres:16
host: 192.168.0.1
env:
clear:
POSTGRES_DB: "myapp_production"
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
redis:
image: redis:7.0
host: 192.168.0.1
directories:
- data:/data
On the Rails side, update config/database.yml
:
production:
<<: *default
username: myapp
password: <%= ENV["POSTGRES_PASSWORD"] %>
database: myapp_production
host: <%= ENV["DB_HOST"] %>
Traefik handles TLS termination, HTTPS redirect, and zero downtime deployments.
traefik:
options:
publish:
- "443:443"
volume:
- "/letsencrypt/acme.json:/letsencrypt/acme.json"
args:
entryPoints.web.address: ":80"
entryPoints.websecure.address: ":443"
entryPoints.web.http.redirections.entryPoint.to: websecure
entryPoints.web.http.redirections.entryPoint.scheme: https
entryPoints.web.http.redirections.entrypoint.permanent: true
certificatesResolvers.letsencrypt.acme.email: "[email protected]"
certificatesResolvers.letsencrypt.acme.storage: "/letsencrypt/acme.json"
certificatesResolvers.letsencrypt.acme.httpchallenge: true
certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web
Run the following commands to set up and deploy your app:
mkdir -p /letsencrypt && touch /letsencrypt/acme.json && chmod 600 /letsencrypt/acme.json
kamal setup
kamal env push
After 210 seconds, your app will be live. To deploy changes, use:
kamal deploy
kamal traefik boot
Check logs if there are issues:
kamal traefik logs
kamal app logs
For live logs:
kamal app logs -f
kamal app exec -i "bin/rails c"
kamal server exec --interactive "/bin/bash"
Here is the overall final look of config/deploy.yml
:
# Name of your application. Used to uniquely configure containers.
service: myapp
# Name of the container image.
image: username/myapp
# Deploy to these servers.
servers:
web:
hosts:
- 192.168.0.1
labels:
traefik.http.routers.messi-web.rule: Host(`intellecta.app`)
traefik.http.routers.messi-web.tls: true
traefik.http.routers.messi-web.entrypoints: websecure
traefik.http.routers.messi-web.tls.certresolver: letsencrypt
job:
hosts:
- 192.168.0.1
cmd: bundle exec sidekiq -q high_priority -q default -q mailers
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
# server: registry.digitalocean.com / ghcr.io / ...
username: shahzaib
password:
- KAMAL_REGISTRY_PASSWORD
# Inject ENV variables into containers (secrets come from .env).
# Remember to run `kamal env push` after making changes!
env:
clear:
HOSTNAME: 192.168.0.1
DB_HOST: 192.168.0.1
RAILS_SERVE_STATIC_FILES: true
RAILS_LOG_TO_STDOUT: true
secret:
- KAMAL_REGISTRY_PASSWORD
- RAILS_MASTER_KEY
- POSTGRES_PASSWORD
- REDIS_URL
# Use accessory services (secrets come from .env).
accessories:
db:
image: postgres:16
host: 192.168.0.1
env:
clear:
POSTGRES_DB: "myapp_production"
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
redis:
image: redis:7.0
host: 192.168.0.1
directories:
- data:/data
traefik:
options:
publish:
- "443:443"
volume:
- "/letsencrypt/acme.json:/letsencrypt/acme.json"
args:
entryPoints.web.address: ":80"
entryPoints.websecure.address: ":443"
entryPoints.web.http.redirections.entryPoint.to: websecure
entryPoints.web.http.redirections.entryPoint.scheme: https
entryPoints.web.http.redirections.entrypoint.permanent: true
certificatesResolvers.letsencrypt.acme.email: "[email protected]"
certificatesResolvers.letsencrypt.acme.storage: "/letsencrypt/acme.json"
certificatesResolvers.letsencrypt.acme.httpchallenge: true
certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web