DEV Community

Cover image for Deploying Rails 8 Applications: A Complete Guide with Docker, Kamal, and Cloudflare
Sulman Baig
Sulman Baig

Posted on • Originally published at sulmanweb.com

Deploying Rails 8 Applications: A Complete Guide with Docker, Kamal, and Cloudflare

Introduction

When I first started deploying Rails applications, the process felt overwhelming. Today, I'm excited to share a complete guide that transforms this complexity into a straightforward process. Let's dive into deploying a Rails 8 app using Docker, with PostgreSQL containerization, all orchestrated by Kamal on a Hetzner server.

Prerequisites

Before we begin our deployment journey, you'll need:

  • A Rails 8 application ready for production
  • Docker installed locally
  • A Hetzner account
  • Domain name and Cloudflare account
  • 1Password account (for secrets management)
  • Basic SSH knowledge

Step 1: Server and DNS Configuration

Hetzner Server Setup

Let's start by creating our production environment:

  1. Create a Hetzner server with these specifications:
   - Ubuntu 24.04
   - ARM64 (Ampere) CPU
   - CAX11 size (or choose based on your needs)
   - IPv4 only configuration
   - Your SSH keys added
Enter fullscreen mode Exit fullscreen mode

Cloudflare Configuration

Set up your domain with proper DNS and security settings:

  1. DNS Configuration:
   - Add A record: @ → your-server-ip (Proxy enabled)
   - Add CNAME: www → @ (Proxy enabled)
Enter fullscreen mode Exit fullscreen mode
  1. SSL/TLS Settings:
   - Edge Certificates: Enable "Always use HTTPS"
   - Overview: Set SSL/TLS mode to "Full"
Enter fullscreen mode Exit fullscreen mode
  1. Page Rules for www to root redirect:
   URL: https://www.yourdomain.com/*
   Setting: Forwarding URL
   Status Code: 301 - Permanent Redirect
   Destination URL: https://yourdomain.com/$1
Enter fullscreen mode Exit fullscreen mode

Step 2: Secrets Management

I've learned that proper secrets management is crucial. Let's set it up with 1Password:

  1. Create a secure note in 1Password with these credentials:
   KAMAL_REGISTRY_PASSWORD: your-docker-registry-password
   RAILS_MASTER_KEY: your-rails-master-key
   POSTGRES_PASSWORD: your-postgres-password
Enter fullscreen mode Exit fullscreen mode
  1. Update .kamal/secrets to handle 1Password integration:
   SECRETS=$(kamal secrets fetch --adapter 1password --account YOUR_1PASSWORD_ACCOUNT_ID --from YOUR_VAULT_NAME/YOU_SECURE_NOTE KAMAL_REGISTRY_PASSWORD RAILS_PRODUCTION_KEY POSTGRES_PASSWORD)

   KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD $SECRETS)
   RAILS_MASTER_KEY=$(kamal secrets extract RAILS_PRODUCTION_KEY $SECRETS)
   POSTGRES_PASSWORD=$(kamal secrets extract POSTGRES_PASSWORD $SECRETS)
Enter fullscreen mode Exit fullscreen mode

For more info visit Kamal Secrets.

Step 3: Database Configuration

PostgreSQL Setup

  1. Create config/init.sql for database initialization:
   CREATE DATABASE IF NOT EXISTS `your_app_production`;
   CREATE DATABASE IF NOT EXISTS `your_app_production_cache`;
   CREATE DATABASE IF NOT EXISTS `your_app_production_queue`;
   CREATE DATABASE IF NOT EXISTS `your_app_production_cable`;
Enter fullscreen mode Exit fullscreen mode
  1. Configure PostgreSQL accessory in config/deploy.yml:
   accessories:
     postgres:
       image: postgres:16-alpine
       host: your-server-ip
       port: 5432
       options:
         restart: always
       env:
         clear:
           POSTGRES_USER: postgres
           POSTGRES_DB: your_app_production
         secret:
           - POSTGRES_PASSWORD
       files:
         - config/init.sql:/docker-entrypoint-initdb.d/init.sql
       volumes:
         - /var/lib/postgresql/your_app_production:/var/lib/postgresql/data
Enter fullscreen mode Exit fullscreen mode
  1. Update config/database.yml for production:

    production:
        primary: &primary_production
            <<: *default host: <%= ENV["DB_HOST"] %>
            database: your_app_production
            username: postgres
            password: <%= ENV["POSTGRES_PASSWORD"] %>
        cache:
            <<: *primary_production
            database: your_app_production_cache
            migrations_paths: db/cache_migrate
        queue:
            <<: *primary_production 
            database: your_app_production_queue
            migrations_paths: db/queue_migrate
        cable: 
            <<: *primary_production
            database: your_app_production_cable
            migrations_paths: db/cable_migrate 
    

Step 4: Production Environment Configuration

Update config/environments/production.rb with these essential settings:

Rails.application.configure do
  # Asset handling
  config.require_master_key = false
  config.assets.css_compressor = nil
  config.assets.compile = false
  config.public_file_server.enabled = true
  config.asset_host = "https://yourdomain.com"
  config.assets.enabled = true
  config.assets.version = "1.0"

  # SSL and security
  config.ssl_options = { 
    redirect: { 
      exclude: ->(request) { request.path == "/up" } 
    } 
  }

  # Health checks
  config.silence_healthcheck_path = "/up"

  # Domain configuration
  config.hosts = [
    "yourdomain.com",
    /.*\.yourdomain\.com/
  ]

  config.host_authorization = { 
    exclude: ->(request) { request.path == "/up" } 
  }

  # URL options
  config.action_mailer.default_url_options = { 
    host: "yourdomain.com", 
    protocol: "https" 
  }

  routes.default_url_options = {
    host: "yourdomain.com",
    protocol: "https"
  }
end
Enter fullscreen mode Exit fullscreen mode

Step 5: Kamal Deployment Setup

Kamal, Rails 8's built-in deployment tool, makes containerized deployment straightforward:

  1. Update rest of config/deploy.yml:
service: your_app_name

registry:
  username: your_dockerhub_username
  password:
    - KAMAL_REGISTRY_PASSWORD

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"

image: your_dockerhub_username/your_app_name

builder:
    arch: arm64

proxy:
  ssl: true
  host: yourdomain.com

servers:
  web:
    hosts:
      - your_server_ip

volumes:
  - your_app_storage:/rails/storage:rw
  - /tmp/storage:/rails/tmp/storage:rw
  - your_app_data:/data

env:
  secret:
    - RAILS_MASTER_KEY
    - POSTGRES_PASSWORD
  clear:
    HOST: yourdomain.com
    RAILS_ENV: production
    DB_HOST: your_app-postgres
    SOLID_QUEUE_IN_PUMA: true
    RAILS_SERVE_STATIC_FILES: true
    RAILS_LOG_TO_STDOUT: true
Enter fullscreen mode Exit fullscreen mode

Step 6: Deployment

Now for the exciting part - deploying our application:

  1. Initial setup:
   bin/kamal setup
Enter fullscreen mode Exit fullscreen mode
  1. Watch the logs to ensure everything starts correctly:
   bin/kamal logs
Enter fullscreen mode Exit fullscreen mode
  1. For subsequent deployments:
   bin/kamal deploy
Enter fullscreen mode Exit fullscreen mode

Deployment Verification Checklist

After deployment, I always verify these key points:

  • [ ] Health check endpoint (/up) responds
  • [ ] Database migrations completed successfully
  • [ ] Assets are serving correctly
  • [ ] SSL certificate is valid
  • [ ] www to root domain redirect works
  • [ ] PostgreSQL container is running and accessible

Troubleshooting Tips

From my experience, here are some common issues and solutions:

  1. Database Connection Issues:
   bin/kamal dbc
Enter fullscreen mode Exit fullscreen mode
  1. Container Inspection:
   bin/kamal shell
Enter fullscreen mode Exit fullscreen mode
  1. PostgreSQL Logs:
   bin/kamal accessory logs postgres
Enter fullscreen mode Exit fullscreen mode

For more info go to kamal-deploy.

Conclusion

Deploying a Rails 8 application might seem daunting at first, but with this structured approach, it becomes a manageable and repeatable process. I've learned that proper configuration of each component - from DNS to secrets management to database setup - is crucial for a robust production deployment.

Remember to always test your deployment in a staging environment first, and keep your secrets secure using proper management tools like 1Password.

Have you deployed a Rails application using this approach? I'd love to hear about your experience in the comments below! 🚀


Happy Coding!


Originally published at sulmanweb.com

Top comments (0)