
Dev Container の Lockfile がGAになっていました
製造ビジネステクノロジー部のかずえです。
Dev Container CLI には、devcontainer.json に書いた Features を「どのバージョン・どのハッシュで解決したか」を記録する devcontainer-lock.json(通称 Dev Container Lockfile)という仕組みがあります。npm の package-lock.json に相当する存在ですが、これまでは Preview(実験的)機能という位置づけでした。
2026年4月、この Lockfile を Preview から Stable に昇格させる提案が devcontainers/cli の Issue #1195 で立てられ、本記事執筆時点ではすでに Closed になっています。この記事では、Lockfile がそもそも何をしているのか、今回の提案で何が変わるのかを、実際に CLI を動かしながら確認します。
Dev Container Lockfile とは
Dev Container Lockfile は、devcontainer.json と同じディレクトリに生成される devcontainer-lock.json という JSON ファイルです。そこには、解決された各 Feature について次の情報が記録されます。
version: 解決された正確なバージョン番号resolved: OCI Feature の場合は sha256 ダイジェスト付きの完全修飾ID、tarball Feature の場合はダウンロードURLintegrity:sha256:に続くチェックサムdependsOn: 依存する他の Feature(依存がなければ省略)
仕様(devcontainer-lockfile.md)に載っている例はこのような形です。
{
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "1.0.4",
"resolved": "ghcr.io/devcontainers/features/node@sha256:567d704b3f4d3eca3acee51ded7c460a8395436d135d53d1175fb565daff42b8",
"integrity": "sha256:567d704b3f4d3eca3acee51ded7c460a8395436d135d53d1175fb565daff42b8"
},
"https://mycustomdomain.com/devcontainer-feature-myfeature.tgz": {
"version": "1.2.3",
"resolved": "https://mycustomdomain.com/devcontainer-feature-myfeature.tgz",
"integrity": "sha256:567d704b3f4d3eca3acee51ded7c460a8395436d135d53d1175fb565daff42b8"
}
}
}
devcontainer.json 側に書くバージョン指定("1" のようなメジャーバージョンだけの指定も多い)に対して、Lockfile 側は「その時点で実際に解決された1つのバージョンとハッシュ」を固定します。これにより、同じ devcontainer.json でも実行タイミングによって別バージョンの Feature が混入する、という事態を防げます。
なぜ Stable 化が議論されていたのか
Issue #1195 では、Lockfile を Stable にする狙いとして次の3点が挙げられています。
- 再現性: 時間や実行環境が変わっても、常に同じ Features の組み合わせに解決される
- セキュリティ検証: Feature のアーティファクトが検証され、意図しない変更や改ざんを検知できる(trust on first use)
- 差分の可視化: 依存関係の変化が透明になり、レビューしやすくなる
具体的な変更提案は次の4点でした。
- Lockfile 生成をデフォルトで有効化する
--no-lockfile(opt-out)と--frozen-lockfile(CI向け)フラグを追加する- 従来の
--experimental-lockfile系フラグを廃止予定にする - 対象コマンドを
buildとupに限定する
この Issue は Dev Container Spec 側の Lockfile 仕様や、実装を追った CLI Issue #564、コミュニティでの議論である Discussion #237 とも紐づいています。
CLI で実際にフラグを確認する
提案が実際にどこまで反映されているか、手元で @devcontainers/cli を動かして確認しました。
$ npx --yes @devcontainers/cli --version
0.88.0
build --help を見ると、提案にあった2つのフラグがすでに実装されていることが分かります。
$ npx --yes @devcontainers/cli build --help
...
--no-lockfile Disable lockfile generation and verification. [boolean] [default: false]
--frozen-lockfile Ensure lockfile exists and remains unchanged; fail otherwise. [boolean] [default: false]
--no-lockfile のデフォルトが false になっている、つまり明示的に無効化しない限り Lockfile 生成が有効というのがポイントです。Issue #1195 で提案されていた「デフォルト有効化」がすでにこのバージョンで実現されていることが確認できました。up コマンドも同様です。
実際に devcontainer.json に Feature を1つ追加した状態で devcontainer build . を実行し、生成される devcontainer-lock.json を確認しました。
// .devcontainer/devcontainer.json
{
"name": "lockfile-test",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/node:1": {}
}
}
$ npx --yes @devcontainers/cli build --workspace-folder .
[3 ms] @devcontainers/cli 0.88.0. Node.js v25.8.0. darwin 25.5.0 arm64.
[624 ms] Resolving Feature dependencies for 'ghcr.io/devcontainers/features/node:1'...
[4171 ms] Soft-dependency 'ghcr.io/devcontainers/features/common-utils' is not required. Removing from installation order...
[12686 ms] Files to omit: ''
[12703 ms] Start: Run: docker buildx build --load --build-arg BUILDKIT_INLINE_CACHE=1 --build-context dev_containers_feature_content_source=/var/folders/0f/n7rzxczd39b1wvh348n5ztqw0000gn/T/devcontainercli/container-features/0.88.0-1785488399594 --build-arg _DEV_CONTAINERS_BASE_IMAGE=mcr.microsoft.com/devcontainers/base:ubuntu --build-arg _DEV_CONTAINERS_IMAGE_USER=root --build-arg _DEV_CONTAINERS_FEATURE_CONTENT_SOURCE=dev_container_feature_content_temp --target dev_containers_target_stage -f /var/folders/0f/n7rzxczd39b1wvh348n5ztqw0000gn/T/devcontainercli/container-features/0.88.0-1785488399594/Dockerfile.extended -t vsc-20260727-devcontainer-lock-075fb970c878fcbf033c3e38f73264c080467c8fbb8839113e70fa6d4016be46-features /var/folders/0f/n7rzxczd39b1wvh348n5ztqw0000gn/T/devcontainercli/empty-folder
[+] Building 0.0s (14/14) FINISHED docker:rancher-desktop
=> [internal] load build definition from Dockerfile.extended 0.0s
=> => transferring dockerfile: 2.42kB 0.0s
=> [internal] load metadata for mcr.microsoft.com/devcontainers/base:ubuntu 0.0s
=> [context dev_containers_feature_content_source] load .dockerignore 0.0s
=> => transferring dev_containers_feature_content_source: 2B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [context dev_containers_feature_content_source] load from client 0.0s
=> => transferring dev_containers_feature_content_source: 101B 0.0s
=> [context dev_containers_feature_content_source] load from client 0.0s
=> => transferring dev_containers_feature_content_source: 1.58kB 0.0s
=> [dev_containers_feature_content_normalize 1/3] FROM mcr.microsoft.com/devcontainers/base:ubuntu 0.0s
=> CACHED [dev_containers_target_stage 2/5] RUN mkdir -p /tmp/dev-container-features 0.0s
=> CACHED [dev_containers_feature_content_normalize 2/3] COPY --from=dev_containers_feature_content_source devcontainer-features.builtin.env /tmp/build-features/ 0.0s
=> CACHED [dev_containers_feature_content_normalize 3/3] RUN chmod -R 0755 /tmp/build-features/ 0.0s
=> CACHED [dev_containers_target_stage 3/5] COPY --from=dev_containers_feature_content_normalize /tmp/build-features/ /tmp/dev-container-features 0.0s
=> CACHED [dev_containers_target_stage 4/5] RUN echo "_CONTAINER_USER_HOME=$( (command -v getent >/dev/null 2>&1 && getent passwd 'root' || grep -E '^root|^[^:]*:[^:]*:root:' /etc/passwd || true) | cut -d 0.0s
=> CACHED [dev_containers_target_stage 5/5] RUN --mount=type=bind,from=dev_containers_feature_content_source,source=node_0,target=/tmp/build-features-src/node_0 cp -ar /tmp/build-features-src/node_0 / 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => preparing layers for inline cache 0.0s
=> => writing image sha256:59f3af19be1fae47e034f734b53c2b2d08130d0b418c3c9fedc2e2bfe7387019 0.0s
=> => naming to docker.io/library/vsc-20260727-devcontainer-lock-075fb970c878fcbf033c3e38f73264c080467c8fbb8839113e70fa6d4016be46-features 0.0s
{"outcome":"success","imageName":["vsc-20260727-devcontainer-lock-075fb970c878fcbf033c3e38f73264c080467c8fbb8839113e70fa6d4016be46-features"]}
Feature の解決が完了した時点で、.devcontainer/devcontainer-lock.json に次の内容が生成されました。
{
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "1.7.1",
"resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6",
"integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6"
}
}
}
devcontainer.json 側では "1" というメジャーバージョン指定しかしていませんが、Lockfile には実際に解決された 1.7.1 というバージョンと、sha256 ダイジェストが記録されています。
--frozen-lockfile は CI での利用を想定したフラグです。Lockfile をリポジトリにコミットしておき、CI では --frozen-lockfile を付けて実行すれば、ローカルで確認した組み合わせと異なる Feature が解決された場合にビルドを失敗させられます。npm の ci コマンドが package-lock.json を検証する動きに近い挙動です。
Featureのバージョンを上げるには
Lockfile で固定された Feature のバージョンを上げたい場合、devcontainer outdated と devcontainer upgrade という専用コマンドが用意されています。動作を確認するため、devcontainer-lock.json を手動で古いバージョンに書き換えた(1.7.1 → 1.6.0)うえで、それぞれ実行してみました。
$ npx --yes @devcontainers/cli outdated --workspace-folder .
[1 ms] @devcontainers/cli 0.88.0. Node.js v25.8.0. darwin 25.5.0 arm64.
Feature Current Wanted Latest
ghcr.io/devcontainers/features/node 1.6.0 1.7.1 2.1.0
Current はロック済みのバージョン、Wanted は devcontainer.json 側の指定(今回は "1" というメジャーバージョン指定)を満たす範囲での最新版、Latest はその指定を無視した絶対最新版です。
devcontainer.json の指定を変えずに Wanted まで上げるだけなら、upgrade コマンドを実行するだけで済みます。
$ npx --yes @devcontainers/cli upgrade --workspace-folder .
実行すると devcontainer-lock.json が Wanted のバージョン(今回の検証では 1.7.1)とその新しいハッシュに書き換わります。--dry-run を付けると、ファイルを書き換えずに生成結果を標準出力に表示できるので、CI や事前確認に使えます。
Wanted を超えて Latest(今回なら 2.1.0)まで上げたい、つまりメジャーバージョンをまたいで上げたい場合は、upgrade だけでは足りません。devcontainer.json 側のバージョン指定自体を変更する必要があります(例: "ghcr.io/devcontainers/features/node:1" を "...node:2" に変更)。変更後に build / up あるいは upgrade を実行すると、新しい指定に基づいて Lockfile が再生成されます。
なお CI で --frozen-lockfile を付けている場合、devcontainer.json の指定と Lockfile の内容が食い違うとビルド自体が失敗する仕様です。バージョンを上げる作業は --frozen-lockfile なしのローカル環境で行い、更新後の devcontainer-lock.json をコミットしてから CI に反映する、という流れになります。
VS Code拡張の場合はどうか
ここまでは @devcontainers/cli 単体での検証でした。実際の開発では VS Code の Dev Containers 拡張機能経由で使うケースも多いため、CLI との違いも押さえておきます。
Dev Container CLI によると、拡張機能には CLI の「variation(変種)」が同梱されており、拡張機能の更新に合わせて自動更新されます。つまり npm 経由で使う @devcontainers/cli とは別物のCLIが拡張の内部で動いています。
Lockfile自体は拡張機能側にも存在します。手元の Dev Containers 拡張(Version 0.466.0)の設定画面で確認したところ、「Dev › Containers: Lockfile」(設定ID: dev.containers.lockfile、"Controls whether a devcontainer-lock.json should be written.")という項目があり、デフォルトで有効になっていました。

一方で、バージョンをアップグレードするための手段は拡張機能のGUI上には用意されていません。コマンドパレットにもロックファイル関連の項目はなく(Version 0.466.0で確認)、バージョンの差分確認やアップグレードをGUIから行うことはできません。実際、Issue #11727: Support devcontainer-lock.json upgrade flow(2026年7月1日作成、Open、VS CodeチームのMicrosoftメンバーがアサイン済み)で、「CLIは devcontainer upgrade に対応済みだが、VS Code拡張にはそれに相当するUI機能がない」という趣旨の要望が上がっており、記事執筆時点でもまだ未解決です。
devcontainer-lock.json はVS Code拡張専用のファイルではなく、Dev Container Spec が定める共通フォーマットのプロジェクトファイルです。そのため一番確実なのは、拡張機能のGUIに頼らず、プロジェクトのターミナルで素の @devcontainers/cli を使って devcontainer outdated / devcontainer upgrade を実行し、Lockfileを直接更新する方法です。outdated で Wanted / Latest を事前に確認できるうえ、更新後に拡張機能側で「Dev Containers: Rebuild Container」を実行すれば、その更新済みLockfileがそのまま使われます。
CLIを手元で使えない、あるいはGUIだけで完結させたい場合は、次のような手動操作でも同じ結果になります。
devcontainer.jsonの指定範囲内で上げる:devcontainer-lock.jsonを削除して「Dev Containers: Rebuild Container」を実行する(Lockfileが存在しなければ改めて解決されるため、devcontainer upgradeと結果は同じになります)- 指定範囲を超えて(メジャーバージョンをまたいで)上げる:
devcontainer.json側のバージョン指定自体を書き換えてから「Rebuild Container」を実行する(CLIと同じ考え方です)
まとめ
- Dev Container Lockfile(
devcontainer-lock.json)は、Features のバージョンとハッシュを固定して再現性とセキュリティを高める仕組みで、package-lock.jsonに近い役割を持ちます。 - チームで Dev Container を使っている場合、
devcontainer-lock.jsonをリポジトリにコミットしておき、CI では--frozen-lockfileを付けて実行するようにしておくと、意図しない Feature の変化に気づきやすくなります。
参考
- Graduate Dev Container Lockfile from Preview to Stable · Issue #1195 · devcontainers/cli
- devcontainer-lockfile.md · devcontainers/spec
- CLI Issue #564 · devcontainers/cli
- Lock Files · devcontainers Discussion #237
- devcontainers/cli
- Dev Container CLI · Visual Studio Code Docs
- Support devcontainer-lock.json upgrade flow · Issue #11727 · microsoft/vscode-remote-release



