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 installRun the following command to initialize the Kamal config file:
kamal initThese two files are crucial: .env and config/deploy.yml.
Add the following variables to your .env file:
RAILS_MASTER_KEY=XXXXXXXXXX
KAMAL_REGISTRY_PASSWORD=XXXXXXXXXKAMAL_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_PASSWORDenv:
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_URLaccessories:
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:/dataOn 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: webRun the following commands to set up and deploy your app:
mkdir -p /letsencrypt && touch /letsencrypt/acme.json && chmod 600 /letsencrypt/acme.jsonkamal setup
kamal env pushAfter 210 seconds, your app will be live. To deploy changes, use:
kamal deploykamal traefik bootCheck logs if there are issues:
kamal traefik logs
kamal app logs For live logs:
kamal app logs -fkamal 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