地方でリモートワーク

リモートワーク、プログラミング、エンジニア、地方

RailsのDockerイメージ作成時に環境変数やシークレットを安全に渡す方法

RailsのDockerイメージを作成する際に、環境変数やシークレットを安全に渡す方法について説明します。


皆さん、例えばconfig/initializers/omniauth.rbのようなファイルに、以下のように書いていることはありませんか?

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :google_oauth2,
    Rails.application.credentials.google[:client_id],
    Rails.application.credentials.google[:client_secret],
      {
      scope: "email, profile",
      prompt: "select_account",
      image_aspect_ratio: "square",
      image_size: 50
    }
end
OmniAuth.config.allowed_request_methods = %i[get]

初期化ファイルであるconfig/initializers以下のファイルでRails.application.credentialsを参照していることです。 このようなコードを書いていると本番環境用のDockerイメージを作成する際に、master.keyが設定されていないとasset:precompileの実行時にエラーが発生してしまいます。

RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
 > [build 6/6] RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile:
1.133 bin/rails aborted!
1.134 NoMethodError: undefined method `[]' for nil (NoMethodError)
1.134
1.134   provider :google_oauth2, Rails.application.credentials.google[:client_id]

config/initializers以下のファイルは、Railsの初期化時に読み込まれます。 しかしmaster.keyが設定されていないため、Rails.application.credentialsnilになってしまい、 []メソッドが呼び出せないためにエラーが発生してしまいます。

回避策としてはRails.application.credentials.google&.fetch[:client_id)のように nilだった場合にエラーにならないようにする方法もありますが、 あまり良い方法ではありません。 credentialsでgoogle > client_idを設定漏れがあった場合に気づかなくなってしまう恐れがあります。

そこで、Dockerイメージを作成する際に、環境変数やシークレットを安全に渡す方法を紹介します。

Docker の Build Secretsを利用する

Dockerには、Build SecretsというDocker Build時にシークレットを安全に渡すための機能があります。

https://docs.docker.com/build/building/secrets/

この仕組みを使うとmaster.keyや他の環境変数も安全に渡すことができます。

環境変数の場合

例えば環境変数にRAILS_MASTER_KEYを設定している場合は、docker build時に次のように指定しておきます。

$ docker build -t chat_gpt_rails --secret id="RAILS_MASTER_KEY" .

このように指定することで、Dockerfile内でRUN --mount=type=secret,id=RAILS_MASTER_KEYを使って、シークレットを安全に参照することができます。

例えばasset:precompileを実行する際に、以下のようにDockerfileを記述します。

RUN --mount=type=secret,id=RAILS_MASTER_KEY,env=RAILS_MASTER_KEY ./bin/rails assets:precompile

--mount=type=secret,id=RAILS_MASTER_KEY,env=RAILS_MASTER_KEYとすることで、Docker Build時に指定したシークレットを環境変数RAILS_MASTER_KEYとして参照できるようになります。

シークレットファイルの場合

また環境変数ではなく、シークレットファイルを指定する場合は、以下のようにbuild時にシークレットファイルを指定します。 以下はconfig/credentials/production.keyというシークレットファイルを指定する例です。

idをmaster_keyとしています。

docker build --secret id=master_key,src=./config/credentials/production.key .

そしてDockerfile内では以下のように記述します。 docker build時にidで指定したmaster_keyをtargetで指定したパスにマウントします。 こうすることで一時的に/rails/config/master.keyにホストのconfig/credentials/production.keyをマウント(作成)することができます。

RUN --mount=type=secret,id=master_key,target=/rails/config/master.key ./bin/rails assets:precompile

余談(Kamalの場合)

余談ですがRailsのデフォルトのコンテナデプロイツールである、Kamalの場合、次のように指定すると Kamalが自動的にRAILS_MASTER_KEYを設定してくれます。

builder:
  arch: amd64
  secrets:
    - RAILS_MASTER_KEY

デフォルトで作成されるDockerfileはRAILS_MASTER_KEYを参照するようになっていないので、 前述のとおり次のように書き換える必要があります。

RUN --mount=type=secret,id=RAILS_MASTER_KEY,env=RAILS_MASTER_KEY ./bin/rails assets:precompile

まとめ

DockerのBuild Secretsを利用することで、RailsのDockerイメージ作成時に環境変数やシークレットを安全に渡すことができます。 とはいえ、build時にたくさんのシークレットを渡すのも大変なので、 Railsの場合は環境変数ではなく、なるべくcredentialsを利用して、RAILS_MASTER_KEYだけを渡すようにすると楽できそうですね。