はじめに
システム開発推進部の星野です。
最近、人にDockerイメージの説明をする機会があったのですが、「ECRにあるやつです」みたいなをしてしまいました。 「イメージのことをちゃんと理解できてないのかな…」と不安になったので、この際じっくり調べてみようと思いました。
そこで今回は、私が何気なくイメージと呼んでいるものを再定義していきます。
なお、本来イメージをちゃんと理解するにはイメージのビルドについての言及が必須だと思いますが、書くことが多すぎて今回はパスしてます。すみません…
イメージとは
まずは、イメージというものの定義を確認しましょう。
Docker-docs-ja(v24.0)におけるイメージの説明は以下の通りです。
Docker イメージは コンテナ の基礎(土台)です。イメージとは、ルートファイルシステムに対する変更と、コンテナ実行時に使う実行パラメータに相当するものを並べ集めたものです。一般的にイメージには、ファイルシステムをレイヤー化した集合が、お互いに積み重なって入っています。イメージは状態を保持せず、変更もできません。
https://docs.docker.jp/glossary.html#image
Dockerはコンテナを立てるための基礎になるもの、というのは普段Dockerを使っていればそこまで難しくないと思います。
一般的にイメージには、ファイルシステムをレイヤー化した集合が、お互いに積み重なって入っています。
ここはちょっと難しい気もしますが、おそらくはレイヤ(イメージレイヤ)の話でしょう。
イメージ内部において、イメージに対する変更箇所がレイヤであり、つまり Dockerfile 内における命令を意味します。ベースイメージから最終的なイメージを作成するまで、レイヤは順番に重なります。イメージの更新や再構築をする場合には、更新が必要なレイヤのみを変更し、ローカルでキャッシュ済みのレイヤは変更しません。これが Docker イメージはなぜ高速かつ軽量なのかという理由の1つです。各レイヤの容量の合計が、最終的なイメージの容量と同じです。
https://docs.docker.jp/glossary.html#layter
すごくざっくり言ってしまえば、 「イメージ=(Dockerfile内にある)命令の集合」という風に言い換えられそうです。
今回はこれを狭義のイメージとして考えていきます。
試しに実例を見てみる
さて、まずは実例を見てみます。
docker imagesで手元にあるイメージの一覧を確認できます。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql latest 7ce93a845a8a 4 weeks ago 586MB
mysql 8 11a5e588a69b 4 weeks ago 591MB
mysql 8.4.2 233a484acc79 4 weeks ago 583MB
mysql 5.7.44 5107333e08a8 8 months ago 501MB
...
出力によると、リポジトリ(
REPOSITORY)というところと、タグ(TAG)というところがありますね。
このあたりがイメージのデータ構造にかかわりがありそうです。
リポジトリ(Repository)
リポジトリについても、Docker-docs-ja(v24.0)に説明があります。
リポジトリとは Docker イメージの集まりです。リポジトリは レジストリ サーバに送信すると、共有されるようにできます。リポジトリの中では、イメージの違いを タグ でラベル付けします。
https://docs.docker.jp/glossary.html#repository
先の例だと、
mysql がリポジトリということですね。
タグ(Tag)
Docker-docs-ja(v24.0)におけるタグの説明は以下の通りです。
タグ(tag)は リポジトリ 上の Docker イメージに割り当てるラベルです。タグを使い、リポジトリ上のイメージを互いに識別します。
たとえばmysqlだと
latestや8などがタグに当たります。
docker image ls REPOSITORYコマンドで、手元にあるリポジトリ配下のイメージの一覧を確認できます。
$ docker image ls mysql
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql latest 7ce93a845a8a 4 weeks ago 586MB
mysql 8 11a5e588a69b 4 weeks ago 591MB
mysql 8.4.2 233a484acc79 4 weeks ago 583MB
mysql 5.7.44 5107333e08a8 8 months ago 501MB
先ほどのタグの説明をそのまま受け取ると、イメージはリポジトリとタグの組に対して一意に定まりそうです(下図)。

注意: 実際には複数のタグが同一のイメージを指すことがありますが、簡単のため今回は省略しています。
しかし、DockerHubを見ると、実際にはタグの配下にまだ何かありますね。

https://hub.docker.com/_/mysql/tags?page=&page_size=&ordering=&name=latest
ここを見ると、 OS/ARCH部分がさらに下にあるようです。
OS/アーキテクチャ
Docker-docs-ja(v24.0)の用語集には明記されていませんが、リポジトリ、タグの下にOS/アーキテクチャという階層もあるようです。
実際、先ほどのタグを選択した画面からさらにOS/ARCHを選択するとレイヤが表示されます。

https://hub.docker.com/layers/library/mysql/latest/images/sha256-e5ab70d6583fab7597870a1c3599059be44648649d59b0e75e50726ab4c42bb6
docker manifest inspect REPOSITORY[:TAG]コマンドでリポジトリ、タグの配下に存在する(狭義の)イメージ一覧を取得できます。
$ docker manifest inspect mysql:8
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 2855,
"digest": "sha256:12f800accdf4aacff75dc1b00a41b17be628240e9f6f8cb355a185df4f86e151",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 842,
"digest": "sha256:9119bbe565c193c2f5f0a37803a29f1bb39efd5002d34c740bfe0e9f2e5dce47",
"platform": {
"architecture": "unknown",
"os": "unknown"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 2857,
"digest": "sha256:19b3ba2b959ec564bc25d09b2a04a2cb1558be581ba3d0b31d7515d6727efdb1",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 842,
"digest": "sha256:7557e146df06089e393affb7c2a79cc4d50d551521238535e645d2ccf57069a7",
"platform": {
"architecture": "unknown",
"os": "unknown"
}
}
]
}
参考: docker manifest inspectのリファレンス
ここまでのまとめ: リポジトリ、タグとイメージの関係
つまり、 (狭義の)イメージはリポジトリ、タグ、アーキテクチャの組に対して一意に定まるということですね。

注意: 実際には複数のタグが同一のイメージを指すことがありますが、簡単のため今回は省略しています。
ちなみに、普段私たちはOS/アーキテクチャの層を意識せずに利用していますが、それはDockerがホストマシンのOS/アーキテクチャを自動検出してホストマシンに合わせたイメージをpullしてきているからです。
注意: 私は公式ドキュメント内でこちらに関する明確な言及を見つけられませんでしたが、ほぼ同様の動作をするDockerfileのFROM句の公式ドキュメントにはこの仕様に関する言及があります。
逆に、
docker pull REPOSITORY[:TAG]コマンドを利用する際に --platformオプションをつけると、ほかのOS/アーキテクチャ用の(狭義の)イメージをpullすることもできます。
例えば私の実行環境は
linux/amd64ですが、linux/arm64用のイメージを利用することもできます。
$ docker pull --platform=linux/arm64 mysql:8
8: Pulling from library/mysql
86a1ed2ecedf: Pull complete
cf88b6547cb5: Pull complete
906b5914950d: Pull complete
c617af9dc74d: Pull complete
4e52819b0ae2: Pull complete
235f6a16f543: Pull complete
0c6aaf631f1d: Pull complete
17b83eb9ad50: Pull complete
1b971475b2b0: Pull complete
6fa369cdb9f9: Pull complete
Digest: sha256:ad77a7c4e2031597e0c73a21993f780cdde6cef15d3dae734fe550c6142f8097
Status: Downloaded newer image for mysql:8
docker.io/library/mysql:8
イメージの構造
次に、イメージの中身を実際に見てみましょう。
docker save REPOSITORY[:TAG]コマンドを使ってmysql:8のイメージを圧縮して手元に保存してみます。
$ docker save mysql:8 > mysql_image.tar
mysql_image.tarを解凍してみると、次のような構造になっていました。
$ tree ./mysql_image
./mysql_image
├── blobs
│ └── sha256
│ ├── 02001e0346e9a229eefb6c3e55c5bcab61b92aaf16aa97339a1a338f9477dc98
│ ├── 0e52e009f856962619ce7deaa5d542e0b2f6ea935b66c48b7ddd73f7ff71fd48
│ ├── ...
│ └── feae6ca17b8320ad46a21c5ab2c506da8c6988425ecc7cd2d9698d68c50faf4a
├── index.json
├── manifest.json
├── oci-layout
└── repositories
以下でイメージの中身を構成する主要なファイルを見てみましょう。
index.json
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:f00baeb2a684f4dbc5b62d1260119ab5a3fc1af6665295d69d368bbe8143bf2c",
"size": 1772,
"annotations": {
"io.containerd.image.name": "docker.io/library/mysql:8",
"org.opencontainers.image.ref.name": "8"
}
}
]
}
index.jsonは mediaTypeに書いてある通り、OCI(Open Container Initiative)の定義するOCI Image Indexに準拠しています。
詳細は省略しますが、ざっくり存在意義としてはイメージの特定のマニフェストを記載するためのもののようですね。
The image index is a higher-level manifest which points to specific image manifests, ideal for one or more platforms.
https://github.com/opencontainers/image-spec/blob/main/image-index.md
manifest.json
[
{
"Config": "blobs/sha256/750b67184e7aeeb9d1bf9279cd311502a17a2d9fa230c77b332d3b51655209f3",
"RepoTags": [
"mysql:8"
],
"Layers": [
"blobs/sha256/3cf436755aff907c2c6a6fd9202eab0da03f0b331f8d1363078a6534d95608a0",
"blobs/sha256/af218cb835a113364f9671e7a7f2ae9464d5966969806439fba65915c14ea948",
...
"blobs/sha256/872d473479037c02b3a4ffedc3b9ea2390671bc20eab15931fba3b89a61c1755"
],
"LayerSources": {
"sha256:1587dbb790fd9eedd2d291e098637c079353c3a644a2f0264bb7ed8417f4f87c": {
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"size": 3072,
"digest": "sha256:1587dbb790fd9eedd2d291e098637c079353c3a644a2f0264bb7ed8417f4f87c"
},
"sha256:22fd6e8c52d22ef5cf9da25ac68f8e04df7ee16c0704438bb1d04dabc54b113b": {
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"size": 3072,
"digest": "sha256:22fd6e8c52d22ef5cf9da25ac68f8e04df7ee16c0704438bb1d04dabc54b113b"
},
"sha256:f2486667f3fae81fbd1b06a7ded627fd370d396bad1a1ecfaa613c2f3f41d98d": {
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"size": 325037056,
"digest": "sha256:f2486667f3fae81fbd1b06a7ded627fd370d396bad1a1ecfaa613c2f3f41d98d"
}
}
}
]
manifest.jsonはOCI Manifest Specificationに準拠していないようですが、大まかな構成は大体同じようですね。
Layersにはblob配下のファイルパスが羅列されていて、これがイメージレイヤの実行順序を定義しています。
LayerSourcesはOCI Manifest Specificationでいうところのlayersに当たる部分で、各レイヤのサイズなどが管理されています。
blob/sha256/{DIGEST}
blob/sha256配下には実際のイメージレイヤがバイナリで格納されています。
ただし、(
manifest.json内のConfigで指定されているような)一部設定ファイルなどもここに格納されていました。
まとめ
普段何気なく使っているDockerのイメージをちゃんと調べてみました。
- イメージは「レイヤ(命令)の集合」
- イメージはリポジトリ、タグ、OS/アーキテクチャの組に対して一意
- イメージはメタデータ(index.json/manifest.json)とレイヤから構成される
マニフェストやOCIのような内容はあまり知らなかったので個人的には勉強になりました。 OCIに準拠している他のツールの仕様も見てみたいなと思います。
こうやって何となく知っているものを腰を据えて公式ドキュメントを読み漁ったりするのも中々楽しい取り組みですね。
参考文献
星野竜輝