一覧CL-10-1 · コンテナイメージと実行

Dockerイメージのレイヤーとタグ

ビルドへ含めるファイル、層の差分、タグと内容識別子の関係を、実際のイメージとアーカイブから説明します。

説明

コンテナイメージは、ファイルシステムの層と、起動コマンドや環境変数などの設定で構成されます。同じタグを後から別のイメージへ付け替えられる場合があるため、内容を固定するときは、digestが何を識別する値かを確認します。

Alpine 3.22を元に、versionを表示するシェルを組み込むDockerfileを使います。ビルドしたイメージを実行し、保存したOCI形式のmanifest・config・層を解析します。その後、シェルだけを変更して再ビルドし、変わった層とタグの付替えを確認します。

ビルドへ含めるファイル、層の差分、タグと内容識別子の関係を、実際のイメージとアーカイブから説明します。

1Dockerfileとビルドコンテキスト

contextがビルドへ渡すディレクトリです。COPYはapp/を/app/へ入れ、.dockerignoreはapp/notes.txtを除外します。

cat context/Dockerfileベース・コピー・環境・起動設定を読む cat context/.dockerignoreビルド対象外のファイルを読む cat context/app/app.sh組み込むアプリの内容を読む

起動コマンドは/app/app.sh、環境変数APP_MODEはdemoです。EXPOSE 8080はポートのメタデータで、ここではアプリをそのポートで待ち受けさせたり、ホストへ公開したりしてはいません。

2版1をビルドして実行

用意したファイルからv1をビルドします。SOURCE_DATE_EPOCH=0は、生成するイメージの作成時刻を1970年1月1日00:00:00 UTCにそろえる指定です。

docker build --network=none --pull=false --provenance=false --build-arg SOURCE_DATE_EPOCH=0試料の作成時刻を固定する -q -t lbox-cl10:v1 contextcontextを版1のタグでビルドする docker run --rm --network none lbox-cl10:v1版1から起動して終了後のコンテナを除く

ビルドの内容識別子に続いてversion=one mode=demoが表示されます。イメージの作成と、そのイメージからプロセスを起動した結果を続けて確認しました。

3起動設定とイメージレイヤーを取得する

イメージの環境変数、起動コマンド、ルートファイルシステムを構成する層を表示します。

docker image inspect lbox-cl10:v1版1の設定と層を取得する --format '{{json .Config.Env}} {{json .Config.Entrypoint}} {{json .RootFS.Layers}}展開後の層のdiff IDを表示する'

APP_MODE=demoと/app/app.shが設定に含まれています。RootFS.Layersの各値は、圧縮前の層のtarデータから求めたSHA-256(diff ID)です。配布用に圧縮したデータのハッシュ値とは対象が異なります。

4保存したOCIデータをバイト列で照合する

イメージをtarに保存します。OCIはコンテナイメージの形式などを定める仕様です。このアーカイブではindexがmanifestを指し、manifestが設定ファイルconfigと各層のデータを指します。archive.pyで参照先のSHA-256を再計算します。

docker image save lbox-cl10:v1 -o image.tar版1をローカルアーカイブへ保存する python3 archive.py各descriptorと保存バイト列のhashを検査する

manifestには層と設定への参照、configには起動設定とdiff IDが入っています。blobはハッシュ値で識別されるデータ本体です。出力のblobとdiff_idの違いは、圧縮前後でバイト列が変わるためです。この環境のimage inspectのIdはmanifestのdigestと一致しています。

5除外したメモが入っていないことを確認

起動コマンドをshへ替え、/appのファイル名を一覧表示します。

docker run --rm --network none --entrypoint sh起動するプログラムを検査用のshへ替える lbox-cl10:v1 -c 'ls -1 /app'イメージ内のアプリファイルを一覧表示する

app.shだけがあり、notes.txtはありません。.dockerignoreの指定を読むだけでなく、出来上がったイメージの中身を確認しました。

6シェルだけを変更して版2を作る

version=twoを表示するapp-v2.shへ差し替えます。cp -pはファイルの更新時刻も引き継ぎます。版1と版2にはそれぞれ異なる固定時刻を設定しています。Dockerfileは変更しません。

cp -p app-v2.sh context/app/app.shアプリの内容を版2へ替える docker build --network=none --pull=false --provenance=false --build-arg SOURCE_DATE_EPOCH=0 -q -t lbox-cl10:v2 context版2のイメージを作る docker run --rm --network none lbox-cl10:v2版2の実行結果を確認する

新しい内容識別子になり、実行結果はversion=two mode=demoです。タグをv2にしただけではなく、コピーした内容そのものが変わっています。

7共通のレイヤーと変更されたレイヤー

二つのイメージを取得し、層のIDを順に比較します。

docker image inspect lbox-cl10:v1 lbox-cl10:v2二つの設定と層を取得する > images.json python3 compare.py同じ位置の層と起動設定を比較する

最初の2層は同じで、3番目のアプリをコピーした層だけが変わります。起動コマンドは同じです。元の層を再利用し、変更された内容を別の層として持つ構成を確認できます。

8同じタグを別の内容へ付け替える

latestをまずv1へ付け、次にv2へ付け替えます。各時点の内容識別子を表示します。

docker image tag lbox-cl10:v1 lbox-cl10:latestlatestを版1へ対応させる docker image inspect lbox-cl10:latest --format '{{.Id}}' docker image tag lbox-cl10:v2 lbox-cl10:latest同じlatestを版2へ付け替える docker image inspect lbox-cl10:latest --format '{{.Id}}'

同じlatestという名前で、指すdigestが変わりました。タグ名だけを記録しても、後から同じ内容を取得できる保証にはなりません。

9イメージIDを指定して元のバージョンを起動する

latestがv2を指す状態で、最初に確認した版1の内容識別子を指定して起動します。

docker run --rm --pull=neverローカルにある内容だけを使用する --network none sha256:e2a07fecc1b1b076ad43d37525884a21f933690ba100af453bd0299f532f2737版1の内容を識別子で選ぶ

version=one mode=demoです。名前の付替え後も、保持されている同じ内容を識別子で選べます。配布時には対象プラットフォームやmanifest/indexのどちらを固定するかも確認します。

まとめ

ビルドコンテキストと、イメージに入ったファイルを照合します。

manifestは設定と層を参照し、各digestは参照先のバイト列から求められます。圧縮後の層のdigestと、圧縮前のdiff IDは異なります。

タグは付け替えられるため、同じ内容を選ぶ条件も記録します。

操作履歴と解説を振り返れます。

Docker Engine 29.7.2 / Linux arm64

1
cat context/Dockerfile cat context/.dockerignore cat context/app/app.sh
FROM alpine:3.22
WORKDIR /app
COPY --chmod=755 app/ /app/
ENV APP_MODE=demo
EXPOSE 8080
ENTRYPOINT ["/app/app.sh"]
LABEL org.lbox.fixture="CL-10"
app/notes.txt
#!/bin/sh
printf 'version=one mode=%s\n' "$APP_MODE"
2
docker build --network=none --pull=false --provenance=false --build-arg SOURCE_DATE_EPOCH=0 -q -t lbox-cl10:v1 context docker run --rm --network none lbox-cl10:v1
sha256:e2a07fecc1b1b076ad43d37525884a21f933690ba100af453bd0299f532f2737
version=one mode=demo
3
docker image inspect lbox-cl10:v1 --format '{{json .Config.Env}} {{json .Config.Entrypoint}} {{json .RootFS.Layers}}'
["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","APP_MODE=demo"] ["/app/app.sh"] ["sha256:9fd9b3248f2fb670edfab1393d1d1e8cd828eb9c65de376b2fcc23268a45d500","sha256:8b6da3bd28008659e694315022ffb9c31aa31b5840b77e8f6197872538b9473a","sha256:026ca6b7c6294345d48e249948307215f55f01545529b77425d3162411aa661e"]
4
docker image save lbox-cl10:v1 -o image.tar python3 archive.py
manifest=sha256:e2a07fecc1b1b076ad43d37525884a21f933690ba100af453bd0299f532f2737
config=sha256:88de531cb89014b1fe7096fb4aae3723bf5027789ed417aed7aef57d682b9643
architecture=arm64 layers=3
layer1 blob=sha256:16fc4f52163f03cd2189c3d6a4b3f28a605cfb7919af64b3da4562cca69d2306 diff_id=sha256:9fd9b3248f2fb670edfab1393d1d1e8cd828eb9c65de376b2fcc23268a45d500
layer2 blob=sha256:b67246d5d21a1e5208d87db10a035e60592ce32fc1bd322536cd6248ad3f3ea1 diff_id=sha256:8b6da3bd28008659e694315022ffb9c31aa31b5840b77e8f6197872538b9473a
layer3 blob=sha256:4fc370b53922a256833ca0d512afe908e8787e96940e2d38af5716fa350e2216 diff_id=sha256:026ca6b7c6294345d48e249948307215f55f01545529b77425d3162411aa661e
last_layer_paths=['app', 'app/app.sh']
5
docker run --rm --network none --entrypoint sh lbox-cl10:v1 -c 'ls -1 /app'
app.sh
6
cp -p app-v2.sh context/app/app.sh docker build --network=none --pull=false --provenance=false --build-arg SOURCE_DATE_EPOCH=0 -q -t lbox-cl10:v2 context docker run --rm --network none lbox-cl10:v2
sha256:35afd429af4fd14711c37928685970adc82481548ecc6b050d5be25b8982a490
version=two mode=demo
7
docker image inspect lbox-cl10:v1 lbox-cl10:v2 > images.json python3 compare.py
v1=sha256:e2a07fecc1b1b076ad43d37525884a21f933690ba100af453bd0299f532f2737
v2=sha256:35afd429af4fd14711c37928685970adc82481548ecc6b050d5be25b8982a490
same_layers=[1, 2]
changed_layers=[3]
entrypoint_unchanged=True
8
docker image tag lbox-cl10:v1 lbox-cl10:latest docker image inspect lbox-cl10:latest --format '{{.Id}}' docker image tag lbox-cl10:v2 lbox-cl10:latest docker image inspect lbox-cl10:latest --format '{{.Id}}'
sha256:e2a07fecc1b1b076ad43d37525884a21f933690ba100af453bd0299f532f2737
sha256:35afd429af4fd14711c37928685970adc82481548ecc6b050d5be25b8982a490
9
docker run --rm --pull=never --network none sha256:e2a07fecc1b1b076ad43d37525884a21f933690ba100af453bd0299f532f2737
version=one mode=demo

演習完了