Introduction
恥ずかしながら、今までマルチステージビルドを避けてきたが、実行リソースの削減を考える必要が出てきたので、本気出して取り組むことにした。
アプリのビルドに使う docker イメージは gcc や数多の開発バイナリが乗っているため、バカにならないイメージサイズになる。
それをマルチステージビルドにより、最初のステージングで生成したアプリのバイナリを後段のステージングにコピーし、必要な実行ランタイムのみをインストールすることで対処するのだが、さらにイメージサイズを削減できる手段があるとのこと。
それが DockerSlim (Slim、正式名称 SlimToolkit)。
昔は DockerSlim だったが、今は Slim とのこと。
ググラビリティが低すぎるので SlimToolkit が完全名らしい。
How work?
About Slim
公式 によると
のようになっており、下記の理解である。
- 対象のイメージを一時的なコンテナで起動
- ヒューリスティックに SSL 証明書やシェルを検出
- ファイルアクセス、syscall、証明書の利用状況から必要なファイルを選別 (動的解析)
- セキュリティの向上支援
- 不要なファイルを削除することによる、攻撃アクターの攻撃対象を物理的に削減
- Seccomp (Secure Computing mode) を使用しシステムコールの制限 (サンドボックス環境構築) の支援 (コンテナ実行時のオプション)
- AppArmor を使用し、プロセスによるファイルアクセス、ネットワーク、能力 (Capability) 制限の支援 (コンテナ実行時のオプション)
Try
実際に使ってみる。
まず Slim をインストールする。
1 | $ sudo -E mkdir -p /usr/local/bin |
続いて削減を行う Dockerfile を用意。
1 | FROM ubuntu:24.04 AS builder |
上記でビルドするアプリ。
1 |
|
仕様は下記
- 引数でコンテナ内の画像ファイルを 5 秒周期で読み取り、その画像の幅と高さを標準出力に書き込む
- 画像ファイル
/app/4.1.01.tiff、/app/4.2.07.tiffが存在
このアプリを含む Docker イメージをスリム化する。
1 | $ docker build -t docker-slim-exp-app . |
元の ubuntu:24.04 のサイズが 78.1 MB だったが、マルチステージビルドで必要なファイルのみを選別したとはいえ 164 MB まで膨れ上がった。
ちなみに、マルチステージビルドを使わなかった場合は 1.48 GB だった。
スリム化を実行する。
1 | $ slim build --continue-after 10 --http-probe=false -entrypoint "/app/Demo /app/4.2.07.tiff" --target docker-slim-exp-app |
削減が完了したのでサイズを確認する。
1 | $ docker images | grep docker-slim-exp-app |
元の ubuntu:24.04 の1/3 以下にまで削減できた。
続いて、このスリム化したイメージ docker-slim-exp-app.slim を動かしてみる。
1 | docker run --rm --name docker-slim-exp-app.slim -it docker-slim-exp-app.slim /app/Demo /app/4.2.07.tiff |
正しく動いている。
ここで、事前にコンテナイメージに含めておいた別の画像ファイルを指定してみる。
1 | $ docker run --rm --name docker-slim-exp-app.slim -it docker-slim-exp-app.slim /app/Demo /app/4.1.01.tiff |
消えてなくなっていることがわかる。
DockerSlim の基本動作である「不要なファイルの削除」が実勢されていることがわかる。
対処方法は DockerSlim での解析作業中に必要なファイルにアクセスさせることになるが、全ての動作パターンを網羅するのが厳しいということもあるので、事前にホワイトリストを作って対処もできる。
1 | $ slim build --continue-after 10 --http-probe=false -entrypoint "/app/Demo /app/4.2.07.tiff" --target docker-slim-exp-app --include-path /app/4.1.01.tiff |
無事に動いた。
ちなみにシェルも削除されているのでコンテナの中に入ることもできなくなっている。
1 | $ docker run --rm -it --name docker-slim-exp-app.slim docker-slim-exp-app.slim /bin/bash |
Security
Seccomp と AppArmor の適用に必要なプロファイルが生成されるので適用してみる。
1 | $ slim build --continue-after 10 --http-probe=false -entrypoint "/app/Demo /app/4.2.07.tiff" --target docker-slim-exp-app --copy-meta-artifacts $PWD/artifacts --remove-file-artifacts |
$PWD/artifacts に
- creport.json
- docker-slim-exp-app-apparmor-profile
- docker-slim-exp-app-seccomp.json
が出力されるので使ってみる。
Seccomp
1 | $ docker run --rm --name docker-slim-exp-app.slim --security-opt seccomp:artifacts/docker-slim-exp-app-seccomp.json -it docker-slim-exp-app.slim /app/Demo /app/4.2.07.tiff |
動かないので少し調べると Incorrect generated seccomp profile for ASP.NET Core app #182 にて fstatfs を Seccomp のプロファイルに追加すれば動くようになる、と。
artifacts/docker-slim-exp-app-seccomp.json を下記のように直す。
1 | "getrandom", |
再度コンテナを起動すると動くようになった。
ちなみに、存在しないファイルを指定するとエラーメッセージが変わる。
1 | $ docker run --rm --name docker-slim-exp-app.slim --security-opt seccomp:artifacts/docker-slim-exp-app-seccomp.json -it docker-slim-exp-app.slim /app/Demo /app/4.1.01.tiff |
AppArmor
まず、プロファイルを AppArmor に読ませる。
1 | $ sudo apparmor_parser -r -W $PWD/artifacts/docker-slim-exp-app-apparmor-profile |
コンテナを起動
1 | $ docker run --rm --name docker-slim-exp-app.slim --security-opt apparmor:docker-slim-exp-app-apparmor-profile -it docker-slim-exp-app.slim /app/Demo /app/4.2.07.tiff |
が、動かない。
AI に聞くと、「.so ファイル(共有ライブラリ)をメモリ上に展開(map)しようとしたが、権限がなくて失敗した」 とのことなので、プロファイルを修正して実行権限をつける。
docker-slim-exp-app-apparmor-profile を下記のように修正する。
1 | profile docker-slim-exp-app-apparmor-profile flags=(attach_disconnected,mediate_deleted) { |
これでコンテナが起動するようになった。
が、コンテナが終了できなくなった。
1 | $ docker stop docker-slim-exp-app.slim |
docker stop、docker kill もダメ。
デーモンを再起動しても意味なし。
直接プロセスを kill してもダメ。
そこでふと思った。SIGKILL すら防ぐのは AppArmor が邪魔をしているからでは?、と思い。
適用したプロファイルをアンロードしてみた。
1 | $ sudo apparmor_parser -R $PWD/artifacts/docker-slim-exp-app-apparmor-profile |
プロファイルがないことを確認後、docker stop すると停止が確認できた。
肝が冷えた。
Option
DockerSlim にはたくさんのオプションがあるが、自分が使ったのは下記
| option | description |
|---|---|
| –continue-after <sec> | DockerSlim で解析中、何秒後に自動終了するか。上記のコンソールのように終了処理を与えられない場合に使用。 |
| –http-probe=<true/false> | HTTP プロービングを使用するかどうか。DockerSlim の解析中に外部からの HTTP リクエストで生存確認などに使用。今回のコンソールアプリでは false にしないと解析エラーになる |
| -entrypoint <command> | DockerSlim での解析中に実行するエントリポイント。Dockerfile に記載した ENTRYPOINTP を上書きできる。この指定を使ってアプリに動作を行わせるので、アプリを完全動作させる自指示を与えること。 |
| –target <image name> | スリム化する DockerSlim の解析対象となる Docker イメージ |
| –include-path </path/to/file> | スリム化から除外するファイル/フォルダへのパス。複数指定可能。 例: --include-path /path/to/a.png --include-path /path/to/b.png |
| –copy-meta-artifacts </path/to/output/dir> | Seccomp や AppArmor のプロファイルを含むメタデータの出力先。通常は `/tmp/mint-state/.mint-state/images/<英数字64文字> に出力される |
| –remove-file-artifacts | Seccomp や AppArmor のプロファイル、Docker イメージを含むメタデータの削除。通常は `/tmp/mint-state/.mint-state/images/<英数字64文字> に出力されるがこれを削除する |
まとめ
イメージサイズを確実に削減でき、強力なセキュリティ機能の実行も支援でききるので今後も使っていきたい。
ところで、DockerSlim があれば、マルチステージビルドは不要か?、というとそれは間違いで、
- マルチステージビルドは: 「これが必要である」という確実な事実に基づき、必要なものだけを丁寧に選別して積み上げる (静的な保証)
- DockerSlim は「動いている最中にこれを使った」という観察結果に基づき、残りを削る )動的な最適化)
であり、似てはいるが役割が違う。
また、DockerSlim はイメージを作った後に「さらに解析して作り直す」という工程を追加するため、ビルド時間が非常に長くなるので、元のイメージサイズが小さいに越したことはない。
これは CI/CD の効率化に直結する。

