Initialize a Rails app on macOS with docker
Published on October 8th, 2023
My current Mac doesn't have enough space to install Xcode Command Line Tools and it turns out it is almost impossible to install Ruby and Rails locally without it.
Instead I decided to install it inside docker right from the start.
The first step is simply to create the following files:
Initial files
Create a Dockerfile with the following content:
# Dockerfile-local
# syntax=docker/dockerfile:1
ARG RUBY_VERSION=3.2.2
FROM ruby:$RUBY_VERSION
# Install system dependencies
# Add the necessary dependencies for building gem native extensions,
# PostgreSQL client, and Node.js (for the Rails asset pipeline)
RUN apt-get update -qq && \
apt-get install -y build-essential libpq-dev nodejs && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
# Configure the main process to run when running the image
CMD ["rails", "server", "-b", "0.0.0.0"]
We name the file Dockerfile-local because Rails will create a production Dockerfile for us from 7.1 on.
Create a Gemfile with the following content:
# Gemfile
source "https://rubygems.org"
gem "rails", "~> 7.1.0"
Create an empty Gemfile.lock:
touch Gemfile.lock
Create an entrypoint.sh file with the following content:
#entrypoint.sh
#!/bin/bash
set -e
# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid
# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
Create a docker-compose.yml file with the following content:
# docker-compose.yml
services:
db:
image: postgres
volumes:
- ./tmp/db:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
web:
build:
context: .
dockerfile: Dockerfile-local
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
Build the project
docker compose run --no-deps web rails new . --force --database=postgresql
--no-deps
here prevents docker from running the linked services in (database, etc.), only because we haven't installed anything yet.
docker compose build
Now replace the default: &default
block of code in config/database.yml
#config/database.yml
default: &default
adapter: postgresql
encoding: unicode
host: db
username: postgres
password: password
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
Run your containers
docker compose up
And create the database
docker compose run web rake db:create
Congratulations, your dev server is up and running at http://0.0.0.0:3000.
Automate the process
All this manual work can be completely automated with a bash script.
Create a file named setup-rails.sh
with the following content:
#!/bin/bash
# Setup script for Dockerized Rails App
# To run simply do:
## bash setup-rails.sh myapp
# or
## bash setup-rails.sh to use default app name 'myapp'
# Ensure the script is run with a name argument or set default name
APP_NAME=${1:-myapp}
# Ensure script is run from the project directory
mkdir -p $APP_NAME
cd $APP_NAME
# Dockerfile-local
DOCKERFILE_LOCAL_CONTENT=$(cat <<DOCKERFILE
ARG RUBY_VERSION=3.2.2
FROM ruby:\$RUBY_VERSION
RUN apt-get update -qq && \\
apt-get install -y build-essential libpq-dev nodejs && \\
apt-get clean && \\
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /$APP_NAME
COPY Gemfile /$APP_NAME/Gemfile
COPY Gemfile.lock /$APP_NAME/Gemfile.lock
RUN bundle install
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
DOCKERFILE
)
echo "$DOCKERFILE_LOCAL_CONTENT" > Dockerfile-local
# Gemfile
GEMFILE_CONTENT="source \"https://rubygems.org\"\ngem \"rails\", \"~> 7.1.0\""
echo -e "$GEMFILE_CONTENT" > Gemfile
touch Gemfile.lock
# entrypoint.sh
ENTRYPOINT_CONTENT=$(cat <<'ENTRYPOINT'
#!/bin/bash
set -e
rm -f /myapp/tmp/pids/server.pid
exec "$@"
ENTRYPOINT
)
echo "$ENTRYPOINT_CONTENT" > entrypoint.sh
chmod +x entrypoint.sh
# docker-compose.yml
DOCKER_COMPOSE_CONTENT=$(cat <<DOCKER_COMPOSE
services:
db:
image: postgres
volumes:
- ./tmp/db:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
web:
build:
context: .
dockerfile: Dockerfile-local
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/$APP_NAME
ports:
- "3000:3000"
depends_on:
- db
DOCKER_COMPOSE
)
echo "$DOCKER_COMPOSE_CONTENT" > docker-compose.yml
# Build the project
docker compose run --no-deps web rails new . --force --database=postgresql
# Build Docker images
docker compose build
# Overwrite database config
DATABASE_CONFIG_CONTENT=$(cat <<'DATABASE_CONFIG'
# PostgreSQL. Versions 9.3 and up are supported.
#
# Install the pg driver:
# gem install pg
# On macOS with Homebrew:
# gem install pg -- --with-pg-config=/usr/local/bin/pg_config
# On Windows:
# gem install pg
# Choose the win32 build.
# Install PostgreSQL and put its /bin directory on your path.
#
# Configure Using Gemfile
# gem "pg"
#
# default: &default
# adapter: postgresql
# encoding: unicode
# # For details on connection pooling, see Rails configuration guide
# # https://guides.rubyonrails.org/configuring.html#database-pooling
# pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
default: &default
adapter: postgresql
encoding: unicode
host: db
username: postgres
password: password
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: ${APP_NAME}_development
test:
<<: *default
database: ${APP_NAME}_test
production:
<<: *default
database: ${APP_NAME}_production
username: $APP_NAME
password: <%= ENV["${APP_NAME^^}_DATABASE_PASSWORD"] %>
DATABASE_CONFIG
)
echo "$DATABASE_CONFIG_CONTENT" > config/database.yml
# Run containers
docker compose up -d
# Create DB
docker compose run web rake db:create
# Feedback to user
echo -e "\nSetup complete! Your Rails application '$APP_NAME' should be accessible at http://0.0.0.0:3000\n"
Make sure this script has the execute permission
chmod +x setup-rails.sh
Run it
bash setup-rails.sh myapp
Open your browser at http://0.0.0.0:3000
Et voilà !
Sources