Back to Blog
Guide8 min read2026-05-01

Ruby on Rails Production Deployment: Complete Guide

A complete guide to deploying Ruby on Rails applications to production using Docker, Puma, Sidekiq, and a managed cloud PaaS with PostgreSQL.

Ruby on Rails Production Deployment: Complete Guide

Ruby on Rails powers many of the world's most successful web applications. Its convention-over-configuration philosophy makes development fast, but production deployments require careful Docker configuration, the Puma web server, asset compilation, and database migration management.

This guide covers a complete Rails production deployment on [PandaStack](https://pandastack.io).

Rails Production Architecture

A production Rails deployment typically consists of:

  • Puma — the web server handling HTTP requests
  • Sidekiq — background job processing (requires Redis)
  • PostgreSQL — primary database
  • Redis — Action Cable, caching, and Sidekiq queue

On PandaStack, you deploy the Rails web server as a container project and can run Sidekiq as a separate cronjob or container service.

Production Dockerfile

Rails 7+ ships with a built-in Dockerfile generator. Here is a production-ready version:

FROM ruby:3.3-alpine AS base

RUN apk add --no-cache     build-base     postgresql-dev     nodejs     yarn     tzdata     libffi-dev

WORKDIR /rails

ENV RAILS_ENV=production
ENV BUNDLE_WITHOUT=development:test

FROM base AS deps
COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs 4 --retry 3

FROM base AS assets
COPY --from=deps /usr/local/bundle /usr/local/bundle
COPY . .
RUN bundle exec rails assets:precompile

FROM ruby:3.3-alpine AS runner

RUN apk add --no-cache postgresql-client tzdata libffi

WORKDIR /rails

ENV RAILS_ENV=production
ENV RAILS_SERVE_STATIC_FILES=true
ENV RAILS_LOG_TO_STDOUT=true

COPY --from=deps /usr/local/bundle /usr/local/bundle
COPY --from=assets /rails /rails

RUN addgroup --system rails && adduser --system --ingroup rails rails
RUN chown -R rails:rails db log storage tmp
USER rails

EXPOSE 3000

CMD ["./bin/rails", "server", "-b", "0.0.0.0"]

Key environment variables:

  • RAILS_SERVE_STATIC_FILES=true — Puma serves precompiled assets without Nginx
  • RAILS_LOG_TO_STDOUT=true — logs to stdout for the platform to capture

Database Migrations via Startup Script

#!/bin/sh
# bin/docker-entrypoint
set -e

if [ "${*}" = "./bin/rails server -b 0.0.0.0" ]; then
  ./bin/rails db:migrate 2>/dev/null || true
fi

exec "${@}"
COPY bin/docker-entrypoint /rails/bin/docker-entrypoint
RUN chmod +x /rails/bin/docker-entrypoint
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

Migrations run only when starting the web server, not on every container command.

Configuring pandastack.json

{
  "type": "container",
  "healthCheckPath": "/up"
}

Rails 7.1+ ships with a built-in /up health check endpoint that verifies database connectivity. If you're on an older version, add a simple health route:

# config/routes.rb
get "/health", to: proc { [200, {}, [{ status: :ok }.to_json]] }

Environment Variables

Set all secrets and configuration from [dashboard.pandastack.io](https://dashboard.pandastack.io):

RAILS_ENV=production
SECRET_KEY_BASE=your-128-char-hex-secret
DATABASE_URL=postgresql://user:pass@host:5432/myapp_prod
REDIS_URL=redis://redis.internal:6379

# Action Mailer
SMTP_HOST=smtp.mailgun.org
SMTP_USERNAME=your-user
SMTP_PASSWORD=your-password
SMTP_PORT=587

# Application config
APP_HOST=yourdomain.com

Generate SECRET_KEY_BASE locally:

bundle exec rails secret

Database Configuration

Configure your database to read from DATABASE_URL:

# config/database.yml
production:
  adapter: postgresql
  encoding: unicode
  url: <%= ENV['DATABASE_URL'] %>
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

Provision a managed PostgreSQL instance from the Databases section at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Puma Configuration

# config/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3).to_i
threads threads_count, threads_count

port ENV.fetch("PORT", 3000)
environment ENV.fetch("RAILS_ENV", "production")

workers ENV.fetch("WEB_CONCURRENCY", 2).to_i

preload_app!

on_worker_boot do
  ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end

before_fork do
  ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
end

Running Sidekiq as a Cronjob

For background jobs, run Sidekiq as a separate process. On PandaStack, you can configure a cronjob that keeps Sidekiq running. Alternatively, deploy it as a separate container project without a web port, using the same Docker image with a different command override:

bundle exec sidekiq -C config/sidekiq.yml
# config/sidekiq.yml
:concurrency: 5
:queues:
  - [critical, 3]
  - [default, 2]
  - [low, 1]

GitHub Integration and Deployment

Connect your GitHub repository from the PandaStack dashboard. Every push to your production branch automatically builds the Docker image (including asset precompilation) and deploys. For CLI deployments:

npm install -g @pandastack/cli
panda deploy

Production Checklist

  • RAILS_ENV=production and RAILS_LOG_TO_STDOUT=true are set
  • SECRET_KEY_BASE is set and secure (128+ hex characters)
  • Assets precompiled in the Docker build stage
  • Database migrations run on startup via entrypoint script
  • Puma configured with workers and threads
  • /up or /health returns HTTP 200
  • Redis provisioned for Sidekiq and caching
  • pandastack.json specifies correct healthCheckPath

Active Storage and Action Mailer in Production

Rails applications that handle file uploads (Active Storage) or send emails (Action Mailer) need additional configuration for production.

For Action Mailer, configure SMTP credentials as environment variables:

# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  address: ENV['SMTP_HOST'],
  port: ENV['SMTP_PORT']&.to_i || 587,
  user_name: ENV['SMTP_USERNAME'],
  password: ENV['SMTP_PASSWORD'],
  authentication: :plain,
  enable_starttls_auto: true
}
config.action_mailer.default_url_options = { host: ENV['APP_HOST'], protocol: 'https' }

Set SMTP_HOST, SMTP_USERNAME, SMTP_PASSWORD, and APP_HOST in the PandaStack dashboard. Always test email delivery in staging before going live — a misconfigured mailer silently drops emails in production.

Visit [docs.pandastack.io](https://docs.pandastack.io) for the full deployment reference.

Ready to deploy?

Start free on PandaStack — no credit card required.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also