🐳

M1 MacにDockerでRuby on Rails 6を構築する

2022/10/16に公開

はじめに

Zennでの初投稿です。ふだんは業務委託でRailsアプリを開発したりしてますが、Dockerfileとかコピペして使い回ししてるので改めて備忘録を兼ねて1から手順を確認しながらまとめてみます。

M1 MacにDocker Desktopをインストールし、Docker Composeで環境を構築します。

https://www.docker.com/products/docker-desktop/

環境

使用機材はMacBook Air (M1, 2020)でmacOS Montereyです。

sw_vers

ProductName: macOS
ProductVersion: 12.6
BuildVersion: 21G115

Docker Desktopのバージョンは4.12.0
Docker Desktopのバージョン

Dockerfileを作る前に調査

公式のDockerfile best practicesでお薦めされているAlpine Linuxを使用します。

Dockerfileを作る前にruby:3.1.2-alpine3.16のイメージがどういう環境なのかざっと確認しました。

docker run -it --rm ruby:3.1.2-alpine3.16 /bin/sh

まずは基本的な情報を確認します。

uname -a

Linux feae492f77e3 5.10.124-linuxkit #1 SMP PREEMPT Thu Jun 30 08:18:26 UTC 2022 aarch64 Linux

ホストがM1 Macなためaarch64になっています。

cat /etc/os-release

NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.16.2
PRETTY_NAME="Alpine Linux v3.16"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"

次に環境変数を確認します。

env
環境変数

RUBY_MAJOR=3.1
HOSTNAME=31cbf56488fe
SHLVL=1
HOME=/root
BUNDLE_APP_CONFIG=/usr/local/bundle
RUBY_VERSION=3.1.2
TERM=xterm
PATH=/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=C.UTF-8
GEM_HOME=/usr/local/bundle
RUBY_DOWNLOAD_SHA256=ca10d017f8a1b6d247556622c841fc56b90c03b1803f87198da1e4fd3ec3bf2a
PWD=/
BUNDLE_SILENCE_ROOT_WARNING=1

最後に初期インストールされているパッケージの一覧を確認します。

apk info -v
パッケージ一覧

alpine-baselayout-data-3.2.0-r22
musl-1.2.3-r0
busybox-1.35.0-r17
alpine-baselayout-3.2.0-r22
alpine-keys-2.4-r1
ca-certificates-bundle-20220614-r0
libcrypto1.1-1.1.1q-r0
libssl1.1-1.1.1q-r0
ssl_client-1.35.0-r17
zlib-1.2.12-r3
apk-tools-2.12.9-r3
scanelf-1.3.4-r0
musl-utils-1.2.3-r0
libc-utils-0.7.2-r3
bzip2-1.0.8-r1
ca-certificates-20220614-r0
gmp-6.2.1-r2
libgcc-11.2.1_git20220219-r2
libstdc++-11.2.1_git20220219-r2
libgmpxx-6.2.1-r2
pkgconf-1.8.0-r1
gmp-dev-6.2.1-r2
linux-headers-5.16.7-r1
libffi-3.4.2-r1
libffi-dev-3.4.2-r1
libintl-0.21-r2
ncurses-terminfo-base-6.3_p20220521-r0
ncurses-libs-6.3_p20220521-r0
libproc-3.3.17-r1
procps-3.3.17-r1
yaml-0.2.5-r0
yaml-dev-0.2.5-r0
zlib-dev-1.2.12-r3
readline-8.1.2-r0
.ruby-rundeps-20221007.071338

各ファイルの作成

まず、ホストのMacに作業用のディレクトリを作成し移動します。

mkdir docker_rails && cd $_

このディレクトリにQuickstart: Compose and Railsを参考に各ファイルを作成します。

  1. Gemfile
  2. Gemfile.lock
  3. Dockerfile
  4. docker-compose.yml
  5. 環境変数を設定する.envファイル
  6. .envをバージョン管理の対象外とする.gitignore

1. Gemfile

Gemfileはrailsだけ指定します。後でrails newすると置き換わります。

vi Gemfile
Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6.1.7'

2. Gemfile.lock

Gemfile.lockはDockerfileでCOPYできればいいので空の状態で作成します。

touch Gemfile.lock

3. Dockerfile

Docker DesktopでCPUsを4にしているのでBundlerのジョブ数も4にしています。aarch64なのでlibc6-compatがないとnokogirigemなどでld-linux-aarch64.soが存在しないというエラーが発生します。tzdatatzinfo-datagemで必要です。tzdataがないとエラーが発生します[1]

vi Dockerfile
Dockerfile
FROM ruby:3.1.2-alpine3.16

ENV TZ=Asia/Tokyo

ENV APP_ROOT /sample_app

RUN mkdir -p ${APP_ROOT}

WORKDIR ${APP_ROOT}

COPY Gemfile Gemfile.lock ${APP_ROOT}/

RUN apk update && apk add --no-cache \
        libc6-compat \
        nodejs \
        postgresql-client \
        tzdata \
        yarn \
 && apk add --no-cache --virtual .build-dependencies \
        build-base \
        postgresql-dev \
 && bundle config set --jobs 4 \
 && bundle install \
 && apk del .build-dependencies

COPY . ${APP_ROOT}
参考まで
build-baseでインストールされるパッケージ

(1/16) Installing binutils (2.38-r3)
(2/16) Installing libmagic (5.41-r0)
(3/16) Installing file (5.41-r0)
(4/16) Installing libgomp (11.2.1_git20220219-r2)
(5/16) Installing libatomic (11.2.1_git20220219-r2)
(6/16) Installing isl22 (0.22-r0)
(7/16) Installing mpfr4 (4.1.0-r0)
(8/16) Installing mpc1 (1.2.1-r0)
(9/16) Installing gcc (11.2.1_git20220219-r2)
(10/16) Installing musl-dev (1.2.3-r0)
(11/16) Installing libc-dev (0.7.2-r3)
(12/16) Installing g++ (11.2.1_git20220219-r2)
(13/16) Installing make (4.3-r0)
(14/16) Installing fortify-headers (1.1-r1)
(15/16) Installing patch (2.7.6-r7)
(16/16) Installing build-base (0.5-r3)
Executing busybox-1.35.0-r17.trigger
OK: 209 MiB in 51 packages

postgresql-devでインストールされるパッケージ

(1/16) Installing libpq (14.5-r0)
(2/16) Installing openssl-dev (1.1.1q-r0)
(3/16) Installing libpq-dev (14.5-r0)
(4/16) Installing libecpg (14.5-r0)
(5/16) Installing libecpg-dev (14.5-r0)
(6/16) Installing xz-libs (5.2.5-r1)
(7/16) Installing libxml2 (2.9.14-r1)
(8/16) Installing llvm13-libs (13.0.1-r2)
(9/16) Installing clang-libs (13.0.1-r1)
(10/16) Installing clang (13.0.1-r1)
(11/16) Installing icu-data-en (71.1-r2)
Executing icu-data-en-71.1-r2.post-install
*

  • If you need ICU with non-English locales and legacy charset support, install
  • package icu-data-full.

(12/16) Installing icu-libs (71.1-r2)
(13/16) Installing icu (71.1-r2)
(14/16) Installing icu-dev (71.1-r2)
(15/16) Installing llvm13 (13.0.1-r2)
(16/16) Installing postgresql14-dev (14.5-r0)
Executing busybox-1.35.0-r17.trigger
OK: 493 MiB in 67 packages

4. docker-compose.yml

PostgreSQLのパスワードは.envで設定しenv_fileで指定します。後ほど.gitignoreに追加してバージョン管理の対象外とします。

vi docker-compose.yml
docker-compose.yml
version: "3"
services:
  db:
    image: postgres:14.5
    env_file:
      - .env
    environment:
      TZ: Asia/Tokyo
    volumes:
      - db_volume:/var/lib/postgresql/data
    ports:
      - 5432:5432
  web:
    build: .
    command: sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    environment:
      RAILS_ENV: development
      NODE_ENV: development
    volumes:
      - .:/sample_app
      - bundle_volume:/usr/local/bundle
    ports:
      - "3000:3000"
    tty: true
    stdin_open: true
    depends_on:
      - db
volumes:
  db_volume:
  bundle_volume:

5. .env(PostgreSQLのパスワードの設定)

環境変数を設定するための.envファイルを作成し、任意のパスワードを設定します(後ほどdotenv-railsgemを追加し.envをRailsが認識するようにします)。

vi .env
.env
POSTGRES_PASSWORD=任意のパスワード

6. .gitignore

上記の.envを追加してバージョン管理に含まれないようします。

echo '.env' >> .gitignore

ここまでの手順のソースコードはこちら

https://github.com/trysmr/docker-alpine-rails6-postgres14/tree/before-rails-new

イメージのビルド

1. Railsアプリのスケルトンの作成

まず、rails newで新しくRailsアプリケーションのスケルトンを作成します。

Dockerfileで不要なパッケージを削除するようにしているため(apk delの箇所)、このタイミングでbundle installするとエラーになりますのでスキップします(同じくWebpackもスキップされます[2])。また、今回はRedisやImageMagick、FFmpegなどを用意していないためAction CableとActive Storageもスキップしています。

docker compose run --no-deps web rails new . --force --database=postgresql --skip-bundle --skip-action-cable --skip-active-storage

2. 設定ファイルの編集

.gitignore

rails newで置き換えられた.gitignoreにどこでもいいので.envを追記します(ここではファイル末尾に追加しています)。

echo '.env' >> .gitignore

database.yml

config/database.ymlを設定します。defaultを変更してpasswordは環境変数から取得するようにします。

vi config/database.yml
config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: <%= ENV.fetch('DATABASE_HOST') { 'db' } %>
  username: postgres
  password: <%= ENV.fetch('POSTGRES_PASSWORD') { '' } %>
  pool: <%= ENV.fetch('RAILS_MAX_THREADS') { 5 } %>

また、productionからpasswordを削除します。

config/database.yml
production:
  <<: *default
  database: sample_app_production
  username: sample_app
-  password: <%= ENV['SAMPLE_APP_DATABASE_PASSWORD'] %>

Gemfile

必要なgemを追加します。開発環境の環境変数を.envファイルで管理するためにgroup :development, :test doのブロック内にdotenv-railsgemを追加します。また、Ruby3.1でnet-smtpgemが別になったのでこれも追加します。

vi Gemfile
Gemfile
+  gem "net-smtp"

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
+  gem "dotenv-rails"
end

3. bundle installの実行

webで必要なパッケージを追加しつつbundle installおよびrails webpacker:installを実行するため、shwebのコンテナに入ります。

docker compose run --no-deps web sh
apk update && apk add --no-cache --virtual .build-dependencies build-base postgresql-dev && bundle install && rails webpacker:install && apk del .build-dependencies

bundle installrails webpacker:installが無事に終了したらexitでコンテナから出ます。

exit

4. .dockerignoreの追加

Railsで重要なconfig/master.keyや不要なファイルがイメージに含まれないように.dockerignoreを追加します(rails newで生成された.gitignoreを基にしています)。

vi .dockerignore
.dockerignore
.DS_Store
.git
.gitignore
.dockerignore
Dockerfile
docker-compose.yml
*.env
docker

# Ignore bundler config.
.bundle

# Ignore all logfiles and tempfiles.
log/*
tmp/*
!log/.keep
!tmp/.keep

# Ignore pidfiles, but keep the directory.
tmp/pids/*
!tmp/pids/.keep


public/assets
.byebug_history

# Ignore master key for decrypting credentials and more.
config/master.key

public/packs
public/packs-test
node_modules
yarn-error.log
yarn-debug.log*
.yarn-integrity

5. イメージのビルド

イメージをビルドします。

docker compose build --no-cache

ここまででイメージサイズは以下の通りになりました。

docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE
docker_rails-web latest 86a7076375a1 57 seconds ago 280MB

参考まで

build-basepostgresql-devを残した場合のイメージサイズ

REPOSITORY TAG IMAGE ID CREATED SIZE
docker_rails_base_postgres-web latest 90dcb8dc0cdc 10 seconds ago 759MB

build-baseを残した場合のイメージサイズ

REPOSITORY TAG IMAGE ID CREATED SIZE
docker_rails_base-web latest 0a2d01c64056 34 seconds ago 473MB

サービス起動

RailsとPostgreSQLのサービスを起動します。

docker compose up -d

ログの確認

起動しているか確認します(ログが表示されるまでしばらくかかるかもしれません)。

docker compose logs web

| => Booting Puma
| => Rails 6.1.7 application starting in development
| => Run bin/rails server --help for more startup options
| Puma starting in single mode...
| * Puma version: 5.6.5 (ruby 3.1.2-p20) ("Birdie's Version")
| * Min threads: 5
| * Max threads: 5
| * Environment: development
| * PID: 1
| * Listening on http://0.0.0.0:3000
| Use Ctrl-C to stop

データベースの作成

無事に起動していることを確認したら最後にデータベースを作成します。

docker compose exec web rails db:create

Running via Spring preloader in process 43
Created database 'sample_app_development'
Created database 'sample_app_test'

ブラウザで確認

ブラウザで http://localhost:3000 にアクセスするとRailsの初期状態のトップ画面が表示されます。

Railsの初期状態のトップ画面

スケルトンアプリ作成までのソースコードはこちら

https://github.com/trysmr/docker-alpine-rails6-postgres14/tree/after-skeleton-created

脚注
  1. None of the paths included in TZInfo::DataSources::ZoneinfoDataSource.search_path are valid zoneinfo directories. (TZInfo::DataSources::ZoneinfoDirectoryNotFound ↩︎

  2. Skipping rails webpacker:install because bundle install was skipped.
    To complete setup, you must run bundle install followed by rails webpacker:install. ↩︎

Discussion