Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

sbt-native-packagerでDockerイメージにファイルを追加する

sbt-native-packagerのDocker pluginでentrypoint.shを追加してイメージの実行を制御したかったがつまづいたので 解決方法をメモ。

sbt-native-packager

sbt-native-packagerとは、Scalaプロジェクトを様々なプラットフォームのネイティブな方式にパッケージしてくれるsbtプラグインで、例えばDockerやWindowsのMSI形式などにsbtアプリケーションをパッケージングしてくれる。

sbt-native-packager.readthedocs.io

自分が開発している解説動画生成ツールであるZMMでもこのsbt-native-packagerを使っていて、Docker pluginを使って色々な依存関係をまとめた形でDockerイメージとしてアプリケーションをリリースしている。

hub.docker.com

さて、今回はDockerでZMMを実行すると生成物のパーミッションがrootになってしまい困ってしまうというトラブルを解消するために、デフォルトのentrypoint.shではなく独自のentrypoint.shを使うことにした。このため、標準でsbt-native-packagerが追加するファイルに加えてentrypoint.shを追加し、これをDockerfileから呼び出す必要が出てきた。

雑に独自ファイルをイメージに取り込もうとしても失敗する

sbt-native-packagerではDockerイメージを構築する過程をbuild.sbtでカスタマイズすることが可能で、以下のようにDSLを記述することで独自のDockerfileを書き、独自のentrypoint.shを指定することができる:

lazy val root = (project in file("."))
.enablePlugins(JavaAppPackaging) // for DockerPlugin
.enablePlugins(DockerPlugin)
  /* snip */
.settings(
  dockerBaseImage := "amazoncorretto:17",
  dockerCommands ++= Seq(
    // coretto image does not have useradd utils
    ExecCmd("RUN", "yum", "install", "-y", "shadow-utils")
  ),
  dockerEntrypoint := Seq("/foo/bar/entrypoint.sh")
)

が、Docker pluginは自由にファイルを追加できない仕組みになっているようで、ExecCmd("COPY", "entrypoint.sh", "/entrypoint.sh")などと書いてもうまく見付けてもらえない。独自のContextでビルドしているようで、基本的に直接ファイル追加に手を入れることは想定していないようだ。

mappings機能を使ってentrypoint.shを取り込む

もともとsbt-native-packagerはDocker専用のツールではなく、MSIやdeb形式などにアプリケーションをパッケージする汎用プラグインを指向している。そのため、Dockerイメージにファイルを追加したい場合はmappingsという機能を使う。

Universal / mappings += file("entrypoint.sh") -> "entrypoint.sh"

という記述をbuild.sbtの設定に書き込むと、そのファイルはDockerfileに取り込まれるようになる(今回の例では、プロジェクトルートにあるentrypoint.sh/opt/docker/entrypoint.shとして取り込まれる)。Dockerの場合、ファイルは/opt/docker/に配置される(sbtのDocker/defaultLinuxInstallLocation の設定でカスタマイズ可能)。

本当はDocker / mappings += ...と書いても動くはずなのだが、うまく動作してくれなかったのでUniversal / mappingsと書いている。

entrypointを指定する

sbt-native-packagerのDocker pluginでは、エントリポイントはDockerfileではなくbuild.sbt上で設定する。

dockerEntrypoint := Seq("/opt/docker/entrypoint.sh")

前述した通り、mappingしたファイルはデフォルトで/opt/docker/に配置されるので、そのパスを指定してやればよい。

まとめ

sbt-native-packagerのDocker pluginで、任意のファイルをDockerイメージに取り込む方法を紹介した。ドキュメントにもちょっと書かれているが、プラグイン自体が汎用的で大きなプラグインなのでなかなかやり方が分からなかった。

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?