← Back to list

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…

Mahad Bin Naeem · 2026-01-06 13:05 · 36 claps · 4.6 min read
#kamal #rails #docker #ci-cd-pipeline #deploy
Open on Medium ↗
Wiki topics: 🌐 · Web Development ☁️ · DevOps & Cloud

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 with write:packages and read:packages scopes
  • SERVER_SSH_KEY: Private SSH key for server access
  • RAILS_MASTER_KEY: Your Rails master key
  • DATABASE_URL: Your database connection string
  • REDIS_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 .secrets file stores all sensitive environment variables
  • Running kamal env push encrypts 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

  1. Commit and push your changes to the main branch.
  2. The GitHub Actions workflow will automatically trigger.
  3. Monitor the deployment in the GitHub Actions tab.

Server Configuration:

Before deploying, ensure:

  1. Stop Nginx (if running) to avoid port conflicts:
sudo systemctl stop nginx
  1. Verify Ports: Kamal’s proxy will use ports 80 (HTTP) and 443 (HTTPS).

Check for conflicts:

sudo lsof -i :80
sudo lsof -i :443
  1. 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