Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.git
.circleci
.github
.DS_Store
coverage
log/*
tmp/*
!log/.keep
Expand Down
23 changes: 8 additions & 15 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# syntax=docker/dockerfile:1
FROM ruby:4.0.4

RUN dpkg --add-architecture i386 \
&& apt-get update -qq \
&& apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
postgresql-client \
imagemagick \
Expand All @@ -12,31 +12,24 @@ RUN dpkg --add-architecture i386 \
libxext6 \
xfonts-75dpi \
xfonts-base \
nodejs \
libc6:i386 \
libstdc++6:i386 \
chromium \
chromium-driver \
&& rm -rf /var/lib/apt/lists/*

RUN gem install bundler -v 2.2.21

WORKDIR /app

COPY Gemfile Gemfile.lock ./
RUN bundle _2.2.21_ install --jobs=4 --retry=3

# Dual-boot: Gemfile.next targets the Rails version we're upgrading to.
# Remove this block (and Gemfile.next / Gemfile.next.lock) once the upgrade lands.
COPY Gemfile.next Gemfile.next.lock ./
RUN BUNDLE_GEMFILE=Gemfile.next bundle _2.2.21_ install --jobs=4 --retry=3
ARG BUNDLE_GEMFILE=/app/Gemfile

COPY . .
ENV BUNDLE_GEMFILE=${BUNDLE_GEMFILE} \
BUNDLE_JOBS=4 \
BUNDLE_RETRY=3

COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
COPY Gemfile Gemfile.lock Gemfile.next Gemfile.next.lock .ruby-version ./
RUN --mount=type=cache,target=/usr/local/bundle/cache,sharing=locked \
bundle install

EXPOSE 3000

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,25 @@ You can see it working in https://audit.fastruby.io

## Getting started (Docker)

The easiest way to run the app locally is with Docker Compose, which builds the app image and a Postgres database for you:
First, run:

docker compose up --build
```bash
bin/docker/setup && \
BUNDLE_GEMFILE=Gemfile.next bin/docker/setup
```

This starts:
This will build both sets of images for each version of Rails. Then, to start the containers:

- `db` — Postgres 16
- `web` — the app on http://localhost:3000, running against the default `Gemfile` (currently Rails 8.1)
- `web_next` — the same image, but with `BUNDLE_GEMFILE=Gemfile.next`, on http://localhost:3001 (see "Dual-boot Rails upgrades" below)
```bash
# For the current version of Rails
bin/docker/start

`docker/entrypoint.sh` copies `config/database.yml.sample` / `.env.sample` into place and runs `rails db:prepare` on boot, so no manual DB setup is needed.
# For the next version of Rails
BUNDLE_GEMFILE=Gemfile.next bin/docker/start
```

Keep in mind that if you change any of the dependencies of the application, you will need to
run the corresponding setup command.

## Getting started (without Docker)

Expand All @@ -35,7 +43,7 @@ You should be able to go to http://localhost:3000 and see the landing page.

Inside Docker:

docker compose run --rm -e RAILS_ENV=test -e DATABASE_HOST=db -e DATABASE_USERNAME=postgres -e DATABASE_PASSWORD=postgres web bin/rails test
bin/docker/run bin/rails test

Without Docker:

Expand Down
10 changes: 10 additions & 0 deletions bin/docker/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/../.."
if [ "$(basename "$BUNDLE_GEMFILE")" = "Gemfile.next" ]; then
CONTAINER=web_next
else
CONTAINER=web
fi
exec docker compose run --rm "$CONTAINER" "$@"

55 changes: 55 additions & 0 deletions bin/docker/setup
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env ruby
require "pathname"
require "fileutils"
include FileUtils

# path to your application root.
APP_ROOT = Pathname.new File.expand_path("../..", __dir__)

def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end

CONTAINER = ENV["BUNDLE_GEMFILE"] == "Gemfile.next" ? "web_next" : "web"
DOCKER_PREFIX = if ENV["CI"] || ENV["RAILS_ENV"] == "test"
# run as the non-root CI user so files created by commands are owned correctly
"docker compose run --user 3434:3434 #{CONTAINER}"
else
# local development: run with the container default user
"docker compose run #{CONTAINER}"
end

# explicit root-run prefix (used only when we must perform privileged actions)
DOCKER_ROOT_PREFIX = "docker compose run --user root #{CONTAINER}"

chdir APP_ROOT do
# This script is a starting point to setup your application.
# Add necessary setup steps to this file.

puts "== Copy .env =="
# We use .env.local because we are using DotenvValidator and there's a known issue with docker-compose: [link](https://github.com/fastruby/dotenv_validator#if-you-use-docker-compose-read-this)
# The symlink is created because `.env.local` is not visible in testing environments but
# we don't want to maintain 2 separate files. This follows [this table](https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use)
unless File.exist?(".env.local")
cp ".env.sample", ".env.local"
system! "ln -s .env.local .env.test"
end

puts "== Setup Database =="
puts "\n== Copying sample files =="
unless File.exist?("config/database.yml")
cp "config/database.yml.sample", "config/database.yml"
end

puts "== Build images =="
system! "docker compose build"

# Some images / mounts can make the rails bin non-executable for the non-root user.
if ENV["CI"]
# run migrations as root to avoid exec permission issues, then restore ownership
system! "#{DOCKER_ROOT_PREFIX} rails db:create db:migrate"
system! "#{DOCKER_ROOT_PREFIX} sh -c 'chown -R 3434:3434 /code || true'"
else
system! "#{DOCKER_PREFIX} rails db:create db:migrate"
end
end
9 changes: 9 additions & 0 deletions bin/docker/start
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/../.."
if [ "$(basename "$BUNDLE_GEMFILE")" = "Gemfile.next" ]; then
CONTAINER=web_next
else
CONTAINER=web
fi
exec docker compose up "$CONTAINER" "$@"
14 changes: 5 additions & 9 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@ services:
tty: true

web_next:
# Reuse the image built for `web` (compose tags it <project>-web, i.e.
# audit-web) instead of building a second image. Only BUNDLE_GEMFILE
# differs, so a separate build is unnecessary. (Not using `extends` here:
# it merges array fields like `ports` instead of overriding them, which
# would leak web's 3000:3000 mapping into this service too.)
image: audit-web
build:
context: .
args:
BUNDLE_GEMFILE: /app/Gemfile.next
platform: linux/amd64
# Distinct pidfile: web and web_next share the same bind-mounted /app, so
# they'd otherwise race on tmp/pids/server.pid and refuse to boot together.
Expand All @@ -53,12 +51,10 @@ services:
DATABASE_HOST: db
DATABASE_USERNAME: postgres
DATABASE_PASSWORD: postgres
BUNDLE_GEMFILE: Gemfile.next
BUNDLE_GEMFILE: /app/Gemfile.next
depends_on:
db:
condition: service_healthy
web:
condition: service_started
stdin_open: true
tty: true

Expand Down
Loading