The following is the configuration setup for
- Rails 8
- Kamal 2
- VPS Ubuntu 24.04 LTS
- PostgreSQL
- Redis
- SSL
This Setup is mainly for 1 VPS running Rails application along with PostgreSQL Database and Redis Database as containers.
The following is the kamal configuration
- Replace x.x.x.x with valid IP Address
- Replace domain.com with valid domain
- Replace appname with valid application name
filepath:
~/appname/config/deploy.yml
configuration:
<% require "dotenv"; Dotenv.load(".env") %>
# Name of your application. Used to uniquely configure containers.
service: appname
# Name of the container image.
image: username/appname
# Deploy to these servers.
servers:
web:
hosts:
- x.x.x.x
# job:
# hosts:
# - 192.168.0.1
# cmd: bin/jobs
# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.
#
# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
proxy:
ssl: true
host: www.domain.com
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
# server: registry.digitalocean.com / ghcr.io / ...
username: registry_username
# Always use an access token rather than real password when possible.
password:
- KAMAL_REGISTRY_PASSWORD
# Inject ENV variables into containers (secrets come from .kamal/secrets).
env:
secret:
- RAILS_MASTER_KEY
- SECRET_KEY_BASE
- POSTGRES_PASSWORD
- REDIS_PASSWORD
clear:
# Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.
# When you start using multiple servers, you should split out job processing to a dedicated machine.
SOLID_QUEUE_IN_PUMA: true
# Set number of processes dedicated to Solid Queue (default: 1)
# JOB_CONCURRENCY: 3
# Set number of cores available to the application on each server (default: 1).
# WEB_CONCURRENCY: 2
# Match this to any external database server to configure Active Record correctly
# Use ncatalogue-db for a db accessory server on same machine via local kamal docker network.
# DB_HOST: 192.168.0.2
# Log everything from Rails
# RAILS_LOG_LEVEL: debug
RAILS_ENV: production
POSTGRES_USER: appname_user
POSTGRES_HOST: appname-postgres
POSTGRES_DB: appname_production_db
REDIS_HOST: redis
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
# "bin/kamal logs -r job" will tail logs from the first server in the job section.
aliases:
console: app exec --interactive --reuse "bin/rails console"
shell: app exec --interactive --reuse "bash"
logs: app logs -f
dbc: app exec --interactive --reuse "bin/rails dbconsole"
# Use a persistent storage volume for sqlite database files and local Active Storage files.
# Recommended to change this to a mounted volume path that is backed up off server.
volumes:
- "appname_storage:/rails/storage"
# 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.
asset_path: /rails/public/assets
# Configure the image builder.
builder:
arch: amd64
# # Build image via remote server (useful for faster amd64 builds on arm64 computers)
# remote: ssh://docker@docker-builder-server
#
# # Pass arguments and secrets to the Docker build process
# args:
# RUBY_VERSION: ruby-3.4.5
# secrets:
# - GITHUB_TOKEN
# - RAILS_MASTER_KEY
# Use a different ssh user than root
# ssh:
# user: app
# Use accessory services (secrets come from .kamal/secrets).
# accessories:
# db:
# image: mysql:8.0
# host: 192.168.0.2
# # Change to 3306 to expose port to the world instead of just local network.
# port: "127.0.0.1:3306:3306"
# env:
# clear:
# MYSQL_ROOT_HOST: '%'
# secret:
# - MYSQL_ROOT_PASSWORD
# files:
# - config/mysql/production.cnf:/etc/mysql/my.cnf
# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql
# directories:
# - data:/var/lib/mysql
# redis:
# image: redis:7.0
# host: 192.168.0.2
# port: 6379
# directories:
# - data:/data
accessories:
postgres:
image: postgres:17
host: x.x.x.x
port: 5432
env:
clear:
POSTGRES_USER: appname_user
POSTGRES_DB: appname_production_db
secret:
- POSTGRES_PASSWORD #comes from .kamal/secrets file
volumes:
- /pg_data:/var/lib/postgresql/data
redis:
image: redis:7
host: x.x.x.x # vps server ip address
port: 6379
env:
clear:
secret:
- REDIS_PASSWORD #comes fromm .kamal/secrets file
volumes:
- /var/lib/redis:/data
# command: ["redis-server", "--appendonly", "yes", "--save", "60", "1"]
# command: redis-server --appendonly yes --save 60 1
cmd: "redis-server --appendonly yes --save 60 1"
proxy:
app_port: 3000
ssl: true
ssl:
cert: /etc/letsencrypt/live/domain.com/fullchain.pem
key: /etc/letsencrypt/live/domain.com/privkey.pem
host: domain.com
The following are settings for environment variables to be read by kamal 2
filepath:
~/appname/.env
configuration:
KAMAL_REGISTRY_PASSWORD=xxxxxxxxxxxxxxxxxxxxxxx //registry token or password of registry such as hub.docker.com or ghcr.io
#postgres
POSTGRES_PASSWORD=appnamePassword
#redis
REDIS_PASSWORD=redispassword
Some of the variables are set by the values set in .env file
filepath:
~/appname/.kamal/secrets
configuration:
# Grab the registry password from ENV
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
# Improve security by using a password manager. Never check config/master.key into git!
RAILS_MASTER_KEY=$(cat config/credentials/production.key)
SECRET_KEY_BASE=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
REDIS_PASSWORD=$REDIS_PASSWORD
The following is the database settings based on variable initialized in above
filepath:
~/appname/config/database.yml
configuration:
production:
primary: &primary_production
<<: *default
host: <%= ENV.fetch("POSTGRES_HOST") %>
username: <%= ENV.fetch("POSTGRES_USER") %>
password: <%= ENV.fetch("POSTGRES_PASSWORD") %>
database: <%= ENV.fetch("POSTGRES_DB") %>
port: 5432
Hope the gist is helpful. Please try the configuration and feel free to contact if you need any help.
Thank You