I’d like to share a recipe I used recently to set up a containerized Rails app with a postgres db.

I hit a wall the other day trying to upgrade Ruby to upgrade on my local machine when I was going to start a new Rails project. It seemed there was a problem with the available builds and my ARM processor. So instead of trying to force Ruby to upgrade on my machine, I decided to do a Dockerized setup, which is more portable and self contained anyways.

Here are the steps I used:

mkdir myapp && cd myapp

Then create this Dockerfile:

# Use the official Ruby image from the Docker Hub
FROM ruby:3.1

# Install dependencies
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn

# Set the working directory inside the container
WORKDIR /myapp

# Copy the Gemfile and Gemfile.lock into the container
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

# Install the gems specified in the Gemfile
RUN bundle install

# Copy the rest of your application code into the container
COPY . /myapp

# Expose the port your app runs on
EXPOSE 3000

# Start the main process
CMD ["rails", "server", "-b", "0.0.0.0"]

Then this docker-compose.yml

version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp_development
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: password
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
    environment:
      RAILS_ENV: development

Then on the command line, init the Rails app:

docker run --rm -v ${PWD}:/myapp -w /myapp ruby:3.1 bash -c "gem install rails && rails new . --force --database=postgresql"

And then set up the database connection - create confid/database.yml:

default: &default
  adapter: postgresql
  encoding: unicode
  host: db # This should match the service name defined in docker-compose.yml for PostgreSQL
  username: myapp
  password: password
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
  <<: *default
  database: myapp_development
test:
  <<: *default
  database: myapp_test
production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV["MYAPP_DATABASE_PASSWORD"] %>

Build the Docker containers:

docker-compose build

Set up the DB:

docker-compose run web rails db:create db:migrate

And finally, kick it all off:

docker-compose up

And voila the app should be running at http://localhost:3000!


Some handy things for development:

It’s handy during development to have a command line running within the container so that you can use Rails commands for various tasks.

docker-compose down # or ctrl+c to shut it down
docker-compose up -d # to start in detached/background mode
docker-compose exec web /bin/bash # now you enter a bash shell running within your container
exit # when you want to get out of the shell without stopping container

In the next post we’ll cover getting it set up to work with Tailwind, hot reloading, and a handy component library.