Alpine+Rails6+PostgreSQLのDockerfileを作る

2020年5月23日

Rails

Alpine Linux ベースの Docker イメージで Rails6.1 + PostgreSQL の環境を作る機会がありました。構築手順を残しておきます。

目次

  1. Rails プロジェクトを生成する
  2. Rails 用の Docker イメージを作る
  3. ローカル開発用の docker-compose.yml を作る
  4. DB の設定をする
  5. データベースの初期化とコンテナの起動
  6. 以降はデバッグの記録です
  7. Quickstart を確認する
  8. Rails6 をインストール
  9. Rails プロジェクトを作成する
  10. tzdata をインストールする
  11. Node.js と yarn をインストールする
  12. DB の設定をする
  13. ここまでの手順を Dockerfile に落とし込む
  14. Docker イメージの容量を小さくする
  15. ENTRYPOINT を設定する
  16. docker-compose.yml で command を指定

Rails プロジェクトを生成する

Rails プロジェクトを生成するための Dockerfile を作成します。

FROM ruby:2.7.2-alpine3.12

WORKDIR /myapp

COPY . .

RUN apk update \
    && apk add --no-cache --virtual=.build-deps \
    build-base \
    && apk add --no-cache \
    postgresql-dev \
    tzdata \
    nodejs~=12 \
    yarn

Docker イメージを作成します。

docker build -t rails-installer .

Docker コンテナを起動し、ログインします。

docker run --rm -v $(pwd):/myapp:delegated -ti rails-installer ash

Rails をインストールします。

gem install rails -v "6.1.0"

Rails プロジェクトを作成します。

rails new . -d postgresql

これで、ホストマシンに Rails プロジェクトが展開されました。コンテナから抜け、rails-installer イメージを削除しておきます。

docker rmi rails-installer

Rails 用の Docker イメージを作る

Dockerfile と同じディレクトリに entrypoint.sh を作ります。

#!/bin/sh
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

Dockerfile を以下のように変更します。

FROM ruby:2.7.2-alpine3.12

ARG RAILS_ENV
ARG RAILS_MASTER_KEY

WORKDIR /myapp

COPY . .

RUN apk update \
    && apk add --no-cache --virtual=.build-deps \
    build-base \
    && apk add --no-cache \
    postgresql-dev \
    tzdata \
    nodejs~=12 \
    yarn \
    && bundle install \
    && yarn install \
    && rails assets:precompile \
    && apk del .build-deps

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]

ARG で指定している RAILS_ENVRAILS_MASTER_KEY は CI サーバなどで本番環境用の Docker イメージをビルドするときに使います。

ローカル開発用の docker-compose.yml を作る

docker-compose.yml を作ります。

version: '3'

services:
  app:
    build: .
    command: ash -c "rm -f tmp/pids/server.pid && rails s -p 3000 -b '0.0.0.0'"
    depends_on:
      - db
    environment:
      - DATABASE_HOST=db
      - DATABASE_USERNAME=postgres
      - DATABASE_PASSWORD=example
    ports:
      - "3000:3000"
    volumes:
      - .:/myapp:delegated

  db:
    image: postgres:13-alpine
    environment:
      POSTGRES_PASSWORD: example
    volumes:
      - db:/var/lib/postgresql/data

volumes:
  db:

DB の設定をする

config/database.yml の development と test 環境を編集し DB の接続情報を環境変数から受け取るようにします。

development:
  <<: *default
      database: myapp_development
      username: <%= ENV['DATABASE_USERNAME'] %>
      password: <%= ENV['DATABASE_PASSWORD'] %>
      host: <%= ENV['DATABASE_HOST'] %>
test:
  <<: *default
      database: myapp_test
      username: <%= ENV['DATABASE_USERNAME'] %>
      password: <%= ENV['DATABASE_PASSWORD'] %>
      host: <%= ENV['DATABASE_HOST'] %>

データベースの初期化とコンテナの起動

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

docker-compose build

データベースを作成します。

docker-compose run --rm app bin/rails db:create

マイグレーションを実行します。

docker-compose run --rm app bin/rails db:migrate

コンテナを起動します。

docker-compose up

ブラウザから http://localhost:3000 にアクセスして、「Yay! You’re on Rails!」が表示されていれば OK です。

以降はデバッグの記録です

開発環境を作るだけならここまでの手順で大丈夫です。ここから先は Alpine ベースの Rails コンテナを作成する上で、必要なパッケージを探すためにデバッグをした記録となります。少々長いのとバージョンが執筆時点のものになり少し古いです。気になる方のみどうぞ。

次回は、このアプリケーションを Heroku にデプロイします。

Heroku
RailsアプリケーションをHerokuで公開する

前回は Rails の開発環境を Docker で作成しました。 作成した Rails のアプリケーションを Herok ...

続きを見る

Quickstart を確認する

Rails の Docker による環境構築は Quickstart: Compose and Rails | Docker Documentation を参考に進めます。

Quickstart の Dockerfile はこんな感じ。

FROM ruby:2.5
RUN apt-get update -qq &amp; apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

これを alpine ベースに変更していきます。

Rails6 をインストール

まずは Rails のインストールから。Dockerfile を新しく作成します。

Dockerfile

FROM ruby:2.7.2-alpine3.11

WORKDIR /myapp

次に docker-compose.yml を作ります。Rails コンテナは Quickstart: Compose and Rails | Docker Documentation を参考に、PostgresSQL は postgres - Docker Hub を参考にしています。

docker-compose.yml

version: '3'

services:
  app:
    build: .
    depends_on:
      - db
    ports:
      - "3000:3000"
    volumes:
      - .:/myapp:delegated

  db:
    image: postgres:12-alpine
    environment:
      POSTGRES_PASSWORD: example
    volumes:
      - db:/var/lib/postgresql/data

volumes:
  db:

docker コンテナを起動して中に入ります。

docker-compose run --service-ports app ash

Rails をインストールしてみます。記事執筆時点では 6.0.3 が最新なのでバージョンを指定します。

gem install rails -v "6.0.3"

失敗しました。

/usr/local/lib/ruby/2.7.0/mkmf.rb:471:in `try_do': The compiler failed to generate an executable file. (RuntimeError)
You have to install development tools first.

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /usr/local/bundle/extensions/x86_64-linux-musl/2.7.0/nokogiri-1.10.9/mkmf.log

エラーログを見ると、gcc などが入っていないのが原因のようなので、コンパイルに必要なツールが入っている build-base をいれます。

apk add build-base

もう一度 rails のインストールを実行。

gem install rails -v "6.0.3"

今度は成功しました。rails-6.0.3.1 が入りました。

Successfully installed rails-6.0.3.1
32 gems installed

Rails プロジェクトを作成する

引き続きコンテナ内で作業。rails new コマンドで rails プロジェクトを作ります。DB は PostgreSQL を使うのでオプションで指定します。

rails new . -d postgresql

pg の gem のインストールで失敗しました。

Fetching pg 1.2.3
Installing pg 1.2.3 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /usr/local/bundle/gems/pg-1.2.3/ext
/usr/local/bin/ruby -I /usr/local/lib/ruby/2.7.0 -r ./siteconf20200522-11586-1h6s4wi.rb extconf.rb
checking for pg_config... no
No pg_config... trying anyway. If building fails, please try again with
 --with-pg-config=/path/to/pg_config
checking for libpq-fe.h... no
Can't find the 'libpq-fe.h header

おそらく何かのパッケージが足りないので、pg の gem のドキュメントを確認します。pg gem の Requirements によると PostgreSQL -dev packages が必要なようです。

alpine のパッケージで postgresql を探してみると

apk search postgresql | grep dev

dev パッケージがありました。

postgresql-pllua-dev-2.0.4-r0
postgresql-bdr-dev-9.4.14_p1-r7
postgresql-dev-12.2-r0

postgresql-dev を入れます。

apk add postgresql-dev

先ほど止まった gem のインストールから再開します。今度は成功しました。

bundle install

tzdata をインストールする

rails server -b 0.0.0.0 コマンドでサーバを起動してみます。

rails server -b 0.0.0.0

エラーが出てきました。tzinfo の gem で TZInfo::DataSourceNotFound が出ているようです。

/usr/local/bundle/gems/tzinfo-1.2.7/lib/tzinfo/data_source.rb:182:in `rescue in create_default_data_source': tzinfo-data is not present.
Please add gem 'tzinfo-data' to your Gemfile and run bundle install (TZInfo::DataSourceNotFound)

tzinfo の gem のドキュメント を確認します。

By default, TZInfo will attempt to use TZInfo::Data. If TZInfo::Data is not available (i.e. if require 'tzinfo/data' fails), then TZInfo will search for a zoneinfo directory instead (using the search path specified by TZInfo::ZoneinfoDataSource::DEFAULT_SEARCH_PATH).

ドキュメントによると、TZInfo は最初に TZInfo::Data クラスを使おうとするようです。Gemfile を確認すると tzinfo-data という gem があります。TZInfo はこの中に入っていそうです。しかしこの gem は platforms で環境の指定があり(Windows 環境での利用を想定されているように見えます)、alpine linux のコンテナ環境では動いていません。

Gemfile

gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

TZInfo::Data が利用可能でない場合、TZInfo::ZoneinfoDataSource::DEFAULT_SEARCH_PATH の zoneinfo を使うと書かれています。ドキュメントには PATH が書かれていないので、ソースを確認します。

tzinfo/zoneinfo_data_source.rb

DEFAULT_SEARCH_PATH = ['/usr/share/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo'].freeze

上記 3 つのディレクトリは Alpine Linux ベースの ruby イメージには存在しないので、TZInfo::DataSourceNotFound エラーにつながったと予想できます。

Alpine Linux ではタイムゾーン関連の情報は tzinfo パッケージにあるので、インストールします。

apk add tzdata

インストール後、/usr/share/zoneinfo ディレクトリにタイムゾーンの情報が配置されました。

ls /usr/share/zoneinfo

Node.js と yarn をインストールする

再び rails server -b 0.0.0.0 コマンドでサーバを起動してみます。

rails server -b 0.0.0.0

今度は Webpacker 周りでエラーが出てきました。

/usr/local/bundle/gems/webpacker-4.2.2/lib/webpacker/configuration.rb:95:in `rescue in load': Webpacker configuration file not found /myapp/config/webpacker.yml.
Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - /myapp/config/webpacker.yml (RuntimeError)

エラーメッセージの通り、rails webpacker:install を実行します。

rails webpacker:install

Node.js がインストールされていないエラーが出てきました。

Node.js not installed. Please download and install Node.js https://nodejs.org/en/download/

最新安定版の Node.js を入れます。この記事を書いた時点では 12.16.3 が最新安定版なので、12 系を入れます。

apk add nodejs~=12

12.15.0 が入りました。Alpine なのでちょっと古いです。

(4/4) Installing nodejs (12.15.0-r1)

エラーメッセージの通り、rails webpacker:install を実行します。

rails webpacker:install

再び rails webpacker:install を実行します。

rails webpacker:install

Yarn がインストールされていないエラーが出てきました。

Yarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/

Yarn を入れます。

apk add yarn

rails webpacker:install を実行します。

rails webpacker:install

今度はうまくいきました。

Webpacker successfully installed

rails server -b 0.0.0.0 コマンドでサーバを起動します。

rails server -b 0.0.0.0

ようやく起動しました。

Puma starting in single mode...
* Version 4.3.5 (ruby 2.7.1-p83), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000

DB の設定をする

ブラウザから http://localhost:3000 にアクセスすると、DB の接続エラーがでてきました。そういえば DB 周りの設定を忘れていました。

config/database.yml の development と test 環境を編集し DB の接続情報を環境変数から受け取るようにします。

development:
  <<: *default
      database: myapp_development
      username: <%= ENV['DATABASE_USERNAME'] %>
      password: <%= ENV['DATABASE_PASSWORD'] %>
      host: <%= ENV['DATABASE_HOST'] %>
test:
  <<: *default
      database: myapp_test
      username: <%= ENV['DATABASE_USERNAME'] %>
      password: <%= ENV['DATABASE_PASSWORD'] %>
      host: <%= ENV['DATABASE_HOST'] %>

docker-compose.yml で DB に接続するための環境変数を設定します。

services:
  app:
    build: .
    depends_on:
      - db
    environment:
      - DATABASE_HOST=db
      - DATABASE_USERNAME=postgres
      - DATABASE_PASSWORD=example
    ports:
      - "3000:3000"
    volumes:
      - .:/myapp:cached

Ctrl-C で rails server コマンドを止めて、rails db:create コマンドでデータベースを作成します。

rails db:create

DB ができました。

Created database 'myapp_development'
Created database 'myapp_test'

rails server -b 0.0.0.0 コマンドで起動します。

rails server -b 0.0.0.0

ブラウザから http://localhost:3000 にアクセスすると、「Yay! You’re on Rails!」が表示されました。

app_1  | Started GET "/" for 192.168.80.1 at 2020-05-22 08:22:50 +0000
app_1  | Cannot render console from 192.168.80.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
app_1  | Processing by Rails::WelcomeController#index as HTML
app_1  |   Rendering /usr/local/bundle/gems/railties-6.0.3.1/lib/rails/templates/rails/welcome/index.html.erb
app_1  |   Rendered /usr/local/bundle/gems/railties-6.0.3.1/lib/rails/templates/rails/welcome/index.html.erb (Duration: 7.1ms | Allocations: 294)
app_1  | Completed 200 OK in 28ms (Views: 16.1ms | ActiveRecord: 0.0ms | Allocations: 1634)

ここまでの手順を Dockerfile に落とし込む

ここまではコンテナの中で作業していました。docker run したときに rails server を起動できるように、これまでやってきた内容を Dockerfile に落とし込みます。

Dockerfile

FROM ruby:2.7.2-alpine3.12

WORKDIR /myapp

COPY . .

RUN apk update \
    && apk add --no-cache \
    build-base \
    postgresql-dev \
    tzdata \
    nodejs~=12 \
    yarn \
    && bundle install \
    && yarn install

EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]

コンテナから抜けて、Docker イメージをビルド。

docker-compose build

コンテナ上で rails が起動してブラウザからアクセスできることを確認しておきます。

docker-compose up

Docker イメージの容量を小さくする

コンパイル用のパッケージ build-base は bundle install した後は不要になるはずなので、最後に削除するようにします。

Dockerfile

RUN apk update \
    && apk add --no-cache --virtual=.build-deps \
    build-base \
    && apk add --no-cache \
    postgresql-dev \
    tzdata \
    nodejs~=12 \
    yarn \
    && bundle install \
    && yarn install \
    && apk del .build-deps

ENTRYPOINT を設定する

Quickstart との差分を埋めていきます。 entrypoint.sh を作ります。

entrypoint.sh

#!/bin/ash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

ENTRYPOINT を Dockerfile に設定。

Dockerfile

FROM ruby:2.7.2-alpine3.12

WORKDIR /myapp

COPY . .

RUN apk update \
    && apk add --no-cache --virtual=.build-deps \
    build-base \
    && apk add --no-cache \
    postgresql-dev \
    tzdata \
    nodejs~=12 \
    yarn \
    && bundle install \
    && yarn install \
    && rails assets:precompile \
    && apk del .build-deps

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]

docker-compose.yml で command を指定

docker-compose.yml も Quickstart に合わせ command を指定するようにします。

docker-compose.yml

version: '3'

services:
  app:
    build: .
    command: ash -c "rm -f tmp/pids/server.pid && rails s -p 3000 -b '0.0.0.0'"
    depends_on:
      - db
    environment:
      - DATABASE_HOST=db
      - DATABASE_USERNAME=postgres
      - DATABASE_PASSWORD=example
    ports:
      - "3000:3000"
    volumes:
      - .:/myapp:delegated

  db:
    image: postgres:13-alpine
    environment:
      POSTGRES_PASSWORD: example
    volumes:
      - db:/var/lib/postgresql/data

volumes:
  db:

Docker イメージをビルドし、コンテナを起動します。Rails の画面が出ていれば OK です。

次回は、このアプリケーションを Heroku にデプロイします。

Heroku
RailsアプリケーションをHerokuで公開する

前回は Rails の開発環境を Docker で作成しました。 作成した Rails のアプリケーションを Herok ...

続きを見る

-技術ブログ
-