Skip to content

Instantly share code, notes, and snippets.

@shah743
Last active October 8, 2024 12:37
Show Gist options
  • Save shah743/a4d584ed292fa3bd146f02b3c654f051 to your computer and use it in GitHub Desktop.
Save shah743/a4d584ed292fa3bd146f02b3c654f051 to your computer and use it in GitHub Desktop.
Deploying Rails 7+ Apps with Kamal Gem on Digital Ocean

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.

Rails 7.1 and Docker

With Rails 7.1, Docker files come by default.

Kamal Configuration

Install Kamal Gem

bundle add kamal && bundle install

Initialize Kamal Config

Run the following command to initialize the Kamal config file:

kamal init

These two files are crucial: .env and config/deploy.yml.

.env File

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.

VPS and IP Configuration

Purchase a VPS and add its IP to the relevant field in deploy.yml. Update deploy.yml according to your project credentials.

Example config/deploy.yml

# 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

Registry Configuration

# 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

Environment Variables

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 Configuration

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

Rails Database Configuration

On the Rails side, update config/database.yml:

production:
  <<: *default
  username: myapp
  password: <%= ENV["POSTGRES_PASSWORD"] %>
  database: myapp_production
  host: <%= ENV["DB_HOST"] %>

Traefik Configuration

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

Deployment Commands

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

Troubleshooting Logs

Check logs if there are issues:

kamal traefik logs
kamal app logs 

For live logs:

kamal app logs -f

Rails Console Access

kamal app exec -i "bin/rails c"

Bash Server Access

kamal server exec --interactive "/bin/bash"

Final config/deploy.yml

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment