Recently I made some experiments to setup a Rails 6 dev environment using Docker.
I started from the Docker’s quick start.

Dockerfile

I install some extra packages like chromium-chromedriver for feature specs.

# Base image
FROM ruby:2.7.2-alpine

# Install required packages and tools
RUN apk add --no-cache --update build-base chromium chromium-chromedriver nano nodejs npm postgresql-client postgresql-dev sqlite sqlite-dev tzdata
RUN npm install -g yarn

# Setup the entrypoint script
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh

# Copy project files
WORKDIR /project
COPY project /project

# Command entrypoint
ENTRYPOINT ["entrypoint.sh"]

docker-compose.yml

For a multi projects environment it can be useful to keep a shared volume for the gems:

version: "3.8"

## Setup
# mkdir -p /Users/mat/containers/common/nodejs-12.18.4/node_modules
# mkdir -p /Users/mat/containers/common/ruby-2.7.2/bundle
# docker volume create --driver local --opt type=none --opt o=bind --opt device=/Users/mat/containers/common/nodejs-12.18.4/node_modules nodejs-12.18.4-node_modules
# docker volume create --driver local --opt type=none --opt o=bind --opt device=/Users/mat/containers/common/ruby-2.7.2/bundle ruby-2.7.2-bundle

x-defaults:
  app: &default_app
    environment: &enviroments
      RAILS_ENV: development
    user: "${UID}:${GID}"

services:
  db:
    environment:
      POSTGRES_HOST_AUTH_METHOD: trust
      POSTGRES_USER: postgres
    image: postgres:13-alpine

  redis:
    image: redis:6-alpine
    volumes:
      - ${PWD}/config/redis.conf:/usr/local/etc/redis/redis.conf:delegated

  app:
    <<: *default_app
    build:
      context: ${PWD}
    command:
      - sh
      - -c
      - |
        rm -f tmp/pids/server.pid
        bundle install --path /project/vendor
        yarn install --check-files
        bin/rails s -p 3000 -b '0.0.0.0'
    depends_on:
      - db
      - redis
    environment:
      <<: *enviroments
      CHROMEDRIVER_PATH: /usr/bin/chromedriver
      DB_HOST: db
      DB_USERNAME: postgres
    ports:
      - "3000:3000"
    volumes:
      - ${PWD}/project:/project:delegated
      - gem_cache:/project/vendor:cached
      - node_modules:/project/node_modules:cached

volumes:
  gem_cache:
    external: true
    name: ruby-2.7.2-bundle
  node_modules:
    external: true
    name: nodejs-12.18.4-node_modules

entrypoint.sh

#!/bin/sh
set -e
exec "$@"

.env

UID=${UID}
GID=${GID}

Project Setup

The database configuration is pretty standard - project/config/database.yml:

default: &default
  adapter: postgresql
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: <%= ENV.fetch("DB_HOST") { 'localhost' } %>
  username: <%= ENV.fetch("DB_USERNAME") { 'postgres' } %>
  timeout: 5000

development:
  <<: *default
  database: db_development

test:
  <<: *default
  database: db_test

Usage

  • Start the machine (and create the machine the first time): docker-compose up -d
  • Setup DB (the first time): docker-compose exec app bin/rails db:reset
  • Enter in the machine: docker-compose exec app sh
  • Stop the machine: docker-compose down
  • Destroy the machine (and delete the VM): docker-compose down -v --rmi local

Conclusion

I like having shared volumes for dependencies because setting up new projects is faster, but also using Dockerfile cache layers is a nice option.

If you are curious, take a look also to this post about setting up a Vagrant Rails 6 dev environment.

Feel free to leave me a comment to improve this post.