OverlayFSとコンテナイメージのレイヤー構造
イメージが速く小さく配れる理由を原理から理解できる。OverlayFSのlower/upper/merged構造とコピーオンライト、レイヤーキャッシュ、ビルド再現性のつまずきまで解説。
- 1.コンテナイメージは読み取り専用レイヤーの積み重ね。OverlayFSが複数のlowerを1枚のmergedに重ね、書き込みはupperにコピーオンライトで落とす。
- 2.各レイヤーはコンテンツのハッシュで識別される。同じハッシュなら再ビルドも再ダウンロードも不要——これがレイヤーキャッシュと差分配信の正体。
- 3.ビルド再現性の敵はタイムスタンプ・パッケージ版・命令順。Dockerfileの順序とキャッシュ無効化の仕組みを理解すると差分が安定する。
なぜレイヤーに分けるのか
コンテナイメージを「アプリ+OSファイル群を1枚に固めたディスクイメージ」だと思うと、肝心の効率が見えません。実体は 読み取り専用レイヤーを下から積み重ねた構造 です。ベースOS、ランタイム、依存ライブラリ、アプリコードがそれぞれ別レイヤーになっており、同じレイヤーは複数イメージで 共有・再利用 されます。
この設計が効くのは2点です。第一に、100個のイメージが同じベースレイヤーを使うなら、ディスク上の実体は1つで済む。第二に、アプリだけ変えて再ビルドしても、変わったレイヤーだけを再生成・再転送すればよい。コンテナとVMの違い(/devops/container-vs-vm/)で触れた「差分が速い」の中身が、まさにこのレイヤー機構です。
OverlayFSのlower / upper / merged
レイヤーを実行時に1枚のファイルシステムへ束ねるのが ユニオンファイルシステム で、Linux の標準実装が OverlayFS(overlay2 ドライバ) です。マウント時に4つのディレクトリを指定します。
- lowerdir:読み取り専用の下層。複数を
:で連結でき、左が上・右が下の優先順位を持つ(イメージの各レイヤーがここに並ぶ) - upperdir:書き込み可能な上層。コンテナ起動時に作られる、変更を受け止める層
- workdir:OverlayFS が原子的な操作のために内部で使う作業領域(空である必要がある)
- merged:上記を重ね合わせた結果として見える、統合ビュー
mount -t overlay overlay \
-o lowerdir=L3:L2:L1,upperdir=U,workdir=W \
/merged
merged を読むと、上の層から順にファイルを探し、最初に見つかったものを返します。同名ファイルが lower の複数層にあれば 上層が勝つ(シャドーイング)。これがレイヤーの上書き意味論です。
lowerdir=L3:L2:L1 は L3 が最上位の lower。Dockerfile で後から追加した命令ほど上に積まれ、同名パスは新しい層が古い層を隠します。「後勝ち」はこの探索順から来ています。
コピーオンライト(CoW)の実際
merged はマウントしたままで読めますが、書き込みは upper でだけ起こる ——ここが要です。lower は不変なので、lower にあるファイルを変更しようとすると次の手順が走ります。
- 該当ファイルを lower から upper へ 丸ごとコピー(copy-up)
- 以降の読み書きは upper のコピーに対して行う
- merged からは「変更後のファイル」が見える(upper が lower を隠す)
つまり最初の書き込みの瞬間にだけコピーコストが発生し、未変更のファイルは1バイトもコピーされません。これが コピーオンライト です。コピー単位は ファイル全体(ブロック単位ではない)なので、巨大ファイルの1バイト書き換えでも全体がコピーされる点は実務で効きます。
削除も特殊です。lower のファイルを「消す」には、upper に whiteout(キャラクタデバイス、デバイス番号 0/0)を置いて、その名前を merged から見えなくします。ディレクトリ全体を不透明にするには trusted.overlay.opaque 属性を使った opaque ディレクトリ を作ります。だから「削除したのにイメージは小さくならない」——前のレイヤーにファイル本体が残り、上の層に whiteout が増えるだけだからです。
1つのレイヤーで巨大ファイルを置き、別の RUN で削除しても、本体は下のレイヤーに残り続けます。サイズを削るには「置く・使う・消す」を 同一 RUN 内(同一レイヤー内) で完結させるか、マルチステージビルドで成果物だけを最終ステージへコピーします。
レイヤーの同一性とキャッシュ
各レイヤーは中身から計算した digest(content-addressable なハッシュ) で識別されます。レジストリ上ではレイヤーは tar アーカイブを gzip 等で圧縮した blob として置かれ、sha256:... で名前付けされます。イメージの manifest がレイヤー digest のリストを保持し、config が展開後の各レイヤーを指す diff_id を順に並べます。
この content-addressing が2つの最適化を生みます。
- ビルドキャッシュ:Dockerfile の各命令の結果レイヤーをキャッシュ。命令文字列とビルドコンテキスト(
COPY対象のファイル内容など)が前回と同一なら、レイヤーを作り直さず再利用する - 差分配信(プル/プッシュ):レジストリとローカルで digest を突き合わせ、手元に無い blob だけ転送。同じベースレイヤーを持つ別イメージを引くなら、共通レイヤーはダウンロードしない
FROM node:20-slim
WORKDIR /app
COPY package*.json ./ # 依存定義だけ先にコピー
RUN npm ci # ← ここまでが変わらなければキャッシュ命中
COPY . . # アプリコード(頻繁に変わる)
RUN npm run build
順序が肝心です。変わりにくいもの(依存)を上、変わりやすいもの(ソース)を下 に置くと、コード変更時に npm ci のレイヤーがキャッシュに当たり、依存の再取得を丸ごと省けます。逆に COPY . . を先頭に置くと、どんな小さな修正でも以降の全レイヤーが無効化されます。レジストリへの集約とキャッシュ運用は /devops/artifact-registry/ も合わせて読むと地続きです。
ある命令でキャッシュが外れると、それ以降の命令はすべて再実行されます(後段は前段のレイヤーに依存するため)。だから「無効化されやすい命令ほど下」が鉄則。CI でのビルド時間短縮(/devops/ci-cd/)に直結します。
ビルド再現性への影響
同じ Dockerfile から 毎回ビット単位で同じイメージ を作れると、検証・キャッシュ共有・サプライチェーン保証が安定します。しかし現実は崩れやすく、原因はレイヤーの中身に紛れ込む非決定性です。
| 再現性を壊す要因 | 何が起きるか | 対処の方向 |
|---|---|---|
| ファイルのタイムスタンプ | tar 化時の mtime が毎回変わり digest が変動 | mtime を固定(SOURCE_DATE_EPOCH 等) |
| パッケージのバージョン浮動 | apt/npm が最新を引き、中身が日替わり | 版を固定(pin)・lockファイルを使う |
| 命令の順序・並列性 | 実行順でレイヤー内容や順番が変わる | 順序を固定、非決定的な並列処理を避ける |
| ベースイメージのタグ | latest や :20 が裏で更新される | digest 指定(FROM img@sha256:...) |
特に効くのは ベースイメージの digest 固定 です。FROM node:20-slim はタグであり、同じタグでも中身が更新され得ます。FROM node@sha256:... と digest で固定すれば、誰がいつ引いても同一の lower レイヤーから始まります。これは不変インフラの考え方(/devops/immutable-infra/)と同根で、「同じ入力からは同じ成果物」を成立させる前提条件です。
「コンテナ削除でディスクが空かない」はコピーオンライトの whiteout が理由。「コード修正のたびに依存が再取得される」は COPY 順序によるキャッシュ無効化。「同じ Dockerfile なのにイメージ digest が変わる」はタグ浮動かタイムスタンプ非決定性——3点セットで押さえると応用が利きます。
まとめ
コンテナイメージは不変な lower レイヤーの積層であり、OverlayFS が lower / upper / merged を重ねて1枚に見せ、書き込みは upper へコピーオンライトで落とします。レイヤーは content-addressable なため、同一 digest は再ビルドも再転送も不要——これがキャッシュと差分配信の核心です。再現性を守るには、Dockerfile の命令順、版の固定、ベースイメージの digest 指定を意識し、レイヤーの中身から非決定性を追い出すことが要点になります。
DevOps/インフラ Article
OverlayFSとコンテナイメージのレイヤー構造を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
コンテナ
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 5
導入後に効く点
各レイヤーはコンテンツのハッシュで識別される。同じハッシュなら再ビルドも再ダウンロードも不要——これがレイヤーキャッシュと差分配信の正体。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「コンテナ / OverlayFS」に近いか確認する。
- 強みである「コンテナイメージは読み取り専用レイヤーの積み重ね。OverlayFSが複数のlowerを1枚のmergedに重ね、書き込みはupperにコピーオンライトで落とす。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。