Docker で rails のプロジェクトを作成する手順(PostgreSQL)
Docker で rails プロジェクトを作成して起動できるまでの手順です。
WEB で調べると何種類かやり方が出てくるのですが、作成されるプロジェクトのバージョンが意図したものが作れなかったり、手順通りにしてもすんなり起動できなったりとしっくりくるものがありませんでした。
手順通りにしてもすんなり起動できない場合にエラーを理解して自分で対処することもできず困っていました。
そもそも Docker や rails についても初学者であるため、まずは基本的な構築方法を習得し、そこからカスタマイズして理解したいと思ったので基本的な状態でのプロジェクトの起動手順をまとめました。
この手順の目的は rails プロジェクトを新規作成し、その状態で起動確認できるところまでなので、この手順通りに作成したプロジェクトをそのまま使うのではなく、あくまで基礎としてプロジェクトに合わせてカスタマイズするためのものです。
最初からプロジェクトに合わせた設定をして構築できればいいのですが、そもそも起動できる状態のものがなければカスタマイズできないので、その素体として使用します。
作成するプロジェクトのバージョン
最初に rails プロジェクトをローカル環境に構築してから Docker コンテナにプロジェクトをコピーするのでローカル環境の構築が必要になります。
- MacOS
- ruby 3.2.2
- rails 7.1.1
- postgres 16
構築手順
- ローカル環境を構築
- rbenv をインストール
- ruby と rails のインストール
- postgresql のインストール
- ローカル環境に rails new でプロジェクトを作成
- Docker でビルドしプロジェクトを起動
- env ファイルと comopse.yaml を作成
- database.yml を編集
- docker でビルド
- DB を作成する
- docker-compose up で起動して rails の初期画面を確認
環境ができてしまえば手間は env ファイルの作成
と compose.yaml の作成
、database.yml
の修正、DB の作成
だけです。
ローカル環境を構築
rbenv をインストール
# Homebrew を使って rbenv をインストール
% brew install rbenv
# rbenvをシェルに組み込む設定
% echo 'eval "$(rbenv init --path)"' >> ~/.zshrc
# シェルをリロード
% source ~/.zshrc
ruby と rails をインストール
# Rubyをインストール
% rbenv install 3.2.2
% rbenv global 3.2.2
# Railsをインストール
% gem install rails -v 7.1.1
PostgreSQL をインストール
# PostgreSQLをインストール
brew install postgresql
rails プロジェクトを作成
Database に PostgreSQL を指定してプロジェクトを作成します。
# プロジェクト作成
% rails new myapp -d postgresql
# ディレクトリ移動
% cd myapp
rails プロジェクト内に env ファイルを作成する
ディレクトリ構造
myapp
└── .env
├── development
│ ├── database
│ └── web
# その他のプロジェクトファイル
.env/development/database
の内容
DB の接続情報を記述します。
POSTGRES_DB=myapp_development
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
.env/development/web
の内容
後述する Dockerfile
には RAILS_ENV
で本番環境が指定されているため、開発環境で起動するようにオーバーライドします。
RAILS_ENV=development
rails プロジェクト内に compose.yaml を作成する
今回、 Dockerfile
は rails new
でプロジェクトを作成した際に自動で作成されるものをそのまま使用します。
また、よく rails 固有のサーバーファイル(server.pid)の削除のために docker-entrypoint.sh
などのスクリプトを作成しますが、こちらも /rails/bin/docker-entrypoint
に含まれているため作成しません。
ここでは docker-compose
用に compose.yaml
のみを作成します。
services:
db:
image: postgres:16
ports:
- "5432:5432"
env_file:
- .env/development/database
volumes:
- "db_data:/var/lib/postgresql/data"
web:
build: .
env_file:
- .env/development/web
- .env/development/database
command: bash -c "bundle e rails s -b '0.0.0.0'"
volumes:
- .:/usr/src/app
- gem_cache:/gems
ports:
- "3000:3000"
depends_on:
- db
volumes:
db_data:
gem_cache:
ちなみに自動で作成される Dockerfile
の内容はこのようになっています。作成するプロジェクトのバージョンによって変わると思います。
内容については後述します。
# syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.2.2
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
# Rails app lives here
WORKDIR /rails
# Set production environment
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
# Throw-away build stage to reduce size of final image
FROM base as build
# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config
# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE*PATH}"/ruby/*/bundler/gems/\_/.git && \
bundle exec bootsnap precompile --gemfile
# Copy application code
COPY . .
# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
# Final stage for app image
FROM base
# Install packages needed for deployment
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libvips postgresql-client && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Copy built artifacts: gems, application
COPY /usr/local/bundle /usr/local/bundle
COPY /rails /rails
# Run and own only the runtime files as a non-root user for security
RUN useradd rails --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER rails:rails
# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]
また、/rails/bin/docker-entrypoint
の内容はこのようになっています。
#!/bin/bash -e
# If running the rails server then create or migrate existing database
if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
./bin/rails db:prepare
fi
exec "${@}"
rails プロジェクト内の database.yml を編集する
DB と接続するために config/database.yml
を修正します。
host
、username
、password
を追記します
必要に応じて database
も修正します。
default: &default
adapter: postgresql
encoding: unicode
host: db # compose.yaml の DB コンテナ名を指定する
username: <%= ENV['POSTGRES_USER'] %> # 追加
password: <%= ENV['POSTGRES_PASSWORD'] %> # 追加
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: myapp_development
Docker でビルドする
% docker-compose build
DB を作成
# コンテナ起動前(手順通り)ならこちら
% docker-compose run web rake db:create
% docker-compose run web rake db:migrate
# コンテナを先に起動させた場合はこちら
% docker-compose exec web rake db:create
% docker-compose exec web rake db:migrate
起動確認
docker-compose
でコンテナを起動します。
% docker-compose up
接続確認
http://localhost:3000 に接続して確認する
これで rails の初期画面が表示されればひとまず成功です。
最初に述べたようにあくまでもこれは素体なのでここから必要に応じてカスタマイズしてください。
例えば自動で作成される Dockerfile
は本番環境用となっているため、開発用では不要な項目も多いと思います。
今回はひとまず標準の状態で起動が確認できるまでですが、ここまでで気になった点を ChatGPT に聞いてみたのでその内容を残しておきます。これからカスタマイズして使用するのに参考になればと思います。
これより先は AI による出力内容です。
私は初学者ですのでこれらのことに関してまだ詳しくないため正誤の判断はできません。
rails new で自動で作成される Dockerfile の内容について
この Dockerfile は、Rails アプリケーションをコンテナ化するためのマルチステージビルドの一例です。マルチステージビルドを用いることで、最終的なイメージサイズを小さく抑えつつ、ビルドやアセットのプリコンパイルなどを効率的に行うことができます。それでは、各セクションごとに解説していきます。
ベースステージ
# syntax = docker/dockerfile:1
ARG RUBY_VERSION=3.2.2
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
WORKDIR /rails
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
解説
- 1 行目 では、Dockerfile の構文バージョンを指定しています。新しいビルド機能や構文を利用するためです。
- ARG で指定されている RUBY_VERSION は、ビルド時にパラメータとして設定可能な変数です。ここでは Ruby のバージョンを指定しています。
- FROM はベースとなるイメージを指定します。ここでは、slim プロファイル(軽量版)の Ruby イメージを使用します。
- WORKDIR で、作業ディレクトリ(アプリケーションのルートディレクトリ)を指定します。
- ENV で、環境変数を設定します。ここでは、Rails の実行環境、Bundler の設定などを行っています。
ビルドステージ
FROM base as build
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile --gemfile
COPY . .
RUN bundle exec bootsnap precompile app/ lib/
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
解説
- ビルドステージ は、ベースステージを元にビルド用の環境を作ります。
- apt-get で、Gems のビルドやアプリケーションの実行に必要なパッケージをインストールします。
- COPY で Gemfile をコピーし、 bundle install で Gems をインストールします。その後、不要なファイルを削除してイメージのサイズを抑えています。
- アプリケーションのコードをコピーし、bootsnap をプリコンパイルし、アセットをプリコンパイルします。
ファイナルステージ
FROM base
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libvips postgresql-client && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
COPY /usr/local/bundle /usr/local/bundle
COPY /rails /rails
RUN useradd rails --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER rails:rails
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
EXPOSE 3000
CMD ["./bin/rails", "server"]
解説
- ファイナルステージ は、最終的なアプリケーションイメージを作成します。ベースステージを元に、実行に必要な最低限のパッケージをインストールします。
- COPY --from=build でビルドステージで用意した Gems やアプリケーションコードをコピーします。
- ユーザー
rails
を作成し、一部のディレクトリのオーナーシップを設定後、そのユーザーでコマンドを実行します(セキュリティ強化のため)。 - ENTRYPOINT でコンテナ起動時に実行されるコマンドを指定します。
- EXPOSE で公開するポートを指定します。
- CMD でコンテナが起動する際に実行されるデフォルトのコマンドを指定します。ここでは Rails サーバーを起動しています。
Dockerfile について
-
開発用 Dockerfile:開発用の
Dockerfile
はビルド時間を短縮するため、ある程度シンプルに保つのが良いです。また、開発ではデバッグやテストのための追加ツールが必要になるかもしれません。
以下は、シンプルな開発用Dockerfile
の一例です。
FROM ruby:3.2.2
# 必要なパッケージをインストール
RUN apt-get update -qq && \
apt-get install -y build-essential libpq-dev nodejs
# Yarnのインストール
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update && apt-get install -y yarn
WORKDIR /myapp
# Gemファイルを追加し、インストール
COPY Gemfile* /myapp/
RUN bundle install
# その他のアプリケーションソースをコピー
COPY . /myapp
このDockerfile
では、アセットのプリコンパイルなど本番特有のステップを省略しています。開発中はコードの変更が頻繁にあるため、ビルドを高速に保つことが重要です。
補足
- 最低限の権限を持つユーザー:本番環境ではセキュリティを強化するため、アプリケーションは root ユーザーとして実行せず、限定された権限を持つユーザーとして実行すべきです。
具体的には、Dockerfile
に以下のコマンドを追加して、アプリケーションを実行するユーザーを作成し、そのユーザーでコマンドを実行します。
RUN useradd -m myuser
USER myuser
上記をDockerfile
の適切な位置に追加します。
- 本番環境でのボリュームマウントの避け方:本番環境では、コンテナにコードをコピーし、変更のないイメージをデプロイすることをお勧めします。
Dockerfile
で以下のようにアプリケーションコードをコピーします。
COPY . /myapp
開発環境でのボリュームのマウントは、ローカルのコード変更をすぐに反映するために使用しますが、本番環境ではこのような動作は望ましくありません。そのため、docker-compose.yml
において、本番用のセクション(サービスやdocker-compose.override.yml
など)を作成し、そこではボリュームのマウントを行わないようにします。
開発と本番で設定が異なる場合、docker-compose
を使ってそれぞれの用途に適したコンテナの起動方法を定義すると、一貫した操作性を保ちつつ、環境ごとの違いを適切にハンドリングできます。
開発用と本番用のDockerfile
を作成例です。
開発用の Dockerfile
# 開発用の Dockerfile
FROM ruby:3.2.2
# 必要なパッケージをインストール
RUN apt-get update -qq && \
apt-get install -y build-essential libpq-dev nodejs
# Yarnのインストール
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update && apt-get install -y yarn
# 作業ディレクトリのセットアップ
WORKDIR /myapp
# Gemファイルを追加し、インストール
COPY Gemfile* /myapp/
RUN bundle install
# その他のアプリケーションソースをコピー
COPY . /myapp
本番用の Dockerfile
# 本番用の Dockerfile
FROM ruby:3.2.2 AS builder
# 必要なパッケージをインストール
RUN apt-get update -qq && \
apt-get install -y build-essential libpq-dev nodejs
# Yarnのインストール
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update && apt-get install -y yarn
# 作業ディレクトリのセットアップ
WORKDIR /myapp
# Gemファイルを追加し、インストール
COPY Gemfile* /myapp/
RUN bundle install --without development test \
&& rm -rf /usr/local/bundle/cache/*.gem \
&& find /usr/local/bundle/gems/ -name "*.c" -delete \
&& find /usr/local/bundle/gems/ -name "*.o" -delete
# アプリケーションソースをコピー
COPY . /myapp
# アセットプリコンパイル
RUN bundle exec rake assets:precompile
# 実行ユーザーを作成
RUN useradd -m myuser
USER myuser
# 実行環境のセットアップ
FROM ruby:3.2.2
# 必要なパッケージをインストール
RUN apt-get update -qq && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
# 作業ディレクトリのセットアップ
WORKDIR /myapp
# ユーザーを追加
RUN useradd -m myuser
USER myuser
# ビルダーから必要なファイル/ディレクトリをコピー
COPY /usr/local/bundle/ /usr/local/bundle/
COPY /myapp /myapp
開発用のDockerfile
はすべての依存関係をインストールし、コードをコンテナ内にコピーします。基本的にはデバッグやテストのツールを追加したり、ボリュームをマウントしてローカルのコード変更を即時に反映させる形になります。
本番用のDockerfile
はビルドステージ(builder
)とランステージを分け、ビルドステージで依存関係のインストールやアセットのプリコンパイルを行い、ランステージではアプリケーションコードとビルドで生成されたものをコピーして軽量なコンテナを作成します。また、セキュリティを強化するため非 root ユーザーでアプリケーションを実行します。
各環境によってdocker-compose.yml
やDockerfile
を調整し、ローカル開発の効率と本番運用のセキュリティ・効率を両立させていくことが大切です。
本番環境と開発環境で異なるDockerfile
を使用する場合、それに伴いdocker-compose.yml
も環境ごとに異なる設定を持たせるのが一般的です。開発環境と本番環境では、使うコンテナの設定や、セキュリティ設定、ネットワークの設定などが異なる可能性があるためです。
docker-compose.yml
開発用 開発用のdocker-compose.yml
では、コードの変更を即座に反映させるためにボリュームをマウントし、開発ツールを含めた Docker イメージを使用します。
version: "3"
services:
db:
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_DB: myapp_development
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
web:
build:
context: .
dockerfile: Dockerfile.dev # 開発用のDockerfileを指定
ports:
- "3000:3000"
volumes:
- ".:/myapp"
depends_on:
- db
docker-compose.yml
本番用 本番環境用のdocker-compose.yml
では、セキュリティを強化し、パフォーマンスを最適化します。コードはイメージにコピーされ、ボリュームは使用されません。
version: "3"
services:
db:
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_DB: myapp_production
POSTGRES_USER: postgres
POSTGRES_PASSWORD: productionpassword
web:
build:
context: .
dockerfile: Dockerfile.prod # 本番用のDockerfileを指定
ports:
- "3000:3000"
depends_on:
- db
留意点として、本番環境のdocker-compose.yml
では、シークレットな情報(データベースのユーザー名やパスワードなど)は環境変数や.env
ファイル、もしくは秘密情報を安全に管理するための専用ツールを使用して、コンテナ外から渡すようにしましょう。
両環境において、サービス(データベースやキャッシュストアなど)の設定が異なる可能性もあるため、それぞれの環境に最適なdocker-compose.yml
を作成することで、よりシームレスな開発・デプロイフローを実現することができます。
docker-compose
コマンドは-f
または--file
オプションを使って、使用するdocker-compose.yml
ファイルを指定することができます。複数のdocker-compose.yml
ファイルを持っている場合(例えば、docker-compose.dev.yml
とdocker-compose.prod.yml
)、このオプションを使用してどちらの設定でコンテナを起動するかを選べます。
開発環境での起動例:
docker-compose -f docker-compose.dev.yml up
本番環境での起動例:
docker-compose -f docker-compose.prod.yml up
docker-compose up
コマンドを実行するディレクトリは、指定したdocker-compose.yml
ファイルが存在するディレクトリである必要があります。また、複数のdocker-compose.yml
ファイルを指定して、設定をオーバーライド(上書き)することも可能です。
複数のファイルを指定した起動例:
docker-compose -f docker-compose.yml -f docker-compose.override.yml up
このコマンドは、最初にdocker-compose.yml
の設定を読み込み、次にdocker-compose.override.yml
の設定を読み込んで、最初の設定を上書きします。これにより、開発環境や本番環境で異なる設定を適用することが可能になります。
なぜローカルにプロジェクトを作成してから Docker コンテナにコピーするのか(Docker コンテナ内でプロジェクトを作成しないのはなぜなのか)
Rails プロジェクトをローカルで作成してから Docker コンテナにコピーする方法と、Docker コンテナ内で直接プロジェクトを作成する方法はいずれも有効ですが、以下の視点から前者のアプローチが一般的にとられることが多いです。
1. 開発効率
- エディタの利便性: ローカルでプロジェクトを生成することで、開発者は自身のお気に入りのエディタや IDE を用いてコードを容易に編集できます。特に IDE の強力なコード補完やリファクタリング機能は、ローカルで直接コードを触る場合に有効です。
- ハードウェアリソース: ローカルで生成・実行した方が、Docker コンテナを経由するよりもハードウェアリソース(CPU, メモリ, I/O)を効率的に利用できる場合があります。
2. ビルド時間の短縮
- プロジェクトを最初にローカルで生成しておくことで、
docker build
を実行する際にbundle install
などの時間を短縮できる場合があります。Gemfile が変わらない限り Docker キャッシュを利用でき、ビルド時間を節約できます。
3. 組み込みのデータベースセットアップ
- ローカルで Rails プロジェクトを作成することで、
rails new
コマンドで生成される初期マイグレーションファイルを用いて、開発者は即座にデータベースをセットアップできます。
4. コンテナのイメージサイズ
- プロジェクトをローカルで作成し、必要なファイルだけをコンテナにコピーすることで、コンテナのイメージサイズを小さく保つことが可能です。特に Node モジュールや一部の Gem など、大量のファイルを生成するパッケージが多いため、これらをコンテナイメージに含めると大きくなりがちです。
5. ローカルでのデバッグ
- 開発中、ローカルで手早く動作確認やデバッグを行いたい場合があります。ローカルでプロジェクトがセットアップされていると、全てのコンテナを起動することなく、素早く動作確認が可能です。
6. CI/CD パイプラインとの整合性
- CI/CD パイプラインを構築する場合、テストやビルドを行うステージではコードがコンテナにコピーされることが一般的です。ローカルでの開発フローと CI/CD のフローを統一することで、本番環境に近いテスト環境を容易に構築でき、デプロイプロセスをスムーズに進めることができます。
結論
いずれの方法も一長一短がありますが、上記の理由から、ローカルでプロジェクトを作成してからコンテナにコピーする方法が採用されることが多いです。プロジェクトのニーズや開発チームの作業スタイルによって、最適な方法は異なるため、特定のコンテキストに最も適したアプローチを選択することが重要です。
Discussion