Techdots
Blog
Deploying Ruby on Rails with Docker: Best Practices for Development and Production
Streamline Ruby on Rails app deployment with Docker. Learn key setup steps, best practices, and tips for efficient, consistent environments across stages.

Deploying a Ruby on Rails app with Docker is a great way to streamline development and production workflows. By Dockerizing your Rails app, you can ensure consistency across environments, simplify deployments, and reduce potential errors. 

But how do you avoid common pitfalls and get the most out of Docker for your Rails application?

Ruby on Rails developers benefit immensely from Docker’s capabilities to handle environment setup, dependency management, and deployment. This guide explores the key steps for Rails Docker deployment, highlighting the best practices to follow for an efficient setup. No matter if you’re new to Docker or looking to refine your process, these tips will help you optimize performance and keep your app running smoothly in both development and production environments.

Why Docker is Essential for Rails Deployments

Deploying Ruby on Rails applications comes with challenges, such as managing Ruby, Node.js versions, and database dependencies (e.g., PostgreSQL). Docker simplifies this process by containerizing the application and bundling all its dependencies within a single portable unit.

A Docker container encapsulates everything needed to run the app, ensuring consistent behavior across local, staging, and production environments.

Key Docker benefits for Rails deployment

Rails Docker deployment offers following benefits:

1. Maintain consistency: Rails containerization provide an identical environment across different stages (development, testing, production).

2. Streamline environment setup: Every developer on your team can work within the same environment without configuring local machines individually.

3. Simplify scaling: Docker enables easy scaling of services like Redis, Sidekiq, and background jobs.

4. Enhance portability: Deploying across platforms (e.g., AWS, Heroku, on-premise servers) is seamless with Docker.

Before Docker:   After Docker:
 

Setting Up Docker for Rails: Dockerfile and Docker Compose

Now that we know what Dockerization is and why Rails containerization is necessary, let’s understand how you can set up Docker for Rails:

The first step in Dockerizing a Rails application is setting up a Dockerfile and Docker Compose to manage dependencies and services like databases and background workers.

Step 1: Creating a Dockerfile for a Rails Application

A Dockerfile defines the environment in which your Rails application will run. Below is a basic Dockerfile for a Rails app:

 

# Use the official Ruby image as the base

FROM ruby:2.6.6 AS base

 

# Install system dependencies

RUN apt-get update -qq && \

  apt-get install -y build-essential libpq-dev curl && \

  curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \

  apt-get install -y nodejs && \

  npm install -g yarn && \

  apt-get clean

 

# Set up the working directory

WORKDIR /app

 

# Copy the Gemfile and Gemfile.lock

COPY Gemfile Gemfile.lock ./

 

# Install gems

RUN gem install bundler -v 2.2.31

RUN bundle check || bundle install

 

# Copy the application code

COPY . ./

 

# Set up default environment variables

ENV RAILS_ENV development

Curious to know what has happened above? Well, here is the simple explanation of Rails Docker deployment:

● The official Ruby image is used as a base.

● Next, we installed system dependencies like libpq-dev (PostgreSQL libraries) and Node.js.

● Gems are installed using Bundler.

● The application code is copied into the Docker image.

● Environment variables are set to ensure Rails runs in the correct mode.

Step 2: Using Docker Compose for Multi-Service Setup

Rails applications often rely on multiple services, such as a database and background processing with Redis. Docker Compose simplifies the orchestration of these services by allowing them to be defined in a single docker-compose.yml file.

Here’s an example of a multi-service Docker Compose setup for a Rails app:

version: '3.8'

 

services:

  app:

    build:

      context: .

      target: base

    ports:

      - "3000:3000"

    volumes:

      - .:/app

    depends_on:

      - db

      - redis

    environment:

      RAILS_ENV: development

      DATABASE_HOST: db

      DATABASE_USERNAME: postgres

      DATABASE_PASSWORD: password

      REDIS_HOST: redis

 

  db:

    image: postgres

    environment:

      POSTGRES_USER: postgres

      POSTGRES_PASSWORD: password

 

  redis:

    image: redis

 

App service: Defines the Rails app, mounts the local codebase, and exposes port 3000.

Database service: Runs a PostgreSQL container with a specified username and password.

Redis service: Handles Redis for background jobs or caching.

By using Docker Compose, you can spin up the entire application stack with a single command: docker-compose up.

Optimizing Docker for Rails: Managing Dependencies and Environment Variables

Handling Gems Efficiently

To reduce build times, especially during development, Docker utilizes caching layers. A best practice is to copy the Gemfile early in the Dockerfile to minimize unnecessary rebuilds when code changes.

# Copy Gemfile first to utilize Docker layer caching

COPY Gemfile Gemfile.lock ./

RUN bundle install

This ensures that gems are only re-installed if the Gemfile changes, saving time during subsequent builds.

Learn advanced caching techniques for Ruby on Rails

Handling Environment Variables Securely

Environment variables are crucial for storing sensitive information like API keys or database credentials. Docker Compose allows you to define environment variables, but for production, it’s best to use Docker secrets or encrypted environment files.

Example for defining environment variables in docker-compose.yml:

services:

  app:

    environment:

      RAILS_ENV: development

      DATABASE_USERNAME: postgres

      DATABASE_PASSWORD: password

 

For production, tools like dotenv or Docker's built-in secret management system should be used to securely manage sensitive data.

Using Docker for Local Development

Docker Compose Rails offers multiple benefits. Scroll down to find the Docker for local development. 

Benefits of Docker for Local Development

Docker helps prevent the infamous “works on my machine” problem by ensuring that the development environment mirrors the production environment. This eliminates inconsistencies that arise from developers using different local setups.

Key practices:

● Use volumes to mount the local code into the Docker container so changes are reflected immediately.

● Define services like PostgreSQL, Redis, and background workers in docker-compose.yml to simplify the setup process.

Example Docker Compose configuration for local development:

services:

  app:

    volumes:

      - .:/app

    command: "bin/rails server -b 0.0.0.0"

  db:

    image: postgres

  redis:

    image: redis

Best Practices for Production Deployments Docker Rails

Here are the best practices you must consider for production deployment Docker Rails;

  1. Using Multi-Stage Builds

Multi-stage builds allow you to create efficient, slim production images by separating the build and runtime stages. For example, you can include build dependencies (like Node.js) in one stage and exclude them from the final production image.

FROM ruby:2.6.6 AS builder

# Install dependencies and build the app

 

FROM ruby:2.6.6 AS production

COPY --from=builder /app /app

 

This approach reduces the final image size and improves security by keeping only the essentials in the production environment.

  1. Minimizing Image Size

For production, you can further minimize image size by excluding development and test gems:

 

RUN bundle install --without development test

Removing cache files and unnecessary libraries also helps to keep the image lean.

  1. Managing Production Environment Variables

Production deployments require secure handling of sensitive data such as database credentials. Use Docker’s secret management capabilities or external secret management tools like AWS Secrets Manager for securely storing environment variables.

Docker Rails Best Practices

Isolating Environments: Use different Docker Compose configurations (e.g., docker-compose.dev.yml vs docker-compose.prod.yml) for different environments.

Database Migrations: Be sure to run migrations automatically upon starting the container in production (e.g., by using an entrypoint script).

Monitoring and Logging: Ensure Docker logs are integrated with your application’s logging system for monitoring and debugging (use tools like the ELK stack).

Rails Continuous Integration and Continuous Deployment (CI/CD) with Docker

Rails CI/CD Docker ensures consistency across development, testing, and production environments. Tools like GitHub Actions, CircleCI, and GitLab CI are commonly used for automating tests and deployment.

Example GitHub Actions CI Workflow

To set up a basic CI workflow with Docker in GitHub Actions:

name: CI

 

on:

  push:

    branches:

      - main

 

jobs:

  test:

    runs-on: ubuntu-latest

 

    services:

      db:

        image: postgres:13

        ports:

          - 5432:5432

        env:

          POSTGRES_USER: postgres

          POSTGRES_PASSWORD: password

 

    steps:

    - uses: actions/checkout@v2

    - name: Set up Ruby

      uses: ruby/setup-ruby@v1

      with:

        ruby-version: 2.7.7

 

    - name: Install dependencies

      run: |

        gem install bundler

        bundle install

 

    - name: Set up the database

      run: |

        bin/rails db:create db:schema:load

 

    - name: Run Tests

      run: |

        RAILS_ENV=test bin/rails test

 The workflow sets up Ruby, installs dependencies, configures a PostgreSQL database, and runs the Rails test suite.

You can extend this workflow to automate deployment to production environments, ensuring your CI/CD pipeline is seamless and reliable.

Some Additional Insights For Docker Rails Best Practices

Besides following all the practices for production deployment Docker Rails, make sure you don’t miss out on following information.

Leverage Docker Caching

To speed up builds, make sure to leverage Docker’s layer caching. Docker caches layers during the build process, which can significantly speed up subsequent builds if nothing has changed in those layers.

Ensure that frequently-changing files (like the application code) are added later in the Dockerfile, and rarely-changing files (like Ruby gems) are added earlier. This way, Docker will only rebuild the final layers when you make changes to your code, and the earlier layers (which install dependencies) are cached.

Log Aggregation

For effective monitoring and debugging in a Dockerized environment, centralized logging is important. Instead of logging to local files, ensure your application logs to stdout/stderr, so Docker can capture and forward them to a logging system (such as ELK stack, Fluentd, or AWS CloudWatch).

# config/environments/production.rb

config.logger = Logger.new(STDOUT)

Use Docker Health-checks

Docker allows you to define health checks to monitor the status of your containerized Rails application. This is useful in production environments where you want to ensure that your application is running correctly, and containers are restarted automatically if they become unresponsive.

For a Rails app, a typical health check might hit the /healthcheck endpoint, which could return a simple status:

services:

  app:

    build: .

    ports:

      - "3000:3000"

    healthcheck:

      test: ["CMD", "curl", "-f", "http://localhost:3000/healthcheck"]

      interval: 30s

      timeout: 10s

      retries: 3

Use tmpfs for Temporary Data

For performance, it's beneficial to store temporary data (such as cache or session data) in memory rather than on disk, especially in a containerized environment. Docker allows you to mount a tmpfs volume, which stores data directly in memory.

For example, in docker-compose.yml, you can add a tmpfs configuration for your temporary files:

services:

  app:

    tmpfs:

      - /app/tmp/cache

 

This reduces disk I/O and can significantly improve the performance of your application.

Monitor and Limit Container Resources

Ensure you set resource limits on your containers to prevent a runaway container from consuming too much memory or CPU. In docker-compose.yml, you can specify resource limits like this:

services:

  app:

    build: .

    ports:

      - "3000:3000"

    deploy:

      resources:

        limits:

          cpus: "0.5"

          memory: "512M"

 

This helps in optimizing resource usage and ensures your app doesn't impact the performance of other services running on the same host.

Security Best Practices

Security is crucial when deploying applications in Docker. Here are some security best practices for Rails apps in Docker:

  • Use official base images: Always use official and up-to-date base images like ruby:2.7.7 to avoid security vulnerabilities.
  • Scan for vulnerabilities: Use tools like Clair or Trivy to scan your Docker images for known vulnerabilities.
  • Run containers as non-root users: By default, Docker containers run as root, which can be risky. Modify your Dockerfile to run as a non-root user:

RUN addgroup --system app && adduser --system --group app

USER app

  • Keep secrets out of images: Never bake secrets (like API keys or passwords) directly into Docker images. Use environment variables or Docker Secrets.

 Rails Docker deployment: Key Takeaways

  • Use Docker to create consistent environments for Rails apps across all stages of development.
  • Optimize the Dockerfile and Docker Compose setup for faster builds and more efficient deployments.
  • Securely manage sensitive data with Docker secrets or external secret managers.
  • Leverage CI/CD pipelines to automate testing, code quality checks, and deployment.

Docker not only simplifies the deployment process for Ruby on Rails applications but also enhances productivity by reducing the complexity of managing environments and dependencies. By following these best practices, you’ll streamline your deployment pipeline and ensure a robust, scalable setup for your Rails app.

 

Contact
Us
Our Technology Stack

Work with future-proof technologies

Typescript
React JS
Node JS
Angular
Vue JS
Rails
Ruby on Rails
Go
Next JS
AWS
SASS