はじめに
本記事はRuby on Rails7.0とPostgresql12で構築したwebアプリケーションへdockerを導入する手順をまとめたものです。
"dockerを導入する"ことにフォーカスした記事となのでrubyやRails、postgresの知識がなくてもお読みいただけます。
この記事でわかること
- Webアプリケーションをdocker化する方法
- ベースイメージのバージョンの決め方
- Dockerイメージの軽量化
- docker-composeを使うメリット
フォルダ構成
今回dockerを導入するにあたって追加したファイルは以下の三つです。上から順に説明していきます。
- Dockerfile
- docker-compose.yml
- config/database.yml
Dockerfile
Dockerfileの中身はこちらです。
FROM ruby:3.2.2
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
nodejs \
postgresql-client \
yarn
WORKDIR /rails-docker
COPY Gemfile Gemfile.lock /rails-docker/
RUN bundle install
まずはDocker InstructionのFROMを使ってベースとなるイメージを設定します。このイメージに対して必要なものを追加していくことで最終的にアプリケーションが動く環境が完成します。
FROM ruby:3.2.2
今回はRailsで作られたアプリケーションを動かすためrubyのベースイメージとします。イメージの情報はDocker Hubから確認できます。必要なバージョンはプロジェクト内のGemfileを参照します。実際にGemfileを見ると以下のようになっています。
ruby "3.2.2"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.0.6"
Railsのバージョン7.0.6を使用していて、それに必要なRubyのバージョンは3.2.2ということが分かります。よってruby:3.2.2をベースのイメージとして設定しています。この辺りは使用する言語とフレームワークの依存関係によって変わってきます。
ruby;latestじゃダメなの?
ちなみにruby:latest
とすることで最新バージョンのrubyイメージを指定することができます。個人利用でとりあえず動かしたい場合などはこの方法でも問題はないかと思います。
しかし、Docker hub上のイメージは日夜更新されていくため、意図しないバージョン変更が起こり、不具合の原因となり得ます、その可能性を回避するため、明示的に3.2.2を指定しています。
latestを指定する場合はその目的とリスクを考慮した上で使用するのが良いでしょう。
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
nodejs \
postgresql-client \
yarn
続いてapt-get updateとinstallについてです。
まずはapt-get update
でパッケージリストを更新し、その後必要なパッケージをapt-get install
でインストールしています。-yをオプションを指定して確認プロンプトを自動でスキップするようにしています。
Dockerイメージを軽量化する
Dockerイメージが大きくなりすぎるといくつかデメリットがあります。
- ローカルのストレージを圧迫する
- buildに時間がかかる
ポイントはDockerイメージレイヤーの数を減らすことです。
Dockerイメージレイヤーの数が増えることは、イメージサイズの増加につながります。
ではどんな時にイメージレイヤーが作られるか確認していきましょう。Docker Instructionにはいくつか種類がありますが、イメージレイヤーが作られるのは、 RUN、COPY、ADDを実行した時です。つまり、この三つのコマンドを使う回数を減らすことで、無駄なイメージレイヤーを作らずにdockerイメージを構築できます。どうすれば減らせるのか、次の2点を確認しましょう。
- コマンドを
&&
で繋げる \
で改行を入れる
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
nodejs \
postgresql-client \
yarn
最初に紹介したコードは&&
を使ってapt-get updateとapu-get installをまとめています。さらに、\
で改行を入れることでinstallするパッケージも一挙に書くことができています。
RUN apt-get update
RUN apt-get install -y
RUN build-essential -y
RUN libpq-dev -y
RUN nodejs -y
RUN postgresql-client -y
RUN yarn
&&
や\
を使わずに書いた例がこちらです。書いてある内容は同じですが、こちらの方が冗長なのが一目でわかると思います。最初の例ではRUNコマンドが1回で済んでいたのに対し、後者では7回も使っています。
イメージレイヤーが作られるのは、 RUN、COPY、ADDを実行した時
余分なイメージレイヤーが6つもできていることになります。これは効率的とは言えませんし、&&
や\
でまとめて書いた方がコードもスッキリ書けるのがお分かりいただけたと思います。
Dockerfileを記述する際はイメージを極力軽量化する点を意識すると、自分だけじゃなく周りもハッピーですね。
docker-compose.yml
version: '3'
volumes:
db-data:
services:
web:
build: .
command: >
bash -c "rm -f tmp/pids/server.pid && rails db:create && rails db:migrate && rails s -b 0.0.0.0"
ports:
- '3000:3000'
volumes:
- '.:/rails-docker'
environment:
- 'DATABASE_PASSWORD=postgres'
tty: true
stdin_open: true
depends_on:
- db
links:
- db
db:
image: postgres:12
volumes:
- 'db-data:/var/lib/postgresql/data'
environment:
- 'POSTGRES_USER=postgres'
- 'POSTGRES_PASSWORD=postgres'
Dockerfileの設定が完了したので、続いてdocker-compose.ymlです。
docker-composeにはDockerfileを元にできたイメージを元にコンテナをどう立ち上げるかを記載します。docker run
を実行するとき、必要に応じて-v
などのオプションをつけますよね。それらを書いていくイメージです。
docker-composeを使うことで以下のようなメリットがあります。
- 実行コマンドが簡潔に
- 複数のコンテナを同時に立ち上げできる
実行コマンドが簡潔に
docker-composeを使うことでコンテナ立ち上げ時のコマンドを簡潔にすることができます。今回の場合、docker-compose up
のみでコンテナを立ち上げることができます。
逆に使わないとどうなるか。webコンテナを立ち上げる場合を例にすると、以下のように非常に長いコマンドが必要となります。
docker run -it -v<ローカルのパス>:/rails-docker -p 3000:3000 <image> bash
これを毎回打つのは骨が折れるのでdocker-composeを使って楽しちゃいましょう。
複数のコンテナを同時に立ち上げできる
コンテナを複数同時に立ち上げたい場面はよくあると思います。例えばフロントエンド、バックエンド、データベースなど分けることがあります。この場合、一度それぞれの情報をdocker-compose.ymlに記載してしまえば、以降はdocker-compose up
で一挙に立ち上げることができます。
docker-compose.ymlの中身
version: '3'
volumes:
db-data:
services:
web:
build: .
command: >
bash -c "rm -f tmp/pids/server.pid && rails db:create && rails db:migrate && rails s -b 0.0.0.0"
ports:
- '3000:3000'
volumes:
- '.:/rails-docker'
environment:
- 'DATABASE_PASSWORD=postgres'
tty: true
stdin_open: true
depends_on:
- db
links:
- db
db:
image: postgres:12
volumes:
- 'db-data:/var/lib/postgresql/data'
environment:
- 'POSTGRES_USER=postgres'
- 'POSTGRES_PASSWORD=postgres'
version: '3'
docker-composeのバージョンは3を指定。
servicesの中にwebコンテナとdbコンテナについて記述しています。
volumes:
db-data:
コンテナを終了or削除してもデータを保持できるようデータベースの中身をホストへ同期します。
まずはwebコンテナについてです。webコンテナは名前の通りwebサーバーとなるコンテナで、ここでrailsを実行しています。
web:
build: .
command: >
bash -c "rm -f tmp/pids/server.pid && rails db:create && rails db:migrate && rails s -b 0.0.0.0"
ports:
- '3000:3000'
volumes:
- '.:/rails-docker'
environment:
- 'DATABASE_PASSWORD=postgres'
tty: true
stdin_open: true
depends_on:
- db
links:
- db
build: .
現在のディレクトリにあるDockerfileを元にビルドします。
bash -c "rm -f tmp/pids/server.pid && rails db:create && rails db:migrate && rails s -b 0.0.0.0"
command
ではコンテナ起動時に実行するコマンドを指定できます。以下のコマンドが順番に実行されます。
-
bash -c
以降の文字列をシェルで実行する -
rm -f tmp/pids/server.pid
前回のサーバーのプロセスID(PID)ファイルを強制的に削除します。これは、アプリケーションサーバーが以前のセッションからクリーンアップされなかった場合の問題を防ぐためのものです。server.pidが存在すると、WEBサーバが起動中(プロセスがある)と判断され、下記のエラーが発生します。
A server is already running. Check /tmp/pids/server.pid. Exiting
-
rails db:create
Railsのデータベースを作成します。 -
rails db:migrate
データベースのマイグレーションを実行します。 -
rails s -b 0.0.0.0
Railsのサーバーを起動します。
ports:
- '3000:3000'
ポート番号を指定します。これによりホストPCのlacalhost:3000でアプリにアクセスできます。
volumes:
- '.:/rails-docker'
ホストPCとコンテナ内のファイルを同期します。ホストPCのカレントディレクトリとコンテナ内の/rails-docker配下を同期しています。
environment:
- 'DATABASE_PASSWORD=postgres'
コンテナ内で扱う環境変数を設定します。データベースのパスワードとして'postgres'を設定しています。
※今回はサンプルとして分かりやすいパスワードで設定しています。
tty: true
stdin_open: true
コンテナ内で対話的な操作を可能にするオプションです。docker run -it
と同義です。
depends_on:
- db
コンテナ間の依存関係を設定しています。dbコンテナが起動した後webコンテナを起動します
"webコンテナはdbコンテナが必要なのでdbコンテナから立ち上げてね”という意味になります。
links:
- db
webコンテナとdbサービスが通信できるようにリンクします。
続いてdbコンテナです。webコンテナと共通したコマンドは割愛します。
db:
image: postgres:12
volumes:
- 'db-data:/var/lib/postgresql/data'
environment:
- 'POSTGRES_USER=postgres'
- 'POSTGRES_PASSWORD=postgres'
データベースはpostgresを使用します。バージョンは12です。
volumes:
- 'db-data:/var/lib/postgresql/data'
前述のdb-dataボリュームをPostgresのデータディレクトリにマウントします。
コンテナを終了or削除してもデータを保持できるようデータベースの中身をホストへ同期します。
config/database.yml
こちらにはアプリケーションがデータベースに接続する方法を設定するために使用されます。
Rails特有のファイルですので、読み飛ばしていただいて大丈夫です。
開発、テスト、本番環境のデータベースをそれぞれどう設定するかを記述します。今回は開発環境を設定しています。変更箇所を説明します。
.
.
.
# 開発環境
default: &default
adapter: postgresql
encoding: unicode
host: db
user: postgres
port: 5432
password: <%= ENV.fetch("DATABASE_PASSWORD") %>
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
.
.
.
adapter
にはRailsアプリケーションが使用するデータベースの種類を設定します。今回はpostgresを使用するので'postgresql'としています。
ちなみにmysqlの場合は'mysql12'、sqliteの場合は'sqlite3'のようにデータベースの種類によって値が決まっています。
port
は'5432'とします。これはPostgreSQLデータベースのデフォルトのTCPポート番号です。postgresを使用する場合'5432'を使用するのが一般的なようです。
password
はdocker-compose.ymlのdbコンテナ部分で指定した'POSTGRES_PASSWORD=postgres'
とリンクしています。
アプリケーションの立ち上げ
ここまで完了すればあとはdocker-compose up
でコンテナを起動しlacalhost:3000でアプリケーションにアクセスできるようになります。
以上がWebアプリケーション(Ruby on Rails7.0 × postgres12)をdocker化する手順となります。
最後に
ここまでお読みいただきありがとうました。
初めての技術記事投稿となり分かりづらい箇所もあったと思いますが、どなたかのヒントになれば幸いです。
またネタができたら投稿いたします。
## 参考記事
米国AI開発者がゼロから教えるDocker講座
A server is already running. Check /myapp/tmp/pids/server.pid. の対処法