Skip to content

Instantly share code, notes, and snippets.

@aashish
Created December 14, 2025 18:47
Show Gist options
  • Select an option

  • Save aashish/733631dea5a02937a4e90cc92782c0e4 to your computer and use it in GitHub Desktop.

Select an option

Save aashish/733631dea5a02937a4e90cc92782c0e4 to your computer and use it in GitHub Desktop.
Rails 8 + Kamal 2 + VPS + SSL + PostgreSQL + Redis Setup

Rails 8 + Kamal 2 + VPS + SSL + PostgreSQL + Redis Setup

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.

Kamal 2 configuration

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

.env configuration

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

Kamal 2 secrets configuration

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

Database configuration

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment