How to Dockerize a Rails Application with PostgreSQL and Redis

Prerequisites

  • Rails 7+ application
  • Docker and docker-compose installed
  • Basic familiarity with Rails and Docker concepts

Project Structure

After dockerizing, your project structure should include:

myapp/
├── Dockerfile
├── docker-compose.yml
├── .dockerignore
├── entrypoint.sh
└── (existing Rails files)

Step 1: Create the Dockerfile

Create Dockerfile in your project root:

FROM ruby:3.3-slim

# Install system dependencies
RUN apt-get update -qq && apt-get install -y \
  build-essential \
  libpq-dev \
  curl \
  && rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Install gems
COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs 4 --retry 3

# Copy application code
COPY . .

# Add entrypoint script
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

# Expose Rails default port
EXPOSE 3000

# Start Rails server
CMD ["rails", "server", "-b", "0.0.0.0"]

Key decisions:

  • ruby:3.3-slim — smaller image than the full Debian variant
  • libpq-dev — required for the pg gem to compile PostgreSQL adapter
  • Entrypoint script — handles database setup and migration before server starts

Step 2: Create the entrypoint script

Create entrypoint.sh:

#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails
rm -f /app/tmp/pids/server.pid

# Wait for PostgreSQL to be ready
until pg_isready -h db -U postgres; do
  echo "Waiting for PostgreSQL..."
  sleep 2
done

# Run database migrations
bundle exec rails db:migrate 2>/dev/null || bundle exec rails db:setup

# Execute the CMD
exec "$@"

This ensures the database exists and migrations run before the server starts, preventing ActiveRecord::NoDatabaseError on first boot.

Step 3: Create docker-compose.yml

version: '3.8'

services:
  web:
    build: .
    command: rails server -b 0.0.0.0
    volumes:
      - .:/app
      - bundle_data:/usr/local/bundle
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://postgres:password@db:5432/myapp_development
      REDIS_URL: redis://redis:6379/0
      RAILS_ENV: development
    depends_on:
      - db
      - redis

  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    ports:
      - "6379:6379"

volumes:
  pgdata:
  redis_data:
  bundle_data:

Important details:

  • bundle_data volume caches gems so bundle install doesn’t run every container start
  • depends_on ensures PostgreSQL and Redis start before the web service
  • Environment variables use Docker service names (db, redis) as hostnames

Step 4: Create .dockerignore

Prevent sending unnecessary files to the Docker build context:

.git
.gitignore
.tmp
log/*
tmp/*
storage/*
node_modules/
vendor/bundle
.env
Dockerfile
docker-compose.yml
README.md

Step 5: Configure database.yml for Docker

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  url: <%= ENV['DATABASE_URL'] %>

test:
  <<: *default
  url: <%= ENV['DATABASE_URL'] %>

production:
  <<: *default
  url: <%= ENV['DATABASE_URL'] %>

Using DATABASE_URL environment variable keeps all database config in one place and works consistently across environments.

Step 6: Build and run

# Build images and start all services
docker-compose up --build

# Or run in background
docker-compose up -d --build

# View logs
docker-compose logs -f web

# Run Rails commands
docker-compose exec web rails console
docker-compose exec web rails db:migrate

Production Considerations

For production, modify the Dockerfile:

# Use multi-stage build for smaller images
FROM ruby:3.3-slim AS builder
# ... install gems without development/test groups
RUN bundle install --without development test --jobs 4 --retry 3

FROM ruby:3.3-slim
# Copy only built artifacts
COPY --from=builder /usr/local/bundle /usr/local/bundle

# Precompile assets
RUN RAILS_ENV=production bundle exec rails assets:precompile

# Use Puma in production
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

And set RAILS_ENV=production and RAILS_MASTER_KEY via environment variables (never in the image).

Verification

docker-compose up --build

# In another terminal
curl http://localhost:3000
# Should return Rails welcome page or your app

# Verify PostgreSQL connection
docker-compose exec web rails runner "puts ActiveRecord::Base.connection.current_database"
# myapp_development

# Verify Redis connection
docker-compose exec web rails runner "puts Redis.new(url: ENV['REDIS_URL']).ping"
# PONG

FAQ

Q: Bundle install is slow every time I build. Use a named volume like bundle_data in docker-compose, or use BuildKit cache mounts.

Q: Changes to code aren’t reflecting. Ensure the volume mount - .:/app is in docker-compose.yml. The container must have the local directory mounted.

Q: How do I run tests inside Docker?

docker-compose exec web rails test

References


Advertisement