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:
- 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
Cloudflare Configuration
Set up your domain with proper DNS and security settings:
- DNS Configuration:
 
   - Add A record: @ → your-server-ip (Proxy enabled)
   - Add CNAME: www → @ (Proxy enabled)
- SSL/TLS Settings:
 
   - Edge Certificates: Enable "Always use HTTPS"
   - Overview: Set SSL/TLS mode to "Full"
- 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
Step 2: Secrets Management
I've learned that proper secrets management is crucial. Let's set it up with 1Password:
- 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
- Update 
.kamal/secretsto 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)
For more info visit Kamal Secrets.
Step 3: Database Configuration
PostgreSQL Setup
- Create 
config/init.sqlfor 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`;
- 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
- 
Update
config/database.ymlfor 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
Step 5: Kamal Deployment Setup
Kamal, Rails 8's built-in deployment tool, makes containerized deployment straightforward:
- 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
Step 6: Deployment
Now for the exciting part - deploying our application:
- Initial setup:
 
   bin/kamal setup
- Watch the logs to ensure everything starts correctly:
 
   bin/kamal logs
- For subsequent deployments:
 
   bin/kamal deploy
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:
- Database Connection Issues:
 
   bin/kamal dbc
- Container Inspection:
 
   bin/kamal shell
- PostgreSQL Logs:
 
   bin/kamal accessory logs postgres
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)