はじめに
Dockerfileを書くことなく、ソースコードからDockerイメージを作ることができると言うCloud Native Buildpacksという存在を知りました。
buildpacks.io
Dockerfileを色々と調べながら書くのはあんまり好きじゃなく、できれば楽できたらなと思ったところに知ったものなので、早速試してみました。
pack
コマンドのインストール
まず、pack
コマンドのインストールを行います。やり方は公式サイトにドキュメントがあるので、それを参考に。
buildpacks.io
私が試した時には、0.26.0
というバージョンでした。
azureuser@myVM:~$ pack --version
0.26.0
azureuser@myVM:~$
また、Docker環境も整えておきます。
docs.docker.com
Javaのサンプルアプリを動かす
チュートリアルが用意されているので、それに従ってやってみます。
buildpacks.io
サンプルリポジトリをcloneして、pack
コマンドを実行します。
azureuser@myVM:~/work/samples/apps/java-maven$ pack build myapp --builder cnbs/sample-builder:bionic
bionic: Pulling from cnbs/sample-builder
(省略)
===> ANALYZING
===> DETECTING
[detector] samples/java-maven 0.0.1
===> RESTORING
[restorer] Restoring metadata for "samples/java-maven:jdk" from app image
[restorer] Restoring metadata for "samples/java-maven:maven_m2" from cache
[restorer] Restoring data for "samples/java-maven:jdk" from cache
[restorer] Restoring data for "samples/java-maven:maven_m2" from cache
===> BUILDING
[builder] ---> Java buildpack
[builder] ---> Installing JDK
[builder] ---> Running Maven Wrapper
[builder] [INFO] Scanning for projects...
[builder] [INFO]
[builder] [INFO] --------------------< io.buildpacks.example:sample >--------------------
[builder] [INFO] Building sample 0.0.1-SNAPSHOT
[builder] [INFO] --------------------------------[ jar ]---------------------------------
[builder] [INFO]
(省略)
[builder] [INFO] ------------------------------------------------------------------------
[builder] [INFO] BUILD SUCCESS
[builder] [INFO] ------------------------------------------------------------------------
[builder] [INFO] Total time: 2.827 s
[builder] [INFO] Finished at: 2022-05-29T08:05:21Z
[builder] [INFO] ------------------------------------------------------------------------
===> EXPORTING
[exporter] Reusing layer 'samples/java-maven:jdk'
[exporter] Adding 1/1 app layer(s)
[exporter] Reusing layer 'launcher'
[exporter] Reusing layer 'config'
[exporter] Reusing layer 'process-types'
[exporter] Adding label 'io.buildpacks.lifecycle.metadata'
[exporter] Adding label 'io.buildpacks.build.metadata'
[exporter] Adding label 'io.buildpacks.project.metadata'
[exporter] Setting default process type 'web'
[exporter] Saving myapp...
[exporter] *** Images (d8a7e88bd822):
[exporter] myapp
[exporter] Reusing cache layer 'samples/java-maven:jdk'
[exporter] Adding cache layer 'samples/java-maven:maven_m2'
Successfully built image myapp
azureuser@myVM:~/work/samples/apps/java-maven$
Mavenによるビルドが実行され、Dockerイメージが作成されました。
azureuser@myVM:~/work/samples/apps/java-maven$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
heroku/pack 20 187818685cd8 8 hours ago 602MB
cnbs/sample-stack-run bionic 0aa0fb9222a5 5 months ago 70.2MB
hello-world latest feb5d9fea6a5 8 months ago 13.3kB
buildpacksio/lifecycle 0.13.1 76412e6be4e1 42 years ago 16.4MB
heroku/buildpacks 20 51904c87109f 42 years ago 1.56GB
cnbs/sample-builder bionic 83509780fa67 42 years ago 181MB
myapp latest d8a7e88bd822 42 years ago 300MB ## ← 作られたDockerイメージ
azureuser@myVM:~/work/samples/apps/java-maven$
アプリを動かしてみます。
azureuser@myVM:~/work/samples/apps/java-maven$ docker run --rm -p 8080:8080 myapp
|'-_ _-'| ____ _ _ _ _ _
| | | | _ \ (_)| | | | | | (_)
'-_|_-' | |_) | _ _ _ | | __| | _ __ __ _ ___ | | __ ___ _ ___
|'-_ _-'|'-_ _-'| | _ < | | | || || | / _` || '_ \ / _` | / __|| |/ // __| | | / _ \
| | | | | | |_) || |_| || || || (_| || |_) || (_| || (__ | < \__ \ _ | || (_) |
'-_|_-' '-_|_-' |____/ \__,_||_||_| \__,_|| .__/ \__,_| \___||_|\_\|___/(_)|_| \___/
| |
|_|
:: Built with Spring Boot :: 2.1.18.RELEASE
2022-05-29 08:09:28.225 INFO 1 --- [ main] i.b.example.sample.SampleApplication : Starting SampleApplication v0.0.1-SNAPSHOT on c6e7f160ad2c with PID 1 (/workspace/target/sample-0.0.1-SNAPSHOT.jar started by cnb in /workspace)
2022-05-29 08:09:28.229 INFO 1 --- [ main] i.b.example.sample.SampleApplication : No active profile set, falling back to default profiles: default
2022-05-29 08:09:29.912 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-05-29 08:09:29.975 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-05-29 08:09:29.976 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.39]
2022-05-29 08:09:30.096 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-05-29 08:09:30.097 INFO 1 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1766 ms
2022-05-29 08:09:30.377 INFO 1 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2022-05-29 08:09:30.463 INFO 1 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index
2022-05-29 08:09:30.635 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-05-29 08:09:30.639 INFO 1 --- [ main] i.b.example.sample.SampleApplication : Started SampleApplication in 3.089 seconds (JVM running for 3.708)
ブラウザでアクセスしてみると、以下のような画面が出てきました。

Rubyのサンプルアプリを試す
サンプルリポジトリにはruby-bundler
というSinatraで作られたアプリがあるので、これを試してみます。
Javaの時と同じようにpackコマンドを使ってDockerイメージを作成します。
azureuser@myVM:~/work/samples/apps/ruby-bundler$ pack build myapp-ruby --builder cnbs/sample-builder:bionic
bionic: Pulling from cnbs/sample-builder
Digest: sha256:f9aa933450900df3a91a5f405c4a55ed3bb9decc9ee503b2dd85ff728db75e22
Status: Image is up to date for cnbs/sample-builder:bionic
bionic: Pulling from cnbs/sample-stack-run
Digest: sha256:7b0946aa031f1ddd30bda8963eb33431458a25a576208646348ce89536fef59a
Status: Image is up to date for cnbs/sample-stack-run:bionic
0.13.1: Pulling from buildpacksio/lifecycle
Digest: sha256:f2cb410c8e204e6d2c6d86b1a171862595d8dccefc35197804eff09ab9221219
Status: Image is up to date for buildpacksio/lifecycle:0.13.1
===> ANALYZING
===> DETECTING
[detector] samples/ruby-bundler 0.0.1
===> RESTORING
[restorer] Restoring metadata for "samples/ruby-bundler:bundler" from app image
[restorer] Restoring metadata for "samples/ruby-bundler:ruby" from app image
[restorer] Restoring data for "samples/ruby-bundler:bundler" from cache
===> BUILDING
[builder] ---> Ruby Buildpack
[builder] ---> Downloading and extracting Ruby 2.5.0
[builder] ---> Installing bundler
[builder] Successfully installed bundler-2.3.14
[builder] 1 gem installed
[builder] ---> Reusing gems
[builder] Your RubyGems version (2.7.6.2) has a bug that prevents `required_ruby_version` from working for Bundler. Any scripts that use `gem install bundler` will break as soon as Bundler drops support for your Ruby version. Please upgrade RubyGems to avoid future breakage and silence this warning by running `gem update --system 3.2.3`
[builder] Your RubyGems version (2.7.6.2) has a bug that prevents `required_ruby_version` from working for Bundler. Any scripts that use `gem install bundler` will break as soon as Bundler drops support for your Ruby version. Please upgrade RubyGems to avoid future breakage and silence this warning by running `gem update --system 3.2.3`
===> EXPORTING
[exporter] Reusing layer 'samples/ruby-bundler:bundler'
[exporter] Reusing layer 'samples/ruby-bundler:ruby'
[exporter] Adding 1/1 app layer(s)
[exporter] Reusing layer 'launcher'
[exporter] Reusing layer 'config'
[exporter] Reusing layer 'process-types'
[exporter] Adding label 'io.buildpacks.lifecycle.metadata'
[exporter] Adding label 'io.buildpacks.build.metadata'
[exporter] Adding label 'io.buildpacks.project.metadata'
[exporter] Setting default process type 'web'
[exporter] Saving myapp-ruby...
[exporter] *** Images (360bd2e06828):
[exporter] myapp-ruby
[exporter] Reusing cache layer 'samples/ruby-bundler:bundler'
Successfully built image myapp-ruby
azureuser@myVM:~/work/samples/apps/ruby-bundler$
docker run
コマンドを使って動かします。
azureuser@myVM:~/work/samples/apps/ruby-bundler$ docker run --rm -p 8080:8080 myapp-ruby
Hello from .profile
Your RubyGems version (2.7.6.2) has a bug that prevents `required_ruby_version` from working for Bundler. Any scripts that use `gem install bundler` will break as soon as Bundler drops support for your Ruby version. Please upgrade RubyGems to avoid future breakage and silence this warning by running `gem update --system 3.2.3`
[2022-05-29 08:18:51] INFO WEBrick 1.4.2
[2022-05-29 08:18:51] INFO ruby 2.5.0 (2017-12-25) [x86_64-linux]
== Sinatra (v2.0.7) has taken the stage on 8080 for development with backup from WEBrick
[2022-05-29 08:18:51] INFO WEBrick::HTTPServer#start: pid=1 port=8080
ブラウザでアクセスしてみます。大変簡素な画面ですが、無事動きました。

Rubyサンプルアプリのバージョンアップを行う
サンプルアプリのRubyのバージョンは2.5だったので最新バージョンである3.1.2で動かすことにします。
最初は.ruby-version
を変更すれば良いだけでしょと思っていたのですが、それだけではうまく動きませんでした。
まずはローカルで動かす
まずはローカルで動かしてみます。ruby app.rb
を実行すると、以下のようなメッセージが出てうまく動きません。
azureuser@myVM:~/work/samples/apps/ruby-builder3$ ruby app.rb
/home/azureuser/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.3.1/lib/rack/handler.rb:45:in `pick': Couldn't find handler for: thin, puma, reel, HTTP, webrick. (LoadError)
from /home/azureuser/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-2.2.0/lib/sinatra/base.rb:1503:in `run!'
from /home/azureuser/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-2.2.0/lib/sinatra/main.rb:45:in `block in <module:Sinatra>'
azureuser@myVM:~/work/samples/apps/ruby-builder3$
これはwebrick
が最近のRubyでは標準ではインストールされないために起こる問題です。gem "webrick"
をGemfileに追加してbundle install
します。また、bundle update --bundler
やbundle update
を実行して、大量に出てくるdeprecatedを消しておきます。これでローカルで動かすことができます。
azureuser@myVM:~/work/samples/apps/ruby-builder3$ ruby app.rb
[2022-05-29 08:37:43] INFO WEBrick 1.7.0
[2022-05-29 08:37:43] INFO ruby 3.1.2 (2022-04-12) [x86_64-linux]
== Sinatra (v2.2.0) has taken the stage on 8080 for development with backup from WEBrick
[2022-05-29 08:37:43] INFO WEBrick::HTTPServer#start: pid=34563 port=8080
Dockerイメージを作成する
続いてpackコマンドでDockerイメージを作成してみます。これまでと同じようにイメージ名だけ変えて動かしてみたら以下のメッセージが出てDockerイメージは作られませんでした。
azureuser@myVM:~/work/samples/apps/ruby-builder3$ pack build myapp-ruby3 --builder cnbs/sample-builder:bionic
bionic: Pulling from cnbs/sample-builder
(省略)
Status: Image is up to date for buildpacksio/lifecycle:0.13.1
===> ANALYZING
[analyzer] Previous image with name "myapp-ruby3" not found
===> DETECTING
[detector] samples/ruby-bundler 0.0.1
===> RESTORING
===> BUILDING
[builder] ---> Ruby Buildpack
[builder] ---> Downloading and extracting Ruby 3.1.2
[builder] ---> Installing bundler
[builder] <internal:/layers/samples_ruby-bundler/ruby/lib/ruby/3.1.0/rubygems/core_ext/kernel_require.rb>:85:in `require': libyaml-0.so.2: cannot open shared object file: No such file or directory - /layers/samples_ruby-bundler/ruby/lib/ruby/3.1.0/x86_64-linux/psych.so (LoadError)
(省略)
[builder] ERROR: failed to build: exit status 1
ERROR: failed to build: executing lifecycle. This may be the result of using an untrusted builder: failed with status code: 51
azureuser@myVM:~/work/samples/apps/ruby-builder3$
これを解決するには--builder
で指定した値を変えます。どの値を指定していいかわかりませんでしたが、以下のマニュアルにあるようにpack builder suggest
コマンドで提示されたものを指定してみます。
buildpacks.io
今回は、heroku/buildpacks:20
を指定してみることにします。
azureuser@myVM:~/work/samples/apps/ruby-builder3$ pack build myapp-ruby3 --builder heroku/buildpacks:20
20: Pulling from heroku/buildpacks
(省略)
Status: Image is up to date for heroku/pack:20
===> ANALYZING
Previous image with name "myapp-ruby3" not found
===> DETECTING
1 of 2 buildpacks participating
heroku/ruby 0.1.3
===> RESTORING
===> BUILDING
-----> Installing bundler 2.2.21
-----> Removing BUNDLED WITH version in the Gemfile.lock
-----> Compiling Ruby/Rack
-----> Using Ruby version: ruby-2.7.4
-----> Loading Bundler Cache
-----> Installing dependencies using bundler 2.2.21
Running: BUNDLE_WITHOUT='development:test' BUNDLE_PATH=/layers/heroku_ruby/gems/vendor/bundle BUNDLE_BIN=vendor/bundle/bin BUNDLE_DEPLOYMENT=1 bundle install -j4
Fetching gem metadata from https://rubygems.org/
Fetching gem metadata from https://rubygems.org/....
(省略)
-----> Detecting rake tasks
###### WARNING:
You have not declared a Ruby version in your Gemfile.
To declare a Ruby version add this line to your Gemfile:
```
ruby "2.7.4"
```
For more information see:
https://devcenter.heroku.com/articles/ruby-versions
===> EXPORTING
(省略)
Saving myapp-ruby3...
*** Images (163a27f2364c):
myapp-ruby3
Adding cache layer 'heroku/ruby:gems'
Successfully built image myapp-ruby3
今度はうまくDockerイメージが作られましたが、Rubyのバージョンが2.7.4になっています。ログの警告にあるようにGemfile
にRubyのバージョンが書かれていないのが原因のようです。このため、Gemfile内にruby "3.1.2"
と書いておきます。bundle update
もして再度pack build
してみます。
azureuser@myVM:~/work/samples/apps/ruby-builder3$ pack build myapp-ruby3 --builder heroku/buildpacks:20
20: Pulling from heroku/buildpacks
(省略)
===> BUILDING
-----> Installing bundler 2.2.21
-----> Removing BUNDLED WITH version in the Gemfile.lock
-----> Compiling Ruby/Rack
-----> Using Ruby version: ruby-3.1.2
-----> Loading Bundler Cache
-----> Installing dependencies using bundler 2.2.21
Running: BUNDLE_WITHOUT='development:test' BUNDLE_PATH=/layers/heroku_ruby/gems/vendor/bundle BUNDLE_BIN=vendor/bundle/bin BUNDLE_DEPLOYMENT=1 bundle install -j4
(省略)
Successfully built image myapp-ruby3
azureuser@myVM:~/work/samples/apps/ruby-builder3$
無事イメージが作られました。
あとはdocker run
すればいいだけと思っていたのですが、まだ動きません。
azureuser@myVM:~/work/samples/apps/ruby-builder3$ docker run --rm -p 8080:8080 myapp-ruby3
Hello from .profile
configuration /workspace/config.ru not found
azureuser@myVM:~/work/samples/apps/ruby-builder3$
動かすためには、Procfile
を作成します。Procfile
については以下を参照。
devcenter.heroku.com
今回は次のような内容にします。
web: ruby app.rb
Procfile
を作成後、再度pack build
を実行し、docker run
すると無事動かすことができました。
azureuser@myVM:~/work/samples/apps/ruby-builder3$ docker run --rm -p 8080:8080 myapp-ruby3
Hello from .profile
[2022-05-29 08:57:06] INFO WEBrick 1.7.0
[2022-05-29 08:57:06] INFO ruby 3.1.2 (2022-04-12) [x86_64-linux]
== Sinatra (v2.2.0) has taken the stage on 8080 for production with backup from WEBrick
[2022-05-29 08:57:06] INFO WEBrick::HTTPServer#start: pid=1 port=8080