From 1b7c38d6ffc6218f635be6ba4aaf4249a1a103ea Mon Sep 17 00:00:00 2001 From: Samuel Frost Date: Fri, 18 Sep 2026 17:43:39 +0000 Subject: [PATCH 1/4] Add extends property for configuration inheritance Document and schema-enable the extends keyword from #22, merging referenced files with the existing image metadata merge logic. --- docs/specs/devcontainer-reference.md | 2 +- docs/specs/devcontainerjson-reference.md | 39 ++++++++++++++++++++++++ schemas/devContainer.base.schema.json | 4 +++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/docs/specs/devcontainer-reference.md b/docs/specs/devcontainer-reference.md index aa269340..b160706e 100644 --- a/docs/specs/devcontainer-reference.md +++ b/docs/specs/devcontainer-reference.md @@ -84,7 +84,7 @@ To apply the metadata together with a user's `devcontainer.json` at runtime the | `updateRemoteUserUID` | `boolean` | Last value wins. | ✓ | | | `hostRequirements` | `cpus`, `memory`, `storage`, `gpu` | Max value wins. | ✓ | | -Variables in string values will be substituted at the time the value is applied. When the order matters, the `devcontainer.json` is considered last. +Variables in string values will be substituted at the time the value is applied. When the order matters, the `devcontainer.json` is considered last. The same merge logic is used when a `devcontainer.json` file [extends](devcontainerjson-reference.md#configuration-inheritance) another configuration file in the same repository. ### Notes diff --git a/docs/specs/devcontainerjson-reference.md b/docs/specs/devcontainerjson-reference.md index fa3893b1..60954097 100644 --- a/docs/specs/devcontainerjson-reference.md +++ b/docs/specs/devcontainerjson-reference.md @@ -9,6 +9,7 @@ Metadata properties marked with a 🏷️ can be stored in the `devcontainer.met | Property | Type | Description | |----------|------|-------------| | `name` | string | A name for the dev container displayed in the UI | +| `extends` | string | A relative path to a JSON or JSONC file in the same repository to use as a base configuration. The referenced file is merged with this file using the [image metadata merge logic](devcontainer-reference.md#merge-logic). See [Configuration inheritance](#configuration-inheritance). | | `forwardPorts` 🏷️ | array | An array of port numbers or `"host:port"` values (e.g. `[3000, "db:5432"]`) that should always be forwarded from inside the primary container to the local machine (including on the web). The property is most useful for forwarding ports that cannot be auto-forwarded because the related process that starts before the `devcontainer.json` supporting service / tool connects or for forwarding a service not in the primary container in Docker Compose scenarios (e.g. `"db:5432"`). Defaults to `[]`. | | `portsAttributes` 🏷️ | object | Object that maps a port number, `"host:port"` value, range, or regular expression to a set of default options. See [port attributes](#port-attributes) for available options. For example:
`"portsAttributes": {"3000": {"label": "Application port"}}` | | `otherPortsAttributes` 🏷️ | object | Default options for ports, port ranges, and hosts that aren't configured using `portsAttributes`. See [port attributes](#port-attributes) for available options. For example:
`"otherPortsAttributes": {"onAutoForward": "silent"}` | @@ -29,6 +30,44 @@ Metadata properties marked with a 🏷️ can be stored in the `devcontainer.met | `overrideFeatureInstallOrder` | array | By default, Features will attempt to automatically set the order they are installed based on a `installsAfter` property within each of them. This property allows you to override the Feature install order when needed. For example:
`"overrideFeatureInstallОrder": [ "ghcr.io/devcontainers/features/common-utils", "ghcr.io/devcontainers/features/github-cli" ]` | | `customizations` 🏷️| object | Product specific properties, defined in [supporting tools](supporting-tools.md) | +## Configuration inheritance + +Multiple teams collaborating on a common codebase may need slightly different `devcontainer.json` settings. The `extends` property lets a configuration inherit from another JSON or JSONC file in the same repository: + +```jsonc +// .devcontainer/defaults.json +{ + "name": "example/project", + "forwardPorts": [80, 5432], + "hostRequirements": { + "storage": "64gb", + "memory": "16gb" + } +} + +// .devcontainer/devcontainer.json +{ + "extends": "./defaults.json", + "forwardPorts": [2222], + "hostRequirements": { + "memory": "32gb" + }, + "onCreateCommand": ".devcontainer/on-create-command.sh" +} +``` + +`extends` is a path relative to the file that declares it (for example `"./defaults.json"`, `"../defaults.json"`, or `"./dev/defaults.json"`). Referenced files may themselves use `extends`. Absolute paths and URLs are not supported. + +The referenced configuration is merged with the current file using the same [merge logic](devcontainer-reference.md#merge-logic) applied to image metadata, with the current file considered last: + +- Array properties such as `forwardPorts`, `capAdd`, and `securityOpt` are the union of values without duplicates. +- `hostRequirements` takes the maximum of each field. +- Object maps such as `remoteEnv`, `containerEnv`, `features`, and `customizations` merge per key, with the current file winning on conflicts. +- Boolean `init` and `privileged` are `true` if at least one value is `true`. +- Scalar properties such as `name`, `image`, `remoteUser`, and lifecycle commands use last value wins. + +The `extends` property itself is not present in the merged result. + ## Scenario specific properties The focus of `devcontainer.json` is to describe how to enrich a container for the purposes of development rather than acting as a multi-container orchestrator format. Instead, container orchestrator formats can be referenced when needed to manage multiple containers and their lifecycles. Today, `devcontainer.json` includes scenario specific properties for working without a container orchestrator (by directly referencing an image or Dockerfile) and for using Docker Compose as a simple multi-container orchestrator. diff --git a/schemas/devContainer.base.schema.json b/schemas/devContainer.base.schema.json index 86709eca..13cc7730 100644 --- a/schemas/devContainer.base.schema.json +++ b/schemas/devContainer.base.schema.json @@ -16,6 +16,10 @@ "type": "string", "description": "A name for the dev container which can be displayed to the user." }, + "extends": { + "type": "string", + "description": "A relative path to a JSON or JSONC file in the same repository whose configuration is used as a base. The referenced file is merged with this file using the image metadata merge logic." + }, "features": { "type": "object", "description": "Features to add to the dev container.", From 9f8d6d7c721cb600aba5d1a2349f9f0ff96aa995 Mon Sep 17 00:00:00 2001 From: Samuel Frost Date: Thu, 24 Sep 2026 06:24:12 +0000 Subject: [PATCH 2/4] Document extendsMergeMode for configuration inheritance Schema and reference docs for combine (default) and override merge when using extends, aligned with the Dev Container CLI behavior. --- docs/specs/devcontainerjson-reference.md | 19 ++++++++++++++++--- schemas/devContainer.base.schema.json | 10 +++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/specs/devcontainerjson-reference.md b/docs/specs/devcontainerjson-reference.md index 60954097..be9a4cd9 100644 --- a/docs/specs/devcontainerjson-reference.md +++ b/docs/specs/devcontainerjson-reference.md @@ -9,7 +9,8 @@ Metadata properties marked with a 🏷️ can be stored in the `devcontainer.met | Property | Type | Description | |----------|------|-------------| | `name` | string | A name for the dev container displayed in the UI | -| `extends` | string | A relative path to a JSON or JSONC file in the same repository to use as a base configuration. The referenced file is merged with this file using the [image metadata merge logic](devcontainer-reference.md#merge-logic). See [Configuration inheritance](#configuration-inheritance). | +| `extends` | string | A relative path to a JSON or JSONC file in the same repository to use as a base configuration. The referenced file is merged with this file using the logic selected by `extendsMergeMode`. See [Configuration inheritance](#configuration-inheritance). | +| `extendsMergeMode` | string | Optional. `combine` (default) or `override`. See [Configuration inheritance](#configuration-inheritance). | | `forwardPorts` 🏷️ | array | An array of port numbers or `"host:port"` values (e.g. `[3000, "db:5432"]`) that should always be forwarded from inside the primary container to the local machine (including on the web). The property is most useful for forwarding ports that cannot be auto-forwarded because the related process that starts before the `devcontainer.json` supporting service / tool connects or for forwarding a service not in the primary container in Docker Compose scenarios (e.g. `"db:5432"`). Defaults to `[]`. | | `portsAttributes` 🏷️ | object | Object that maps a port number, `"host:port"` value, range, or regular expression to a set of default options. See [port attributes](#port-attributes) for available options. For example:
`"portsAttributes": {"3000": {"label": "Application port"}}` | | `otherPortsAttributes` 🏷️ | object | Default options for ports, port ranges, and hosts that aren't configured using `portsAttributes`. See [port attributes](#port-attributes) for available options. For example:
`"otherPortsAttributes": {"onAutoForward": "silent"}` | @@ -58,7 +59,11 @@ Multiple teams collaborating on a common codebase may need slightly different `d `extends` is a path relative to the file that declares it (for example `"./defaults.json"`, `"../defaults.json"`, or `"./dev/defaults.json"`). Referenced files may themselves use `extends`. Absolute paths and URLs are not supported. -The referenced configuration is merged with the current file using the same [merge logic](devcontainer-reference.md#merge-logic) applied to image metadata, with the current file considered last: +The referenced configuration is merged with the current file. The current file is considered last. Use `extendsMergeMode` on the file that declares `extends` to choose the merge behavior (default: `combine`). + +### `extendsMergeMode`: `combine` (default) + +Uses the same [merge logic](devcontainer-reference.md#merge-logic) applied to image metadata: - Array properties such as `forwardPorts`, `capAdd`, and `securityOpt` are the union of values without duplicates. - `hostRequirements` takes the maximum of each field. @@ -66,7 +71,15 @@ The referenced configuration is merged with the current file using the same [mer - Boolean `init` and `privileged` are `true` if at least one value is `true`. - Scalar properties such as `name`, `image`, `remoteUser`, and lifecycle commands use last value wins. -The `extends` property itself is not present in the merged result. +### `extendsMergeMode`: `override` + +Uses overlay-style merging when the current file should replace rather than combine with the base: + +- Arrays and scalars from the current file replace the base when set on the current file (for example, `forwardPorts` is only the current file's list). +- Object maps and `hostRequirements` are shallow-merged per key, with the current file winning on conflicts. +- Boolean `init` and `privileged` use the current file's value when set. + +Neither `extends` nor `extendsMergeMode` is present in the merged result. ## Scenario specific properties diff --git a/schemas/devContainer.base.schema.json b/schemas/devContainer.base.schema.json index 13cc7730..0344b15a 100644 --- a/schemas/devContainer.base.schema.json +++ b/schemas/devContainer.base.schema.json @@ -18,7 +18,15 @@ }, "extends": { "type": "string", - "description": "A relative path to a JSON or JSONC file in the same repository whose configuration is used as a base. The referenced file is merged with this file using the image metadata merge logic." + "description": "A relative path to a JSON or JSONC file in the same repository whose configuration is used as a base. The referenced file is merged with this file using the merge logic selected by extendsMergeMode." + }, + "extendsMergeMode": { + "type": "string", + "enum": [ + "combine", + "override" + ], + "description": "How to merge the referenced extends file with this file. combine (default) uses the image metadata merge logic. override replaces arrays and scalars from this file and shallow-merges object maps and hostRequirements." }, "features": { "type": "object", From c221072495d5d066bb9262898b9f2d1d532409bd Mon Sep 17 00:00:00 2001 From: Samuel Frost Date: Thu, 24 Sep 2026 07:16:55 +0000 Subject: [PATCH 3/4] Document override extendsMergeMode as full top-level replace Align spec and schema with spread semantics: set properties replace the inherited value entirely; omitted properties keep the chain below. --- docs/specs/devcontainerjson-reference.md | 7 +++---- schemas/devContainer.base.schema.json | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/specs/devcontainerjson-reference.md b/docs/specs/devcontainerjson-reference.md index be9a4cd9..4e5bb303 100644 --- a/docs/specs/devcontainerjson-reference.md +++ b/docs/specs/devcontainerjson-reference.md @@ -73,11 +73,10 @@ Uses the same [merge logic](devcontainer-reference.md#merge-logic) applied to im ### `extendsMergeMode`: `override` -Uses overlay-style merging when the current file should replace rather than combine with the base: +Uses overlay-style merging when the current file should replace inherited values rather than combine with the base (`{ ...base, ...current }`): -- Arrays and scalars from the current file replace the base when set on the current file (for example, `forwardPorts` is only the current file's list). -- Object maps and `hostRequirements` are shallow-merged per key, with the current file winning on conflicts. -- Boolean `init` and `privileged` use the current file's value when set. +- Each top-level property set on the current file fully replaces the inherited value (arrays, object maps, `hostRequirements`, scalars, and booleans). +- Top-level properties omitted on the current file keep the value from the referenced configuration chain. Neither `extends` nor `extendsMergeMode` is present in the merged result. diff --git a/schemas/devContainer.base.schema.json b/schemas/devContainer.base.schema.json index 0344b15a..1d2831ac 100644 --- a/schemas/devContainer.base.schema.json +++ b/schemas/devContainer.base.schema.json @@ -26,7 +26,7 @@ "combine", "override" ], - "description": "How to merge the referenced extends file with this file. combine (default) uses the image metadata merge logic. override replaces arrays and scalars from this file and shallow-merges object maps and hostRequirements." + "description": "How to merge the referenced extends file with this file. combine (default) uses the image metadata merge logic. override uses spread semantics: each top-level property set on this file fully replaces the inherited value; omitted properties keep the inherited value." }, "features": { "type": "object", From 77355fd8186b12b629f647b239df743b96abe2cd Mon Sep 17 00:00:00 2001 From: Samuel Frost Date: Fri, 25 Sep 2026 04:12:16 +0000 Subject: [PATCH 4/4] Simplification of wording to improve readability --- docs/specs/devcontainerjson-reference.md | 22 +++++++++++----------- schemas/devContainer.base.schema.json | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/specs/devcontainerjson-reference.md b/docs/specs/devcontainerjson-reference.md index 4e5bb303..0bea315f 100644 --- a/docs/specs/devcontainerjson-reference.md +++ b/docs/specs/devcontainerjson-reference.md @@ -9,7 +9,7 @@ Metadata properties marked with a 🏷️ can be stored in the `devcontainer.met | Property | Type | Description | |----------|------|-------------| | `name` | string | A name for the dev container displayed in the UI | -| `extends` | string | A relative path to a JSON or JSONC file in the same repository to use as a base configuration. The referenced file is merged with this file using the logic selected by `extendsMergeMode`. See [Configuration inheritance](#configuration-inheritance). | +| `extends` | string | Relative path to a JSON or JSONC base configuration in the same repository. See [Configuration inheritance](#configuration-inheritance). | | `extendsMergeMode` | string | Optional. `combine` (default) or `override`. See [Configuration inheritance](#configuration-inheritance). | | `forwardPorts` 🏷️ | array | An array of port numbers or `"host:port"` values (e.g. `[3000, "db:5432"]`) that should always be forwarded from inside the primary container to the local machine (including on the web). The property is most useful for forwarding ports that cannot be auto-forwarded because the related process that starts before the `devcontainer.json` supporting service / tool connects or for forwarding a service not in the primary container in Docker Compose scenarios (e.g. `"db:5432"`). Defaults to `[]`. | | `portsAttributes` 🏷️ | object | Object that maps a port number, `"host:port"` value, range, or regular expression to a set of default options. See [port attributes](#port-attributes) for available options. For example:
`"portsAttributes": {"3000": {"label": "Application port"}}` | @@ -33,7 +33,7 @@ Metadata properties marked with a 🏷️ can be stored in the `devcontainer.met ## Configuration inheritance -Multiple teams collaborating on a common codebase may need slightly different `devcontainer.json` settings. The `extends` property lets a configuration inherit from another JSON or JSONC file in the same repository: +Use `extends` to inherit settings from another JSON or JSONC file in the same repository: ```jsonc // .devcontainer/defaults.json @@ -57,28 +57,28 @@ Multiple teams collaborating on a common codebase may need slightly different `d } ``` -`extends` is a path relative to the file that declares it (for example `"./defaults.json"`, `"../defaults.json"`, or `"./dev/defaults.json"`). Referenced files may themselves use `extends`. Absolute paths and URLs are not supported. +`extends` is relative to the file that declares it, for example `"./defaults.json"`, `"../defaults.json"`, or `"./dev/defaults.json"`. Referenced files may also use `extends`. Absolute paths and URLs are not supported. -The referenced configuration is merged with the current file. The current file is considered last. Use `extendsMergeMode` on the file that declares `extends` to choose the merge behavior (default: `combine`). +The referenced configuration is merged first, then the current file is applied. Use `extendsMergeMode` on the file that declares `extends` to choose the merge behavior. + +Both `extends` and `extendsMergeMode` properties are removed from the merged result. ### `extendsMergeMode`: `combine` (default) -Uses the same [merge logic](devcontainer-reference.md#merge-logic) applied to image metadata: +Uses the same [merge logic](devcontainer-reference.md#merge-logic) as image metadata: - Array properties such as `forwardPorts`, `capAdd`, and `securityOpt` are the union of values without duplicates. - `hostRequirements` takes the maximum of each field. - Object maps such as `remoteEnv`, `containerEnv`, `features`, and `customizations` merge per key, with the current file winning on conflicts. - Boolean `init` and `privileged` are `true` if at least one value is `true`. -- Scalar properties such as `name`, `image`, `remoteUser`, and lifecycle commands use last value wins. +- Single-value properties such as `name`, `image`, and `remoteUser` override the value from the referenced file if set in the current file. ### `extendsMergeMode`: `override` -Uses overlay-style merging when the current file should replace inherited values rather than combine with the base (`{ ...base, ...current }`): - -- Each top-level property set on the current file fully replaces the inherited value (arrays, object maps, `hostRequirements`, scalars, and booleans). -- Top-level properties omitted on the current file keep the value from the referenced configuration chain. +Uses top-level merging with key-based overrides (`{ ...base, ...current }`): -Neither `extends` nor `extendsMergeMode` is present in the merged result. +- Properties set in the current file replace the inherited value. +- Properties omitted from the current file keep the inherited value. ## Scenario specific properties diff --git a/schemas/devContainer.base.schema.json b/schemas/devContainer.base.schema.json index 1d2831ac..f1da9ebe 100644 --- a/schemas/devContainer.base.schema.json +++ b/schemas/devContainer.base.schema.json @@ -18,7 +18,7 @@ }, "extends": { "type": "string", - "description": "A relative path to a JSON or JSONC file in the same repository whose configuration is used as a base. The referenced file is merged with this file using the merge logic selected by extendsMergeMode." + "description": "Relative path to a JSON or JSONC configuration to be inherited from present in the same repository." }, "extendsMergeMode": { "type": "string", @@ -26,7 +26,7 @@ "combine", "override" ], - "description": "How to merge the referenced extends file with this file. combine (default) uses the image metadata merge logic. override uses spread semantics: each top-level property set on this file fully replaces the inherited value; omitted properties keep the inherited value." + "description": "Specifies how to merge this file with the referenced extends file when extends is set. combine (default) uses image metadata merge logic. override inherits then replaces top-level properties when a key is set in this file." }, "features": { "type": "object",