Rails の Docker Image を Cloud Native Buildpacks で作る
はじめに
プライベートで開発している Rails プロジェクトの Docker image を作りたかったのでやってみました。
普通に Dockerfile を書いて作っても良いのですが、ベストプラクティスを調べて維持し続けるのが面倒だったので以前から気になっていた Cloud Native Buildpacks を使ってみました。
Cloud Native Buildpacks
Cloud Native Buildpacks (CNB) を使うと Dockerfile を書くことなくソースコードから Docker image を作ることができます。
Heroku を使っている方は buildpacks という用語に馴染みがあると思いますが、CNB は heroku で作られたこの buildpacks の仕組みを標準化し、ベンダーロックインなしで使えるようにしたものです。
Docker image にビルドするための仕組みは builder, buildpacks としてコミュニティによってメンテナンスされているため、Dockerfile のベストプラクティスやセキュリティ対策を自分ひとりで追わなくても良いメリットがあります。
ちょっと前に Google Cloud も全体的に CNB を採用したってアナウンスしていましたね。
準備
pack
というコマンドをインストールする必要があります。
https://buildpacks.io/docs/tools/pack/ に従ってインストールするだけです。
早速ビルドしてみる
今回検証に使った Rails アプリについて
- Rails 6.1.3.2
-
Vite Ruby を使って React でフロントエンドを書いています
- 多分今回の検証に関しては特に Vite や Vite Ruby 特有の何かは無く、webpacker を使っているプロジェクトでも参考になるかと思います
ビルド
これでビルドできます。
$ pack build -e NODE_ENV=development --builder heroku/buildpacks:20 --buildpack heroku/nodejs,heroku/ruby app
heroku に push したときみたいにズラズラとログが流れ、最終的に Successfully built image app
と出れば成功です。
以下、少しオプションについて補足します。
builder, buildpacks について
pack builder suggest
と打つと以下のように出力されると思います。
$ pack builder suggest
Suggested builders:
Google: gcr.io/buildpacks/builder:v1 Ubuntu 18 base image with buildpacks for .NET, Go, Java, Node.js, and Python
Heroku: heroku/buildpacks:18 Base builder for Heroku-18 stack, based on ubuntu:18.04 base image
Heroku: heroku/buildpacks:20 Base builder for Heroku-20 stack, based on ubuntu:20.04 base image
Paketo Buildpacks: paketobuildpacks/builder:base Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, Ruby, NGINX and Procfile
Paketo Buildpacks: paketobuildpacks/builder:full Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, PHP, Ruby, Apache HTTPD, NGINX and Procfile
Paketo Buildpacks: paketobuildpacks/builder:tiny Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java Native Image and Go
Tip: Learn more about a specific builder with:
pack builder inspect <builder-image>
今回は自分が heroku に慣れているということもあり、多分共通の知識が役に立つんだろうな。。と予想して heroku/buildpacks:20
を選択しました。Paketo は Cloud Foundry (懐かしい)由来なんでしょうか。調べてなくてちょっとわからないです。
そして buildpacks ですが明示的に heroku/nodejs も指定してあげないとダメでした。heorku と同じように勝手に nodejs も入れてくれるわけではないんですね。
そして一つハマりどころがあって、ビルド時の環境変数として NODE_ENV=development
を指定しないと nodejs のビルド後に package.json の devDependencies が削除されてしまい asset:precompile 時に vite コマンドが見つからなくてビルドにこける問題がありました。
他にもビルド時に必要な環境変数があったらここで指定してください。
例えば vite 使ってるなら VITE_*
とか。
project.toml を使う
あまりこだわりがなければもう先程のコマンドで十分なのですが、README.md だったり .git だったり、本来実行時に不要なものも Docker image に入ってしまいます。
それらを取り除こうと思うと project.toml を使って exclude を指定する必要があるようなのでやってみました。
[project]
id = "dev.ebisawa.hoge"
name = "hoge"
version = "1.0.0"
[build]
exclude = [
".env*",
".foreman",
".bundle",
".git",
".github",
".vscode",
".byebug_history",
".DS_Store",
"node_modules",
"tmp",
]
[[build.buildpacks]]
uri = "heroku/nodejs"
[[build.buildpacks]]
uri = "heroku/ruby"
[[build.env]]
name = 'NODE_ENV'
value = 'development'
これを置いた状態で以下のようなコマンドでいけます。
$ pack build --builder heroku/buildpacks:20 app
動かす
普通に Docker image なので docker compose でも docker コマンドでも普通に動かせます。
環境変数とかはいい感じに調整してください。
$ docker run --rm -p 3000:3000 --env-file ./.env app
Procfile を置いておくと heroku/procfile buildpacks のおかげで自動的にその内容に応じた entrypoint を作ってくれて便利です。
Procfile が以下のようになってる場合
release: bin/rails db:migrate
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq
$ pack inspect app
Inspecting image: app
REMOTE:
(not present)
LOCAL:
Stack: heroku-20
Base Image:
Reference: xxxxxx
Top Layer: sha256:xxxxxx
Run Images:
heroku/pack:20
Buildpacks:
ID VERSION HOMEPAGE
heroku/nodejs-engine 0.7.3 -
heroku/nodejs-npm 0.4.3 -
heroku/nodejs-typescript 0.2.2 -
heroku/procfile 0.6.2 -
heroku/ruby 0.0.1 -
Processes:
TYPE SHELL COMMAND ARGS
web (default) bash bin/rails server -p ${PORT:-5000} -e $RAILS_ENV
console bash bin/rails console
rake bash bundle exec rake
release bash bin/rails db:migrate
worker bash bundle exec sidekiq
Processes に worker や release が定義されているのがわかると思います。
(web は上書きされちゃうみたいですね。)
$ docker run --rm -p 3000:3000 --env-file ./.env --entrypoint worker app
終わりに
あとは必要に応じて GitHub Actions あたりを使って CI/CD するようにすると良さそうです!
Discussion