Skip
Arish's avatar

50. Deploying Rails Applications


Deploying Rails Applications

Deploying a Rails application involves several steps: setting up a server, configuring the database, managing assets, and running the application in production mode.

Production Checklist

Before deploying, ensure:

  • Database is configured for production
  • Secret keys are set via environment variables
  • Assets are precompiled
  • Proper error handling is in place
  • Logging is configured
  • Background jobs are set up (if needed)

Environment Configuration

Database

yaml
1# config/database.yml
2production:
3  adapter: postgresql
4  encoding: unicode
5  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
6  database: <%= ENV['DATABASE_NAME'] %>
7  username: <%= ENV['DATABASE_USERNAME'] %>
8  password: <%= ENV['DATABASE_PASSWORD'] %>
9  host: <%= ENV['DATABASE_HOST'] %>

Secrets and Credentials

bash
1# Edit encrypted credentials
2rails credentials:edit
3
4# For production-specific
5rails credentials:edit --environment production
ruby
1# Access credentials in code
2Rails.application.credentials.secret_key_base
3Rails.application.credentials.dig(:aws, :access_key_id)

Environment Variables

ruby
1# config/environments/production.rb
2config.require_master_key = true
3
4# Use environment variables
5config.action_mailer.smtp_settings = {
6  address: ENV['SMTP_ADDRESS'],
7  port: ENV['SMTP_PORT'],
8  user_name: ENV['SMTP_USERNAME'],
9  password: ENV['SMTP_PASSWORD']
10}

Deploying to Render

Create render.yaml

yaml
1# render.yaml
2databases:
3  - name: myapp-db
4    databaseName: myapp_production
5    user: myapp
6
7services:
8  - type: web
9    name: myapp
10    env: ruby
11    buildCommand: "./bin/render-build.sh"
12    startCommand: "bundle exec puma -C config/puma.rb"
13    envVars:
14      - key: DATABASE_URL
15        fromDatabase:
16          name: myapp-db
17          property: connectionString
18      - key: RAILS_MASTER_KEY
19        sync: false

Build Script

bash
1#!/usr/bin/env bash
2# bin/render-build.sh
3set -o errexit
4
5bundle install
6bundle exec rails assets:precompile
7bundle exec rails assets:clean
8bundle exec rails db:migrate

Deploying to Heroku

bash
1# Install Heroku CLI
2# Create app
3heroku create myapp
4
5# Add PostgreSQL
6heroku addons:create heroku-postgresql:mini
7
8# Set master key
9heroku config:set RAILS_MASTER_KEY=$(cat config/master.key)
10
11# Deploy
12git push heroku main
13
14# Run migrations
15heroku run rails db:migrate
16
17# Open app
18heroku open

Procfile

# Procfile web: bundle exec puma -C config/puma.rb worker: bundle exec sidekiq release: bundle exec rails db:migrate

Deploying with Docker

Dockerfile

dockerfile
1# Dockerfile
2FROM ruby:3.3.0-slim
3
4# Install dependencies
5RUN apt-get update -qq && \
6    apt-get install --no-install-recommends -y \
7    build-essential libpq-dev nodejs npm && \
8    rm -rf /var/lib/apt/lists/*
9
10WORKDIR /rails
11
12# Install gems
13COPY Gemfile Gemfile.lock ./
14RUN bundle install
15
16# Copy application
17COPY . .
18
19# Precompile assets
20RUN SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile
21
22EXPOSE 3000
23CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

Docker Compose

yaml
1# docker-compose.yml
2version: '3.8'
3
4services:
5  db:
6    image: postgres:15
7    environment:
8      POSTGRES_PASSWORD: password
9    volumes:
10      - postgres_data:/var/lib/postgresql/data
11
12  web:
13    build: .
14    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails server -b 0.0.0.0"
15    volumes:
16      - .:/rails
17    ports:
18      - "3000:3000"
19    depends_on:
20      - db
21    environment:
22      DATABASE_URL: postgres://postgres:password@db/myapp_production
23      RAILS_ENV: production
24      RAILS_MASTER_KEY: ${RAILS_MASTER_KEY}
25
26volumes:
27  postgres_data:

Deploying to VPS (Ubuntu)

Server Setup

bash
1# Update system
2sudo apt update && sudo apt upgrade -y
3
4# Install dependencies
5sudo apt install -y curl git-core zlib1g-dev build-essential \
6  libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 \
7  libxml2-dev libxslt1-dev libcurl4-openssl-dev \
8  libffi-dev nodejs npm nginx postgresql postgresql-contrib
9
10# Install Ruby (using rbenv)
11git clone https://github.com/rbenv/rbenv.git ~/.rbenv
12git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
13echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
14echo 'eval "$(rbenv init -)"' >> ~/.bashrc
15source ~/.bashrc
16
17rbenv install 3.3.0
18rbenv global 3.3.0
19gem install bundler

Nginx Configuration

nginx
1# /etc/nginx/sites-available/myapp
2upstream puma {
3  server unix:///home/deploy/myapp/shared/tmp/sockets/puma.sock;
4}
5
6server {
7  listen 80;
8  server_name example.com;
9  root /home/deploy/myapp/current/public;
10  
11  location ^~ /assets/ {
12    gzip_static on;
13    expires max;
14    add_header Cache-Control public;
15  }
16  
17  try_files $uri/index.html $uri @puma;
18  
19  location @puma {
20    proxy_pass http://puma;
21    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
22    proxy_set_header Host $http_host;
23    proxy_redirect off;
24  }
25}

Systemd Service

ini
1# /etc/systemd/system/puma.service
2[Unit]
3Description=Puma HTTP Server
4After=network.target
5
6[Service]
7Type=simple
8User=deploy
9WorkingDirectory=/home/deploy/myapp/current
10Environment=RAILS_ENV=production
11ExecStart=/home/deploy/.rbenv/bin/rbenv exec bundle exec puma -C config/puma.rb
12Restart=always
13
14[Install]
15WantedBy=multi-user.target

SSL with Let's Encrypt

bash
1# Install Certbot
2sudo apt install certbot python3-certbot-nginx
3
4# Get certificate
5sudo certbot --nginx -d example.com -d www.example.com
6
7# Auto-renewal
8sudo certbot renew --dry-run

Monitoring

Error Tracking

ruby
1# Gemfile
2gem 'sentry-ruby'
3gem 'sentry-rails'
4
5# config/initializers/sentry.rb
6Sentry.init do |config|
7  config.dsn = ENV['SENTRY_DSN']
8  config.environment = Rails.env
9  config.traces_sample_rate = 0.5
10end

Performance Monitoring

ruby
1# Gemfile
2gem 'newrelic_rpm'
3gem 'skylight'

Deployment is just the beginning - monitoring and maintaining your app is equally important!