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:
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-passwordUpdate
.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/dataUpdate
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 setupWatch the logs to ensure everything starts correctly:
bin/kamal logsFor 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 dbcContainer Inspection:
bin/kamal shellPostgreSQL 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!
About the author
Senior Software Engineer with 12 years of expertise in Ruby on Rails and Vue.js, specializing in health, e-commerce, staffing, and transport. Experienced in software development and version analysis.
Comments
Join the discussionWilson Mayers Thanks for the appreciation.
I haven't came across the issue you are mentioning but keep in mind my postgres user is postgres and I am only getting password of db in this tutorial and username is hardcoded.
Also please make sure your database.yml file has:
<% if ENV["DB_HOST"] %>
host: <%= ENV["DB_HOST"] %>
username: postgres
<% end %>
for production db.
Hoping this resolves your issue. If issue still persists, contact me and may be we can come to some solution together.
Hello! Congrats on this great text. I was trying to follow your process, but I'm having a problem with accessing app to postgres. every time, in the end of deployment process, I get a message saying the authentication. I'm sure about the password is being passed, since I'm having the error about username/password not matching. Did you pass anything like this?
Rails Deployment
This comprehensive series will equip readers with practical knowledge and skills in deploying Rails applications using Kamal, Dokku, and other relevant tools.






