Deploying a Rails app to cloud services using Docker and Kamal (a library for deploying apps containerized with Docker).
rails new APP_NAME -d postgresql -j esbuild -c bootstrap --skip-test
cd APP_NAME
git add .
git commit -m 'Init app'
rails g scaffold Post title content:text
rails db:create
rails db:migrate
# Defines the root path route ("/")
root "posts#index"
# Start Rails
bin/dev
git add .
git commit -m 'Implement a basic blog functionality'
# Add Kamal gem to Gemfile
bundle add kamal
kamal init
This command will add several new files like .env
, config/deploy.yml
and .kamal/hooks
folder with hook samples.
This can be any cloud service you prefer, such as DigitalOcean, AWS, CoogleCloud, etc.
This can be DockerHub, DigitalOcean CR, AWS ECR, GCP Artifact Registry etc.
# .env
KAMAL_REGISTRY_PASSWORD=change-this
RAILS_MASTER_KEY=use-your-master-key
POSTGRES_PASSWORD=set-posgtres-password
production:
<<: *default
database: your_app_name_production
username: your_app_name
password: <%= ENV["POSTGRES_PASSWORD"] %>
host: <%= ENV["DB_HOST"] %>
config.force_ssl = false
# Name of your application. Used to uniquely configure containers.
service: your-app-name
# Name of the container image.
image: your-container-registry-nickname/your-app-name
# Deploy to these servers.
servers:
web:
hosts:
- SERVER_IP_ADDRESS
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
# server: registry.digitalocean.com / ghcr.io / ...
username: your-container-registry-nickname
# Always use an access token rather than real password when possible.
password:
- KAMAL_REGISTRY_PASSWORD
# Inject ENV variables into containers (secrets come from .env).
# Remember to run `kamal env push` after making changes!
env:
clear:
DB_HOST: SERVER_IP_ADDRESS
RAILS_ENV: production
secret:
- RAILS_MASTER_KEY
- POSTGRES_PASSWORD
# Use a different ssh user than root
# ssh:
# user: app
# Configure builder setup.
# builder:
# args:
# RUBY_VERSION: 3.2.0
# secrets:
# - GITHUB_TOKEN
# remote:
# arch: amd64
# host: ssh://[email protected]
# Use accessory services (secrets come from .env).
accessories:
db:
image: postgres:15
host: SERVER_IP_ADDRESS
port: 5432
env:
clear:
POSTGRES_USER: 'your_app_name'
POSTGRES_DB: 'your_app_name_production'
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
# redis:
# image: redis:7.0
# host: 192.168.0.2
# port: 6379
# directories:
# - data:/data
# Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it.
# traefik:
# args:
# accesslog: true
# accesslog.format: json
# Configure a custom healthcheck (default is /up on port 3000)
# healthcheck:
# path: /healthz
# port: 4000
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
# hitting 404 on in-flight requests. Combines all files from new and old
# version inside the asset_path.
#
# If your app is using the Sprockets gem, ensure it sets `config.assets.manifest`.
# See https://github.com/basecamp/kamal/issues/626 for details
#
# asset_path: /rails/public/assets
# Configure rolling deploys by setting a wait time between batches of restarts.
# boot:
# limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
# wait: 2
# Configure the role used to determine the primary_host. This host takes
# deploy locks, runs health checks during the deploy, and follow logs, etc.
#
# Caution: there's no support for role renaming yet, so be careful to cleanup
# the previous role on the deployed hosts.
# primary_role: web
# Controls if we abort when see a role with no hosts. Disabling this may be
# useful for more complex deploy configurations.
#
# allow_empty_roles: false
The PostgreSQL image requires some environment variables to be set, namely POSTGRES_USER
, POSTGRES_DB
, and POSTGRES_PASSWORD
# config/deploy.yml
...
postgres:
...
env:
clear:
POSTGRES_USER: "project"
POSTGRES_DB: "project_production"
secret:
- POSTGRES_PASSWORD
...
The official Docker image will use these variables to create the database for you and you'll be able to connect to it with the following URL:
"postgres://POSTGRES_USER:POSTGRES_PASSWORD@DB_HOST/POSTGRES_DB"
Your DB_HOST
will either be the IP address or the internal service name on the local Docker private network.
It's important to use ENV variables POSTGRES_USER
, POSTGRES_DB
and POSTGRES_PASSWORD
, don't change their names or use others, because Postrges uses these variables when configuring the database!
git add .
git commit -m 'Kamal init'
That's it.
# For the first run
kamal setup
# When the server is configured
kamal deploy
List of other Kamal commands: https://kamal-deploy.org/docs/commands/view-all-commands/
Voila! All the servers are now serving the app on port 80 💪