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/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)
For more info visit Kamal Secrets.
Step 3: Database Configuration
PostgreSQL Setup
- 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`;
- 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.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
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)