docker buildx build を使用して Rails の master.key をコンテナイメージから除外する

master.key とは

Rails の master.key とは認証情報を暗号化する時に使用されるキーファイルです。rails credentials:edit で認証情報を credentials.yml.enc に暗号化(ENCrypt) して保存する時に使用されます。認証機能に Firebase authentication を利用している場合、Firebase の API キーを格納している方が多いのではないでしょうか。

10.1 Custom Credentials

Rails stores secrets in config/credentials.yml.enc, which is encrypted and hence cannot be edited directly. Rails uses config/master.key or alternatively looks for the environment variable ENV["RAILS_MASTER_KEY"] to encrypt the credentials file. Because the credentials file is encrypted, it can be stored in version control, as long as the master key is kept safe.

master.key は .dockerignore でコンテナイメージに入れないようにしておく

私の場合 fly.io のデプロイ手順で Dockerfile を生成したので、master.key はデフォルトで除外する設定になっていました。スクラッチから作る時は Github などから 参考となる .dockerignore をいくつか見て回るのがよいかもしれません。

➜ grep master.key .dockerignore 
config/master.key

master.key がない場合に Rails を失敗させる config.require_master_key 設定

config.require_master_key を true に設定することで、キーファイル master.key もしくは環境変数 RAILS_MASTER_KEY が存在せず Rails が認証情報を取得できない場合に、Rails コマンドを異常終了させることができます。

config/environments/development.rb

  # Causes the app to not boot if a master key hasn't been made available through
  # ENV["RAILS_MASTER_KEY"] or the config/master.key file.
  config.require_master_key = true

コンテイメージビルド失敗: 抱える矛盾

この設定を有効にした場合、認証情報を取得できない場合は rails db:migrate も失敗するようになります。コンテナイメージを作成する場合に必要となる rails db:migrate が失敗するのでこのままではコンテナイメージを作成することができません。

Dockerfile

RUN bundle install
RUN bundle exec rails db:migrate
➜ docker buildx build -t camelot:latest .
[+] Building 27.8s (13/14)
 => [internal] load build definition from Dockerfile                                                                                                 0.0s
 => => transferring dockerfile: 626B                                                                                                                 0.0s

(... snip ...)

 => [ 8/10] RUN bundle install                                                                                                                      23.8s
 => ERROR [ 9/10] RUN bundle exec rails db:migrate                                                                                                   2.8s
------
 > [ 9/10] RUN bundle exec rails db:migrate:
#0 2.753 Missing encryption key to decrypt file with. Ask your team for your master key and write it to /camelot/config/master.key or put it in the ENV['RAILS_MASTER_KEY'].
------
ERROR: failed to solve: executor failed running [/bin/sh -c bundle exec rails db:migrate]: exit code: 1

➜ 

コンテナイメージビルド成功: docker buildx build の Secret to expose the build (--secret) を利用して矛盾を解決する

コンテナイメージに master.key は含めたくないがビルドには必要、という矛盾を解決するために docker buildx build が提供している機能が Secret to expose the build (--secret) です。この機能を利用することで、master.key をビルド時に「一時的に」渡すことが可能になります。

Dockerfile 側で「一時的に」渡すがイメージには含めたくないファイルを RUN --mount ... で指定し、コマンド側で docker buildx build --secret ... で指定して受け渡しをそれぞれ設定します。

Dockerfile

RUN bundle install
RUN --mount=type=secret,id=master_key,target=config/master.key,required=true bundle exec rails db:migrate
➜ docker buildx build --secret id=master_key,src=./config/master.key -t camelot:latest .
[+] Building 29.4s (15/15) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                 0.0s
 => => transferring dockerfile: 626B                                                                                                                 0.0s

(... snip ...)

 => [ 8/10] RUN bundle install                                                                                                                      23.1s
 => [ 9/10] RUN --mount=type=secret,id=master_key,target=config/master.key,required=true bundle exec rails db:migrate                                3.6s
 => [10/10] COPY ./db/development.sqlite3 /camelot/db/                                                                                               0.0s
 => exporting to image                                                                                                                               1.7s
 => => exporting layers                                                                                                                              1.6s
 => => writing image sha256:900504dca3fe2761332f919e0526baa016c053722466844412efdacf0a371d37                                                         0.0s
 => => naming to docker.io/library/camelot:latest                                                                                                    0.0s

➜ 

Secret to expose the build (--secret) 機能を使用することで、先ほどはコンテナイメージビルド時に失敗した rails db:migrate が「一時的に」ファイルを渡すことで無事成功するようになりました。ビルドしたイメージの中にファイルは含まれていません。

➜ docker run -it camelot:latest ls -l config/master.key
ls: cannot access 'config/master.key': No such file or directory

ランタイム時に master.key を環境変数でコンテナに渡す: ローカルホストの場合

ビルドタイム時には「一時的に」渡すことができましたが、master.key がコンテナイメージに含まれていないため、ランタイム時は環境変数を使用して渡すことが必要になります。ローカルマシンであれば -e オプションが使えます。

➜ docker run -p 3000:3000 --name camelot-on-docker -e RAILS_MASTER_KEY='__THIS_IS_SECRET__' -it camelot:latest

ランタイム時に master.key を環境変数でコンテナに渡す: AWS AppRunner の場合

AWS AppRunner の場合は AWS Management Console から App Runner > Service > [Service name] > Configuration > Configure Service > Service Settings > Environment variables から設定可能です。master.key の内容を Plain text として貼り付けてもいいですが Secrets Manager や SSM Parameter Store 経由で設定するとより安全かもしれません。

参考情報

https://guides.rubyonrails.org から特定の Rails のバージョンに絞って検索する場合、site 検索が便利です。