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 variantlibpq-dev— required for thepggem 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_datavolume caches gems sobundle installdoesn’t run every container startdepends_onensures 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