DEV Community

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

Posted on • Edited on • Originally published at sulmanweb.com

6 1 1 1 1

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

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

Billboard image

Try REST API Generation for Snowflake

DevOps for Private APIs. Automate the building, securing, and documenting of internal/private REST APIs with built-in enterprise security on bare-metal, VMs, or containers.

  • Auto-generated live APIs mapped from Snowflake database schema
  • Interactive Swagger API documentation
  • Scripting engine to customize your API
  • Built-in role-based access control

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay