Docker Compose: Create a Development Environment

Learn how to set up an efficient development environment for Ruby on Rails applications using Docker Compose. Our tutorial guides you through the process of containerizing, synchronizing application code, and configuring databases and services.

Application development can be complex, especially when setting up development environments. Docker offers an elegant solution by enabling applications to be encapsulated within containers. These containers are isolated, portable, and provide consistent environments, making development, troubleshooting, and deployment of applications easier.

The setup involves the following steps:

  1. Synchronize application code on the host with the code in the container to facilitate changes during development.
  2. Persist application data between container restarts.
  3. Configure Sidekiq workers to process jobs as expected.

Requirements

Before starting this tutorial, you will need:

  • A local development or server system with Ubuntu 18.04.
  • Docker and Docker Compose installed according to the instructions in the relevant guides.

Step 1: Clone Project and Add Dependencies


git clone https://github.com/do-community/rails-sidekiq.git rails-docker
cd rails-docker
nano Gemfile


gem ‘pg’, ‘~>1.1.3’

# Comment out sqlite gem
# gem ‘sqlite3’

# Comment out spring-watcher-listen gem
# gem ‘spring-watcher-listen’, ‘~> 2.0.0’

Step 2: Configure Application for PostgreSQL and Redis



default: &default
adapter: postgresql
encoding: unicode
database: <%= ENV[‘DATABASE_NAME’] %>
username: <%= ENV[‘DATABASE_USER’] %>
password: <%= ENV[‘DATABASE_PASSWORD’] %>
port: <%= ENV[‘DATABASE_PORT’] || ‘5432’ %>
host: <%= ENV[‘DATABASE_HOST’] %>
pool: <%= ENV.fetch(“RAILS_MAX_THREADS”) { 5 } %>
timeout: 5000

development:
<<: *default

test:
<<: *default

production:
<<: *default




DATABASE_NAME=rails_development
DATABASE_USER=sammy
DATABASE_PASSWORD=shark
DATABASE_HOST=database
REDIS_HOST=redis

Step 3: Write Dockerfile and Entry Scripts



FROM ruby:2.5.1-alpine

ENV BUNDLER_VERSION=2.0.2

RUN apk add –update –no-cache \
binutils-gold \
build-base \
curl \
file \
g++ \
gcc \
git \
less \
libstdc++ \
libffi-dev \
libc-dev \
linux-headers \
libxml2-dev \
libxslt-dev \
libgcrypt-dev \
make \
netcat-openbsd \
nodejs \
openssl \
pkgconfig \
postgresql-dev \
python \
tzdata \
yarn

RUN gem install bundler -v 2.0.2

WORKDIR /app

COPY Gemfile Gemfile.lock ./

RUN bundle config build.nokogiri –use-system-libraries

RUN bundle check || bundle install

COPY package.json yarn.lock ./

RUN yarn install –check-files

COPY . ./

ENTRYPOINT [“./entrypoints/docker-entrypoint.sh”]



mkdir entrypoints
nano entrypoints/docker-entrypoint.sh


#!/bin/sh

set -e

if [ -f tmp/pids/server.pid ]; then
rm tmp/pids/server.pid
fi

bundle exec rails s -b 0.0.0.0



chmod +x entrypoints/docker-entrypoint.sh


nano entrypoints/sidekiq-entrypoint.sh


#!/bin/sh

set -e

if [ -f tmp/pids/server.pid ]; then
rm tmp/pids/server.pid
fi

bundle exec sidekiq



chmod +x entrypoints/sidekiq-entrypoint.sh

Step 4: Define Services with Docker Compose

With Docker Compose, we can run the multiple containers required for our setup. We will define our composition services in our main `docker-compose.yml` file. A service in Compose is a running container, and the service definitions you include in your `docker-compose.yml` file contain information on how each container image runs. The Compose tool allows you to define multiple services to create multi-container applications.

Our application setup will include the following services:

  • The application itself
  • The PostgreSQL database
  • Redis
  • Sidekiq

We will also include a bind mount as part of our setup so that any code changes we make during development are immediately synchronized with the containers that need access to that code.

Open the `docker-compose.yml` file:

First, add the service definition for the application:


version: ‘3.4’

services:
app:
build:
context: .
dockerfile: Dockerfile
depends_on:
– database
– redis
ports:
– “3000:3000”
volumes:
– .:/app
– gem_cache:/usr/local/bundle/gems
– node_modules:/app/node_modules
env_file: .env
environment:
RAILS_ENV: development

The application service definition includes the following options:

– Build: Defines the configuration options for building the application image.
– Depends On: Specifies dependencies that must be provided before starting the app service.
– Ports: Maps host port 3000 to container port 3000.
– Volumes: Defines the volume mounts for the app service.
– Env File and Environment: Sets environment variables passed to the container.

Next, add the following code below the application service definition to define your database service:


database:
image: postgres:12.1
volumes:
– db_data:/var/lib/postgresql/data
– ./init.sql:/docker-entrypoint-initdb.d/init.sql

Unlike the app service, the database service pulls a Postgres image directly from Docker Hub and uses the `db_data` volume to persist application data between container starts.

Then, add the Redis service definition:

Finally, add the Sidekiq service definition:


sidekiq:
build:
context: .
dockerfile: Dockerfile
depends_on:
– app
– database
– redis
volumes:
– .:/app
– gem_cache:/usr/local/bundle/gems
– node_modules:/app/node_modules
env_file: .env
environment:
RAILS_ENV: development
entrypoint: ./entrypoints/sidekiq-entrypoint.sh

Our Sidekiq service resembles our app service in some respects. It uses the same build context and image, environment variables, and volumes. However, it depends on the app, Redis, and database services, so it is started last. Additionally, it uses an entry point that overrides the entry point set in the Dockerfile and starts the Sidekiq service.

Step 5: Add Volume Definitions Below the Sidekiq Service Definition


volumes:
gem_cache:
db_data:
node_modules:

Our top-level `volumes` key defines the `gem_cache`, `db_data`, and `node_modules` volumes. When Docker creates volumes, the contents of the volume are stored in a part of the host filesystem, `/var/lib/docker/volumes/`, managed by Docker. The contents of each volume are stored in a directory under `/var/lib/docker/volumes/` and mounted to each container that uses the volume. This way, our application service’s data persists in the `db_data` volume, even if we remove and recreate the database service.

The finished file looks like this:


version: ‘3.4’

services:
app:
build:
context: .
dockerfile: Dockerfile
depends_on:
– database
– redis
ports:
– “3000:3000”
volumes:
– .:/app
– gem_cache:/usr/local/bundle/gems
– node_modules:/app/node_modules
env_file: .env
environment:
RAILS_ENV: development

database:
image: postgres:12.1
volumes:
– db_data:/var/lib/postgresql/data
– ./init.sql:/docker-entrypoint-initdb.d/init.sql

redis:
image: redis:5.0.7

sidekiq:
build:
context: .
dockerfile: Dockerfile
depends_on:
– app
– database
– redis
volumes:
– .:/app
– gem_cache:/usr/local/bundle/gems
– node_modules:/app/node_modules
env_file: .env
environment:
RAILS_ENV: development
entrypoint: ./entrypoints/sidekiq-entrypoint.sh

volumes:
gem_cache:
db_data:
node_modules:

Save and close the file when you are finished editing.

Create a Free Account

Register now and get access to our Cloud Services.

Posts you might be interested in: