しぐまろぐ

勉強したことや読んだ本について書きます。

docker-composeでwebアプリをdocker化する方法

はじめに

現在在籍しているHappiness Chainというプログラミングスクールの課題の一環として、docker-composeを使ってrailsで作られたwebアプリをdocker化する方法について記載しておきます。

なお、あくまでdocker化に焦点を当てることとし、railsアプリの作成方法については記載しません。

Railsプロジェクトの取得

docker化するRailsプロジェクトのコードをgitから取得します。

git clone <repositoryURL>

Dockerfileの作成

まず、Dockerfileを作成します。Dockerfileはコンテナの中身を記載するファイルで、ビルド時に読み込まれます。

FROM ruby:3.2.2
RUN apt-get update -qq && apt-get install -y \
  build-essential \
  libpq-dev \
  nodejs \
  postgresql-client \
  yarn
WORKDIR /rails-docker
COPY Gemfile Gemfile.lock /rails-docker/
RUN bundle install

Dockerfileの書き方

簡単に中身の解説をします。

FROMで使用するイメージを取得します。
RUNでアプリに必要なライブラリやツールをインストールします。
本アプリを動かすディレクトリをWORKDIRで指定します。ここで指定したディレクトリがコンテナ内に存在しない場合は新規作成してくれます。
Railsアプリで使用するGemfileとGemfile.lockは、今回はすでに作成されているので、それをそのままCOPYでコンテナにコピーします。
最後にbundle installを実行します。これで適切にgemをインストールします。

なお、このコマンドの記載順には意味があります。再ビルド時にはキャッシュをうまく使って時間短縮しており、変更があった箇所だけ再実行されるからです。

例えば、Gemfile、Gemfile.lockを更新したとします。
この場合、改めてbuild-essentialなどをインストールする必要はありませんが、必ずbundle installを行いたいため、上記のような順番で書いておきます。

docker-compose.ymlの作成

次に、docker-compose.ymlを作成します。 docker-compose.ymlはdocker composeの設定ファイルです。コンテナをどのように使っていくかについて記載します。

docker composeのメリット

docker composeについて説明する前に、docker-composeを使わない通常のコンテナの起動について見ておきます。
例えば以下のように行います。

docker run -d \
  --network app --network-alias mysql \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=todos \
  mysql:8.0

これは起動時に使用するネットワーク、ボリューム、環境変数、データベースをオプションで指定しているのですが、とても長いコマンドになっていて、打ち込むのも他の開発者に伝えるのも大変ですよね。

docker composeを使えば、このたくさんのオプションはdocker-compose.ymlという設定ファイルに記載しておき、起動時はdocker-compose upだけで良くなります。

起動も楽ですし、他の開発者の方にもdocker-compose.ymlを共有するだけで良くなります。

他にも、docker composeを使うと複数のコンテナを立てやすくなるというメリットもあります。

docker-compose.ymlの書き方

docker composeのメリットがわかったところで、docker-compose.ymlを見ていきましょう。

例えば、railsとpostgresを別々のコンテナに格納する場合、docker-compose.ymlには以下のように設定します。
servicesの下にあるweb, dbがそれぞれrails, postgresのコンテナを指します。

# 現在は大抵バージョン3を指定する
version: '3'

# ホストにボリューム領域を作成
volumes:
  db-data:

services:
  web:
    build: .
    # アプリ起動時に実行するコマンドを指定
    # ここではrailsサーバの起動を実行している
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    # アプリのコードはホストだけに設置としたいため、ホストのファイルをコンテナにマウント
    volumes:
      - .:/rails-docker
    # database.yml(後述)内で使用する環境変数を指定
    environment:
      - 'DATABASE_PASSWORD=postgres'
    # ホストとコンテナのポートを指定
    # railsの場合はデフォルトが3000
    ports:
      - "3000:3000"
    # 先にデータベースを作成するよう指定
    depends_on:
      - db
  db:
    # 使用するデータベースの種類とバージョンを指定
    image: postgres:12
    # コンテナが削除されてもデータが消えないように、ホストのファイルをコンテナにマウント
    volumes:
      - 'db-data:/var/lib/postgresql/data'
    # データベースの環境変数を指定
    environment:
      - 'POSTGRES_USER=postgres'
      - 'POSTGRES_PASSWORD=postgres'

なお、このコンテナをクラウドで公開する場合は、データベースの環境変数はここに書いてはいけません。
別途環境変数を指定するファイルを作成してそこに記載し、そのファイルはGitで共有しないなど対策をしてください。方法については、例えばこちらを参照してください。

config/database.ymlの作成

次にdatabase.ymlの作成をします。
database.ymlはデータベースの設定値について記載したファイルです。
postgresの場合は以下のように書きます。

default: &default
  adapter: postgresql
  encoding: unicode
  # host名がdocker-compose.ymlのサービス名になる
  host: db
  username: postgres
  # postgresのデフォルトのポートは5432
  port: 5432
  # このファイルをGitに上げたりするので、パスワードは直接書かない
  # 環境変数はホスト側(docker-compose.yml)から読み取る
  password: <%= ENV.fetch("DATABASE_PASSWORD") %>
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

使用するデータベースの種類によっても異なるため、詳細は割愛します。

ビルドしてデータベースの作成

設定ファイルの作成が終わったので、ビルドしてコンテナ内でデータベースを作成します。

# バックグラウンドで起動するので「-d」オプションを追加
# ビルドが2回目以降の場合はさらに「--build」をつけること
$ docker-compose up -d
# コンテナの中に入る
$ docker-compose exec web bash
# データベースを作る
$ rails db:create
# データベースにテーブル定義を作る
$ rails db:migrate
# コンテナから出る
$ exit
# 再度起動
$ docker-compose up

正常にデータベースが作成できていれば、エラーは出ません。

起動して確認

ブラウザでlocalhost:3000にアクセスできるかを確認します。
また、ボリュームのマウントが上手くいっているかも確認してください。

  1. アプリでデータ登録の処理を実行する
  2. Ctrl + Cでアプリを停止
  3. $ docker-compose upで再度起動
  4. localhost:3000にアクセスし、1で登録したデータが残っているか確認する。

ここで残っていなければ、ボリュームのマウンドがうまくいっていないので、docker-compose.ymlの当該箇所を確認してください。

参考