Deploying Rails 8 with Kamal and Docker: A Complete CI/CD Guide
In this article, I’ll guide you through setting up a CI/CD pipeline for a Ruby on Rails application using Kamal and Docker. This setup will…
Deploying Rails 8 with Kamal and Docker: A Complete CI/CD Guide

In this article, I’ll guide you through setting up a CI/CD pipeline for a Ruby on Rails application using Kamal and Docker. This setup will enable you to deploy your application with zero-downtime deployments, rollback capabilities, and containerized environments
Prerequisites
- A Ruby on Rails application
- GitHub repository
- Docker installed locally
- A server with Docker installed (for production)
- GitHub Container Registry (GHCR) account
- SSH access to your server
1. Docker Setup
Let’s start by creating a production-ready docker-compose file and Dockerfile:
docker-compose.yml
version: "3.9"
services:
web:
image: ghcr.io/username/app_name:${TAG:-latest}
restart: always
ports:
- "3000:3000"
environment:
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=redis://redis:6379
- RAILS_MASTER_KEY=${MASTER_KEY}
depends_on:
- postgres
- redis
sidekiq:
image: ghcr.io/username/app_name:${TAG:-latest}
restart: always
command: bundle exec sidekiq
environment:
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=redis://redis:6379
- RAILS_MASTER_KEY=${MASTER_KEY}
depends_on:
- redis
- postgres
redis:
image: redis:7
restart: always
postgres:
image: postgres:15
restart: always
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Dockerfile
FROM ruby:3.4.7-slim
# Install system dependencies
RUN apt-get update -qq && \
apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
postgresql-client \
nodejs \
npm \
git \
curl \
libyaml-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Node.js and Yarn
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs && \
npm install --global yarn
WORKDIR /rails
# Install gems
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment 'true' && \
bundle config set --local without 'development test' && \
bundle install --jobs 4 --retry 3
# Copy application code
COPY . .
# Precompile bootsnap code for faster boot times
# Precompile assets
RUN SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile
EXPOSE 3000
ENV RAILS_ENV="production"
ENV RAILS_LOG_TO_STDOUT="true"
ENV RAILS_SERVE_STATIC_FILES="true"
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "3000"]
2. Kamal Setup
Install Kamal if you haven’t already:
gem install kamal
kamal init
This will create a config/deploy.yml file. Add your project configurations in it.
service: your_app_name # Unique identifier for your application in Docker
image: your-username/your-repository-name # Path to your container image in the registry
servers: # Defines where your application will be deployed
web: # Server role (can have multiple roles like web, worker, etc.)
hosts:
- your-server-ip # Replace with your actual server IP
# Container registry authentication
registry:
server: ghcr.io # Container registry URL (GitHub Container Registry)
username: your-github-username # Your GitHub username
password: KAMAL_REGISTRY_PASSWORD # GitHub Personal Access Token with package:write
# Reverse proxy configuration
proxy:
ssl: true # Enable HTTPS with automatic SSL certificate management
host: "www.example.com" # Your production domain
app_port: 3000 # Port your Rails application listens on
builder:
arch: amd64 # Target architecture for the Docker image
env:
clear: # Non-sensitive environment variables
RAILS_ENV: production
secret: # Sensitive environment variables (stored in .kamal/.secrets)
- RAILS_MASTER_KEY # Rails encryption key
- DATABASE_URL # Database connection string
- REDIS_URL # Redis connection URL
ssh:
user: server-user # SSH user for deployment
# Managed services (accessories) that your application depends on
accessories:
postgres:
image: postgres:15
host: your-server-ip
port: 5432
env:
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
redis:
image: redis:7
host: your-server-ip
port: 6379
directories:
- data:/data
3. GitHub Actions Workflow
Create a GitHub Actions workflow file at
.github/workflows/deploy.yml:
name: Deploy to Production
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
packages: write
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GCHR_PAT }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/app_name
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
runs-on: ubuntu-latest
needs: build-and-push
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
- name: Setup SSH
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SERVER_SSH_KEY }}
- name: Deploy via Kamal
env:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_USER: ${{ secrets.SERVER_USER }}
KAMAL_REGISTRY_PASSWORD: ${{ secrets.GCHR_PAT }}
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
REDIS_URL: ${{ secrets.REDIS_URL }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
run: |
gem install kamal
kamal deploy
4. Required Secrets
Set up the following secrets in your GitHub repository settings:
GCHR_PAT: GitHub Personal Access Token withwrite:packagesandread:packagesscopesSERVER_SSH_KEY: Private SSH key for server accessRAILS_MASTER_KEY: Your Rails master keyDATABASE_URL: Your database connection stringREDIS_URL: Your Redis connection URL (if using)SERVER_HOST: IP address or domain name of your production server.SERVER_USER: SSH username for server access (e.g., ‘deploy’ or ‘root’)POSTGRES_PASSWORD: Database password for PostgreSQL (used in database.yml)
Setting Up Environment Secrets
Create the secrets file:
mkdir -p .kamal
touch .kamal/.secrets
echo ".kamal/.secrets" >> .gitignore # Ensure it's not committed
Add your secrets
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
RAILS_MASTER_KEY=$MASTER_KEY
DATABASE_URL=$DATABASE_URL
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
SERVER_USER=$SERVER_USER
Push secrets to your servers:
kamal env push
How It Works
- The
.secretsfile stores all sensitive environment variables - Running
kamal env pushencrypts these values using SSH and stores them securely on your servers - The values are decrypted only during deployment and never stored in plaintext
5. First Deployment
- Commit and push your changes to the main branch.
- The GitHub Actions workflow will automatically trigger.
- Monitor the deployment in the GitHub Actions tab.
Server Configuration:
Before deploying, ensure:
- Stop Nginx (if running) to avoid port conflicts:
sudo systemctl stop nginx
- Verify Ports: Kamal’s proxy will use ports 80 (HTTP) and 443 (HTTPS).
Check for conflicts:
sudo lsof -i :80
sudo lsof -i :443
- Get the correct Postgres host for
DATABASE_URL
SSH to the server and find your app container:
docker ps
docker exec -it CONTAINER_ID bash
Inside the app container, get the Docker gateway IP (this is usually the host-side IP for that Docker network):
gw=$(awk '$2=="00000000"{print $3}' /proc/net/route)
printf "%d.%d.%d.%d\n" 0x${gw:6:2} 0x${gw:4:2} 0x${gw:2:2} 0x${gw:0:2}
Use the printed IP as the DB host in your production configuration (e.g., DATABASE_URL or database.yml):
Example:
DATABASE_URL=postgres://USER:PASSWORD@<GATEWAY_IP>:5432/DBNAME
Run the following command to open the pg_hba.conf file:
sudo nano /etc/postgresql/16/main/pg_hba.conf
Add the rule at the end of file:
host all all IP_address/16 md5
Run the following command to open the postgresql.conf file:
sudo nano /etc/postgresql/16/main/postgresql.conf
Set listen_addresses to accept external connections:
- Listen on all interfaces:
listen_addresses = ''
Reload/restart Postgres:
sudo systemctl restart postgresql
Assets Issues:
Add the following code to your files:
- config/environments/production.rb:
config.public_file_server.enabled = true
- config/initializers/assets.rb
Rails.application.config.assets.paths << Rails.root.join('app', 'assets', 'fonts', 'images')
Rails.application.config.assets.precompile += %w( application.js application.css *.svg *.png *.jpg *.jpeg *.gif )
Conclusion
You now have a robust CI/CD pipeline for your Rails application using Kamal and Docker. This setup provides:
- Automated builds and deployments on every push to main
- Zero-downtime deployments
- Easy rollback capabilities
- Containerized environments for consistency
- Secure secret management
- Built-in monitoring and maintenance tools
By following this guide, you’ve set up a professional-grade deployment pipeline that will scale with your application’s needs. Happy deploying!
메타데이터
- post_id
- 94ab9738d8ae
- slug
- deploying-rails-8-with-kamal-and-docker-a-complete-ci-cd-guide-94ab9738d8ae
- url
- https://medium.com/@mahad.bin.naeem/deploying-rails-8-with-kamal-and-docker-a-complete-ci-cd-guide-94ab9738d8ae
- canonical_url
- https://medium.com/@mahad.bin.naeem/deploying-rails-8-with-kamal-and-docker-a-complete-ci-cd-guide-94ab9738d8ae
- author_url
- https://medium.com/@mahad.bin.naeem
- status
- ok
- fetched_at
- 2026-06-24 04:09:36