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

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)