From dd3a0f8abb5cf85b3b9d41bb1bef84d4d377dee9 Mon Sep 17 00:00:00 2001 From: Andrew Leng Date: Sat, 13 Dec 2025 19:26:38 -0500 Subject: [PATCH] feat: Add localFeaturesRoot property for flexible local feature paths Addresses #671 - making it easier to reuse local features in dev containers. This proposal introduces a new `localFeaturesRoot` property for `devcontainer.json` that allows users to specify a custom base directory for resolving local Feature paths, enabling Feature reuse across multiple dev container configurations. Changes: - Add proposal document: proposals/flexible-local-feature-paths.md - Update devcontainer.json reference with localFeaturesRoot property - Update Features distribution spec with expanded local feature docs - Update JSON schema with localFeaturesRoot property definition --- .../devcontainer-features-distribution.md | 108 +- docs/specs/devcontainerjson-reference.md | 151 +- proposals/flexible-local-feature-paths.md | 140 ++ schemas/devContainer.base.schema.json | 1441 ++++++++--------- 4 files changed, 968 insertions(+), 872 deletions(-) create mode 100644 proposals/flexible-local-feature-paths.md diff --git a/docs/specs/devcontainer-features-distribution.md b/docs/specs/devcontainer-features-distribution.md index ecc5f285..af338034 100644 --- a/docs/specs/devcontainer-features-distribution.md +++ b/docs/specs/devcontainer-features-distribution.md @@ -2,12 +2,12 @@ **TL;DR Check out the [quick start repository](https://github.com/devcontainers/feature-template) to get started on distributing your own Dev Container Features.** -This specification defines a pattern where community members and organizations can author and self-publish [Dev Container Features](./devcontainer-features.md). +This specification defines a pattern where community members and organizations can author and self-publish [Dev Container Features](./devcontainer-features.md). Goals include: - For Feature authors, create a "self-service" way to publish a Feature, either publicly or privately, that is not centrally controlled. -- For users, provide the ability to validate the integrity of fetched Feature assets. +- For users, provide the ability to validate the integrity of fetched Feature assets. - For users, provide the ability to pin to a particular version (absolute, or semantic version) of a Feature to allow for consistent, repeatable environments. - Provide the ability to standardize publishing such that [supporting tools](../docs/specs/supporting-tools.md) may implement their own mechanism to aid Feature discoverability as they see fit. @@ -49,19 +49,19 @@ Source code for the set follows the example file structure below: ...where `src` is a directory containing a sub-folder with the name of the Feature (e.g. `src/dotnet` or `src/go`) with at least a file named `devcontainer-feature.json` that contains the Feature metadata, and an `install.sh` script that implementing tools will use as the entrypoint to install the Feature. -Each sub-directory should be named such that it matches the `id` field of the `devcontainer-feature.json`. Other files can also be included in the Feature's sub-directory, and will be included during the [packaging step](#packaging) alongside the two required files. Any files that are not part of the Feature's sub-directory (e.g. outside of `src/dotnet`) will not be included in the [packaging step](#packaging). +Each sub-directory should be named such that it matches the `id` field of the `devcontainer-feature.json`. Other files can also be included in the Feature's sub-directory, and will be included during the [packaging step](#packaging) alongside the two required files. Any files that are not part of the Feature's sub-directory (e.g. outside of `src/dotnet`) will not be included in the [packaging step](#packaging). Optionally, a mirrored `test` directory can be included with an accompanying `test.sh` script. Implementing tools may use this to run tests against the given Feature. ## Versioning -Each Feature is individually [versioned according to the semver specification](https://semver.org/). The `version` property in the respective `devcontainer-feature.json` file is parsed to determine if the Feature should be republished. +Each Feature is individually [versioned according to the semver specification](https://semver.org/). The `version` property in the respective `devcontainer-feature.json` file is parsed to determine if the Feature should be republished. Tooling that handles publishing Features will not republish features if that exact version has already been published; however, tooling must republish major and minor versions in accordance with the semver specification. ## Packaging -Features are distributed as tarballs. The tarball contains the entire contents of the Feature sub-directory, including the `devcontainer-feature.json`, `install.sh`, and any other files in the directory. +Features are distributed as tarballs. The tarball contains the entire contents of the Feature sub-directory, including the `devcontainer-feature.json`, `install.sh`, and any other files in the directory. The tarball is named `devcontainer-feature-.tgz`, where `` is the Feature's `id` field. @@ -71,10 +71,10 @@ A reference implementation for packaging and distributing Features is provided a The `devcontainer-collection.json` is an auto-generated metadata file. -| Property | Type | Description | -| :--- | :--- | :--- | -| `sourceInformation` | object | Metadata from the implementing packaging tool. | -| `features` | array | The list of features that are contained in this collection.| +| Property | Type | Description | +| :------------------ | :----- | :---------------------------------------------------------- | +| `sourceInformation` | object | Metadata from the implementing packaging tool. | +| `features` | array | The list of features that are contained in this collection. | Each Features's `devcontainer-feature.json` metadata file is appended into the `features` top-level array. @@ -92,16 +92,16 @@ An OCI registry that implements the [OCI Artifact Distribution Specification](ht Each packaged feature is pushed to the registry following the naming convention `//[:version]`, where version is the major, minor, and patch version of the Feature, according to the semver specification. -> **Note:** `namespace` is a unique identifier for the collection of Features. There are no strict rules for `namespace`; however, one pattern is to set `namespace` equal to source repository's `/`. The `namespace` should be lowercase, following [the regex provided in the OCI specification](https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests). +> **Note:** `namespace` is a unique identifier for the collection of Features. There are no strict rules for `namespace`; however, one pattern is to set `namespace` equal to source repository's `/`. The `namespace` should be lowercase, following [the regex provided in the OCI specification](https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests). A custom media type `application/vnd.devcontainers` and `application/vnd.devcontainers.layer.v1+tar` are used as demonstrated below. -For example, the `go` Feature in the `devcontainers/features` namespace at version `1.2.3` would be pushed to the ghcr.io OCI registry. +For example, the `go` Feature in the `devcontainers/features` namespace at version `1.2.3` would be pushed to the ghcr.io OCI registry. -> **Note:** The example below uses [`oras`](https://oras.land/) for demonstration purposes. A supporting tool should directly implement the required functionality from the aforementioned OCI artifact distribution specification. +> **Note:** The example below uses [`oras`](https://oras.land/) for demonstration purposes. A supporting tool should directly implement the required functionality from the aforementioned OCI artifact distribution specification. ```bash -# ghcr.io/devcontainers/features/go:1 +# ghcr.io/devcontainers/features/go:1 REGISTRY=ghcr.io NAMESPACE=devcontainers/features FEATURE=go @@ -131,7 +131,7 @@ oras push ${REGISTRY}/${NAMESPACE}:latest \ ./devcontainer-collection.json:application/vnd.devcontainers.collection.layer.v1+json ``` -Additionally, an [annotation](https://github.com/opencontainers/image-spec/blob/main/annotations.md) named `dev.containers.metadata` should be populated on the manifest when published by an implementing tool. This annotation is the escaped JSON object of the entire `devcontainer-feature.json` as it appears during the [packaging stage](https://containers.dev/implementors/features-distribution/#packaging). +Additionally, an [annotation](https://github.com/opencontainers/image-spec/blob/main/annotations.md) named `dev.containers.metadata` should be populated on the manifest when published by an implementing tool. This annotation is the escaped JSON object of the entire `devcontainer-feature.json` as it appears during the [packaging stage](https://containers.dev/implementors/features-distribution/#packaging). An example manifest with the `dev.containers.metadata` annotation: @@ -168,21 +168,19 @@ The `.tgz` archive file must be named `devcontainer-feature-.tgz`. ### Locally referenced Features -Instead of publishing a Feature to an OCI registry, a Feature's source code may be referenced from a local folder. Locally referencing a Feature may be useful when first authoring a Feature. +Instead of publishing a Feature to an OCI registry, a Feature's source code may be referenced from a local folder. Locally referencing a Feature may be useful when first authoring a Feature or when maintaining personal Features that don't need to be published. -A local Feature is referenced in the devcontainer's `feature` object **relative to the folder containing the project's `devcontainer.json`**. +A local Feature is referenced in the devcontainer's `feature` object using a path prefixed with `./`. -Additional constraints exists when including local Features in a project: +#### Basic usage -* The project must have a `.devcontainer/` folder at the root of the [**project workspace folder**](/docs/specs/devcontainer-reference.md#project-workspace-folder). +Additional constraints exist when including local Features in a project: -* A local Feature's source code **must** be contained within a sub-folder of the `.devcontainer/ folder`. +- The sub-folder name **must** match the Feature's `id` field. -* The sub-folder name **must** match the Feature's `id` field. +- A local Feature may **not** be referenced by absolute path. -* A local Feature may **not** be referenced by absolute path. - -* The local Feature's sub-folder **must** contain at least a `devcontainer-feature.json` file and `install.sh` entrypoint script, mirroring the [previously outlined file structure](#Source-code). +- The local Feature's sub-folder **must** contain at least a `devcontainer-feature.json` file and `install.sh` entrypoint script, mirroring the [previously outlined file structure](#Source-code). The relative path is provided using unix-style path syntax (e.g. `./myFeature`) regardless of the host operating system. @@ -203,13 +201,67 @@ An example project is illustrated below: ``` ##### devcontainer.json + ```jsonc { - // ... - "features": { - "./localFeatureA": {}, - "./localFeatureB": {} - } + // ... + "features": { + "./localFeatureA": {}, + "./localFeatureB": {} + } +} +``` + +#### Reusing local Features with `localFeaturesRoot` +The `localFeaturesRoot` property allows you to specify a custom base directory for resolving local Feature paths. This enables reuse of Features across multiple dev container configurations. + +| Property | Type | Description | +| ------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `localFeaturesRoot` | string | Path (relative to the folder containing `devcontainer.json`) that serves as the base for resolving local Feature paths. When specified, all local Feature paths (those prefixed with `./`) are resolved relative to this directory. Defaults to `.` (the folder containing `devcontainer.json`). | + +##### Example: Shared Features repository + +Consider a repository structure where Features are shared across multiple dev container configurations: + +``` +dev-configs/ +├── features/ +│ ├── python-tools/ +│ │ ├── devcontainer-feature.json +│ │ └── install.sh +│ └── shell-config/ +│ ├── devcontainer-feature.json +│ └── install.sh +└── configs/ + ├── python-project/ + │ └── devcontainer.json + └── node-project/ + └── devcontainer.json +``` + +The `configs/python-project/devcontainer.json` can reference the shared Features: + +```jsonc +{ + "name": "Python Development", + "image": "mcr.microsoft.com/devcontainers/python:3", + "localFeaturesRoot": "../../features", + "features": { + "./python-tools": {}, + "./shell-config": {} + } } ``` + +When `localFeaturesRoot` is set to `"../../features"`, the Feature path `./python-tools` resolves to `dev-configs/features/python-tools`. + +##### Resolution algorithm + +When resolving a local Feature path (one starting with `./`): + +1. If `localFeaturesRoot` is specified: + - Resolve `localFeaturesRoot` relative to the folder containing `devcontainer.json` + - Resolve the Feature path relative to the resolved `localFeaturesRoot` +2. If `localFeaturesRoot` is not specified: + - Resolve the Feature path relative to the folder containing `devcontainer.json` (default behavior) diff --git a/docs/specs/devcontainerjson-reference.md b/docs/specs/devcontainerjson-reference.md index fa3893b1..9c8b66da 100644 --- a/docs/specs/devcontainerjson-reference.md +++ b/docs/specs/devcontainerjson-reference.md @@ -6,28 +6,29 @@ Metadata properties marked with a 🏷️ can be stored in the `devcontainer.met ## General devcontainer.json properties -| Property | Type | Description | -|----------|------|-------------| -| `name` | string | A name for the dev container displayed in the UI | -| `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"}` | -| `containerEnv` 🏷️ | object | A set of name-value pairs that sets or overrides environment variables for the container. Environment and [pre-defined variables](#variables-in-devcontainerjson) may be referenced in the values. For example:
`"containerEnv": { "MY_VARIABLE": "${localEnv:MY_VARIABLE}" }`
If you want to reference an existing container variable while setting this one (like updating the `PATH`), use `remoteEnv` instead.
`containerEnv` will set the variable on the Docker container itself, so all processes spawned in the container will have access to it. But it will also be static for the life of the container - you must rebuild the container to update the value.
We recommend using `containerEnv` (over `remoteEnv`) as much as possible since it allows all processes to see the variable and isn't client-specific. | -| `remoteEnv` 🏷️ | object | A set of name-value pairs that sets or overrides environment variables for the `devcontainer.json` supporting service / tool (or sub-processes like terminals) but not the container as a whole. Environment and [pre-defined variables](#variables-in-devcontainerjson) may be referenced in the values.
You may want to use `remoteEnv` (over `containerEnv`) if the value isn't static since you can update its value without having to rebuild the full container. | -| `remoteUser` 🏷️ | string | Overrides the user that `devcontainer.json` supporting services tools / runs as in the container (along with sub-processes like terminals, tasks, or debugging). Does not change the user the container as a whole runs as which can be set using `containerUser`. Defaults to the user the container as a whole is running as (often `root`).
You may learn more in the [remoteUser section below](#remoteUser). | -| `containerUser` 🏷️ | string | Overrides the user for all operations run as inside the container. Defaults to either `root` or the last `USER` instruction in the related Dockerfile used to create the image. If you want any connected tools or related processes to use a different user than the one for the container, see `remoteUser`. | -| `updateRemoteUserUID` 🏷️ | boolean | On Linux, if `containerUser` or `remoteUser` is specified, the user's UID/GID will be updated to match the local user's UID/GID to avoid permission problems with bind mounts. Defaults to `true`. | -| `userEnvProbe` 🏷️ | enum | Indicates the type of shell to use to "probe" for user environment variables to include in `devcontainer.json` supporting services' / tools' processes: `none`, `interactiveShell`, `loginShell`, or `loginInteractiveShell` (default). The specific shell used is based on the default shell for the user (typically bash). For example, bash interactive shells will typically include variables set in `/etc/bash.bashrc` and `~/.bashrc` while login shells usually include variables from `/etc/profile` and `~/.profile`. Setting this property to `loginInteractiveShell` will get variables from all four files. | -| `overrideCommand` 🏷️ | boolean | Tells `devcontainer.json` supporting services / tools whether they should run `/bin/sh -c "while sleep 1000; do :; done"` when starting the container instead of the container's default command (since the container can shut down if the default command fails). Set to `false` if the default command must run for the container to function properly. Defaults to `true` for when using an image Dockerfile and `false` when referencing a Docker Compose file. | -| `shutdownAction` 🏷️ | enum | Indicates whether `devcontainer.json` supporting tools should stop the containers when the related tool window is closed / shut down.
Values are `none`, `stopContainer` (default for image or Dockerfile), and `stopCompose` (default for Docker Compose). | -| `init` 🏷️ | boolean | Defaults to `false`. Cross-orchestrator way to indicate whether the [tini init process](https://github.com/krallin/tini) should be used to help deal with zombie processes. | -| `privileged` 🏷️ | boolean | Defaults to `false`. Cross-orchestrator way to cause the container to run in privileged mode (`--privileged`). Required for things like Docker-in-Docker, but has security implications particularly when running directly on Linux. | -| `capAdd` 🏷️ | array | Defaults to `[]`. Cross-orchestrator way to add capabilities typically disabled for a container. Most often used to add the `ptrace` capability required to debug languages like C++, Go, and Rust. For example:
`"capAdd": ["SYS_PTRACE"]` | -| `securityOpt` 🏷️ | array | Defaults to `[]`. Cross-orchestrator way to set container security options. For example:
`"securityOpt": [ "seccomp=unconfined" ]` | -| `mounts` 🏷️ | string or object | Defaults to unset. Cross-orchestrator way to add additional mounts to a container. Each value is a string that accepts the same values as the [Docker CLI `--mount` flag](https://docs.docker.com/engine/reference/commandline/run/#mount). Environment and [pre-defined variables](#variables-in-devcontainerjson) may be referenced in the value. For example:
`"mounts": [{ "source": "dind-var-lib-docker", "target": "/var/lib/docker", "type": "volume" }]` | -| `features` | object | An object of [Dev Container Feature IDs](https://containers.dev/features) and related options to be added into your primary container. The specific options that are available varies by feature, so see its documentation for additional details. For example:
`"features": { "ghcr.io/devcontainers/features/github-cli": {} }` | -| `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) | +| Property | Type | Description | +| ----------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | string | A name for the dev container displayed in the UI | +| `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"}` | +| `containerEnv` 🏷️ | object | A set of name-value pairs that sets or overrides environment variables for the container. Environment and [pre-defined variables](#variables-in-devcontainerjson) may be referenced in the values. For example:
`"containerEnv": { "MY_VARIABLE": "${localEnv:MY_VARIABLE}" }`
If you want to reference an existing container variable while setting this one (like updating the `PATH`), use `remoteEnv` instead.
`containerEnv` will set the variable on the Docker container itself, so all processes spawned in the container will have access to it. But it will also be static for the life of the container - you must rebuild the container to update the value.
We recommend using `containerEnv` (over `remoteEnv`) as much as possible since it allows all processes to see the variable and isn't client-specific. | +| `remoteEnv` 🏷️ | object | A set of name-value pairs that sets or overrides environment variables for the `devcontainer.json` supporting service / tool (or sub-processes like terminals) but not the container as a whole. Environment and [pre-defined variables](#variables-in-devcontainerjson) may be referenced in the values.
You may want to use `remoteEnv` (over `containerEnv`) if the value isn't static since you can update its value without having to rebuild the full container. | +| `remoteUser` 🏷️ | string | Overrides the user that `devcontainer.json` supporting services tools / runs as in the container (along with sub-processes like terminals, tasks, or debugging). Does not change the user the container as a whole runs as which can be set using `containerUser`. Defaults to the user the container as a whole is running as (often `root`).
You may learn more in the [remoteUser section below](#remoteUser). | +| `containerUser` 🏷️ | string | Overrides the user for all operations run as inside the container. Defaults to either `root` or the last `USER` instruction in the related Dockerfile used to create the image. If you want any connected tools or related processes to use a different user than the one for the container, see `remoteUser`. | +| `updateRemoteUserUID` 🏷️ | boolean | On Linux, if `containerUser` or `remoteUser` is specified, the user's UID/GID will be updated to match the local user's UID/GID to avoid permission problems with bind mounts. Defaults to `true`. | +| `userEnvProbe` 🏷️ | enum | Indicates the type of shell to use to "probe" for user environment variables to include in `devcontainer.json` supporting services' / tools' processes: `none`, `interactiveShell`, `loginShell`, or `loginInteractiveShell` (default). The specific shell used is based on the default shell for the user (typically bash). For example, bash interactive shells will typically include variables set in `/etc/bash.bashrc` and `~/.bashrc` while login shells usually include variables from `/etc/profile` and `~/.profile`. Setting this property to `loginInteractiveShell` will get variables from all four files. | +| `overrideCommand` 🏷️ | boolean | Tells `devcontainer.json` supporting services / tools whether they should run `/bin/sh -c "while sleep 1000; do :; done"` when starting the container instead of the container's default command (since the container can shut down if the default command fails). Set to `false` if the default command must run for the container to function properly. Defaults to `true` for when using an image Dockerfile and `false` when referencing a Docker Compose file. | +| `shutdownAction` 🏷️ | enum | Indicates whether `devcontainer.json` supporting tools should stop the containers when the related tool window is closed / shut down.
Values are `none`, `stopContainer` (default for image or Dockerfile), and `stopCompose` (default for Docker Compose). | +| `init` 🏷️ | boolean | Defaults to `false`. Cross-orchestrator way to indicate whether the [tini init process](https://github.com/krallin/tini) should be used to help deal with zombie processes. | +| `privileged` 🏷️ | boolean | Defaults to `false`. Cross-orchestrator way to cause the container to run in privileged mode (`--privileged`). Required for things like Docker-in-Docker, but has security implications particularly when running directly on Linux. | +| `capAdd` 🏷️ | array | Defaults to `[]`. Cross-orchestrator way to add capabilities typically disabled for a container. Most often used to add the `ptrace` capability required to debug languages like C++, Go, and Rust. For example:
`"capAdd": ["SYS_PTRACE"]` | +| `securityOpt` 🏷️ | array | Defaults to `[]`. Cross-orchestrator way to set container security options. For example:
`"securityOpt": [ "seccomp=unconfined" ]` | +| `mounts` 🏷️ | string or object | Defaults to unset. Cross-orchestrator way to add additional mounts to a container. Each value is a string that accepts the same values as the [Docker CLI `--mount` flag](https://docs.docker.com/engine/reference/commandline/run/#mount). Environment and [pre-defined variables](#variables-in-devcontainerjson) may be referenced in the value. For example:
`"mounts": [{ "source": "dind-var-lib-docker", "target": "/var/lib/docker", "type": "volume" }]` | +| `features` | object | An object of [Dev Container Feature IDs](https://containers.dev/features) and related options to be added into your primary container. The specific options that are available varies by feature, so see its documentation for additional details. For example:
`"features": { "ghcr.io/devcontainers/features/github-cli": {} }` | +| `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" ]` | +| `localFeaturesRoot` | string | Path (relative to the folder containing `devcontainer.json`) that serves as the base directory for resolving local Feature paths. When specified, all local Feature paths (those prefixed with `./`) are resolved relative to this directory instead of the folder containing `devcontainer.json`. Defaults to `.` (current behavior). For example:
`"localFeaturesRoot": "../shared-features"` | +| `customizations` 🏷️ | object | Product specific properties, defined in [supporting tools](supporting-tools.md) | ## Scenario specific properties @@ -35,28 +36,28 @@ The focus of `devcontainer.json` is to describe how to enrich a container for th ### Image or Dockerfile specific properties -| Property | Type | Description | -|----------|------|-------------| -| `image` | string | **Required** when using an image. The name of an image in a container registry ([DockerHub](https://hub.docker.com), [GitHub Container Registry](https://docs.github.com/packages/guides/about-github-container-registry), [Azure Container Registry](https://azure.microsoft.com/services/container-registry/)) that `devcontainer.json` supporting services / tools should use to create the dev container. | -| `build.dockerfile` | string |**Required** when using a Dockerfile. The location of a [Dockerfile](https://docs.docker.com/engine/reference/builder/) that defines the contents of the container. The path is relative to the `devcontainer.json` file. | -| `build.context` | string | Path that the Docker build should be run from relative to `devcontainer.json`. For example, a value of `".."` would allow you to reference content in sibling directories. Defaults to `"."`. | -| `build.args` | Object | A set of name-value pairs containing [Docker image build arguments](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg) that should be passed when building a Dockerfile. Environment and [pre-defined variables](#variables-in-devcontainerjson) may be referenced in the values. Defaults to not set. For example: `"build": { "args": { "MYARG": "MYVALUE", "MYARGFROMENVVAR": "${localEnv:VARIABLE_NAME}" } }` | -| `build.options` | array | An array of [Docker image build options](https://docs.docker.com/engine/reference/commandline/build/#options) that should be passed to the build command when building a Dockerfile. Defaults to `[]`. For example: `"build": { "options": [ "--add-host=host.docker.internal:host-gateway" ] }` | -| `build.target` | string | A string that specifies a [Docker image build target](https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target) that should be passed when building a Dockerfile. Defaults to not set. For example: `"build": { "target": "development" }` | -| `build.cacheFrom` | string,
array | A string or array of strings that specify one or more images to use as caches when building the image. Cached image identifiers are passed to the `docker build` command with `--cache-from`. | -| `appPort` | integer,
string,
array | In most cases, we recommend using the new [forwardPorts property](#general-devcontainerjson-properties). This property accepts a port or array of ports that should be published locally when the container is running. Unlike `forwardPorts`, your application may need to listen on all interfaces (`0.0.0.0`) not just `localhost` for it to be available externally. Defaults to `[]`.
Learn more about publishing vs forwarding ports [here](#publishing-vs-forwarding-ports).
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array properties. | -| `workspaceMount` | string | Requires `workspaceFolder` be set as well. Overrides the default local mount point for the workspace when the container is created. Supports the same values as the [Docker CLI `--mount` flag](https://docs.docker.com/engine/reference/commandline/run/#add-bind-mounts-or-volumes-using-the---mount-flag). Environment and [pre-defined variables](#variables-in-devcontainerjson) may be referenced in the value. For example:
`"workspaceMount": "source=${localWorkspaceFolder}/sub-folder,target=/workspace,type=bind,consistency=cached", "workspaceFolder": "/workspace"` | -| `workspaceFolder` | string | Requires `workspaceMount` be set. Sets the default path that `devcontainer.json` supporting services / tools should open when connecting to the container. Defaults to the automatic source code mount location. | -| `runArgs` | array | An array of [Docker CLI arguments](https://docs.docker.com/engine/reference/commandline/run/) that should be used when running the container. Defaults to `[]`. For example, this allows ptrace based debuggers like C++ to work in the container:
`"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ]` . | +| Property | Type | Description | +| ------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `image` | string | **Required** when using an image. The name of an image in a container registry ([DockerHub](https://hub.docker.com), [GitHub Container Registry](https://docs.github.com/packages/guides/about-github-container-registry), [Azure Container Registry](https://azure.microsoft.com/services/container-registry/)) that `devcontainer.json` supporting services / tools should use to create the dev container. | +| `build.dockerfile` | string | **Required** when using a Dockerfile. The location of a [Dockerfile](https://docs.docker.com/engine/reference/builder/) that defines the contents of the container. The path is relative to the `devcontainer.json` file. | +| `build.context` | string | Path that the Docker build should be run from relative to `devcontainer.json`. For example, a value of `".."` would allow you to reference content in sibling directories. Defaults to `"."`. | +| `build.args` | Object | A set of name-value pairs containing [Docker image build arguments](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg) that should be passed when building a Dockerfile. Environment and [pre-defined variables](#variables-in-devcontainerjson) may be referenced in the values. Defaults to not set. For example: `"build": { "args": { "MYARG": "MYVALUE", "MYARGFROMENVVAR": "${localEnv:VARIABLE_NAME}" } }` | +| `build.options` | array | An array of [Docker image build options](https://docs.docker.com/engine/reference/commandline/build/#options) that should be passed to the build command when building a Dockerfile. Defaults to `[]`. For example: `"build": { "options": [ "--add-host=host.docker.internal:host-gateway" ] }` | +| `build.target` | string | A string that specifies a [Docker image build target](https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target) that should be passed when building a Dockerfile. Defaults to not set. For example: `"build": { "target": "development" }` | +| `build.cacheFrom` | string,
array | A string or array of strings that specify one or more images to use as caches when building the image. Cached image identifiers are passed to the `docker build` command with `--cache-from`. | +| `appPort` | integer,
string,
array | In most cases, we recommend using the new [forwardPorts property](#general-devcontainerjson-properties). This property accepts a port or array of ports that should be published locally when the container is running. Unlike `forwardPorts`, your application may need to listen on all interfaces (`0.0.0.0`) not just `localhost` for it to be available externally. Defaults to `[]`.
Learn more about publishing vs forwarding ports [here](#publishing-vs-forwarding-ports).
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array properties. | +| `workspaceMount` | string | Requires `workspaceFolder` be set as well. Overrides the default local mount point for the workspace when the container is created. Supports the same values as the [Docker CLI `--mount` flag](https://docs.docker.com/engine/reference/commandline/run/#add-bind-mounts-or-volumes-using-the---mount-flag). Environment and [pre-defined variables](#variables-in-devcontainerjson) may be referenced in the value. For example:
`"workspaceMount": "source=${localWorkspaceFolder}/sub-folder,target=/workspace,type=bind,consistency=cached", "workspaceFolder": "/workspace"` | +| `workspaceFolder` | string | Requires `workspaceMount` be set. Sets the default path that `devcontainer.json` supporting services / tools should open when connecting to the container. Defaults to the automatic source code mount location. | +| `runArgs` | array | An array of [Docker CLI arguments](https://docs.docker.com/engine/reference/commandline/run/) that should be used when running the container. Defaults to `[]`. For example, this allows ptrace based debuggers like C++ to work in the container:
`"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ]` . | ### Docker Compose specific properties -| Property | Type | Description | -|----------|------|-------------| +| Property | Type | Description | +| ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `dockerComposeFile` | string,
array | **Required** when [using Docker Compose](https://docs.docker.com/compose/). Path or an ordered list of paths to Docker Compose files relative to the `devcontainer.json` file. Using an array is useful [when extending your Docker Compose configuration](https://docs.docker.com/compose/extends/#multiple-compose-files). The order of the array matters since the contents of later files can override values set in previous ones.
The default `.env` file is picked up from the root of the project, but you can use `env_file` in your Docker Compose file to specify an alternate location. | -| `service` | string | **Required** when [using Docker Compose](https://docs.docker.com/compose/). The name of the service `devcontainer.json` supporting services / tools should connect to once running. | -| `runServices` | array | An array of services in your Docker Compose configuration that should be started by `devcontainer.json` supporting services / tools. These will also be stopped when you disconnect unless `"shutdownAction"` is `"none"`. Defaults to all services. | -| `workspaceFolder` | string | Sets the default path that `devcontainer.json` supporting services / tools should open when connecting to the container (which is often the path to a volume mount where the source code can be found in the container). Defaults to `"/"`. | +| `service` | string | **Required** when [using Docker Compose](https://docs.docker.com/compose/). The name of the service `devcontainer.json` supporting services / tools should connect to once running. | +| `runServices` | array | An array of services in your Docker Compose configuration that should be started by `devcontainer.json` supporting services / tools. These will also be stopped when you disconnect unless `"shutdownAction"` is `"none"`. Defaults to all services. | +| `workspaceFolder` | string | Sets the default path that `devcontainer.json` supporting services / tools should open when connecting to the container (which is often the path to a volume mount where the source code can be found in the container). Defaults to `"/"`. | ## Tool-specific properties @@ -66,15 +67,15 @@ While most properties apply to any `devcontainer.json` supporting tool or servic When creating or working with a dev container, you may need different commands to be run at different points in the container's lifecycle. The table below lists a set of command properties you can use to update what the container's contents in the order in which they are run (for example, `onCreateCommand` will run after `initializeCommand`). Each command property is an string or list of command arguments that should execute from the `workspaceFolder`. -| Property | Type | Description | -|----------|------|-------------| -| `initializeCommand` | string,
array,
object | A command string or list of command arguments to run on the **host machine** during initialization, including during container creation and on subsequent starts. The command may run more than once during a given session.

⚠️ The command is run wherever the source code is located on the host. For cloud services, this is in the cloud.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | -| `onCreateCommand` 🏷️ | string,
array,
object | This command is the first of three (along with `updateContentCommand` and `postCreateCommand`) that finalizes container setup when a dev container is created. It and subsequent commands execute **inside** the container immediately after it has started for the first time.

Cloud services can use this command when caching or prebuilding a container. This means that it will not typically have access to user-scoped assets or secrets.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | -| `updateContentCommand` 🏷️ | string,
array,
object | This command is the second of three that finalizes container setup when a dev container is created. It executes inside the container after `onCreateCommand` whenever new content is available in the source tree during the creation process.

It will execute at least once, but cloud services will also periodically execute the command to refresh cached or prebuilt containers. Like cloud services using `onCreateCommand`, it can only take advantage of repository and org scoped secrets or permissions.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | -| `postCreateCommand` 🏷️ | string,
array,
object | This command is the last of three that finalizes container setup when a dev container is created. It happens after `updateContentCommand` and once the dev container has been assigned to a user for the first time.

Cloud services can use this command to take advantage of user specific secrets and permissions.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | -| `postStartCommand` 🏷️ | string,
array,
object | A command to run each time the container is successfully started.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | -| `postAttachCommand` 🏷️ | string,
array,
object | A command to run each time a tool has successfully attached to the container.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | -| `waitFor` 🏷️ | enum | An enum that specifies the command any tool should wait for before connecting. Defaults to `updateContentCommand`. This allows you to use `onCreateCommand` or `updateContentCommand` for steps that must happen before `devcontainer.json` supporting tools connect while still using `postCreateCommand` for steps that can happen behind the scenes afterwards. | +| Property | Type | Description | +| ------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `initializeCommand` | string,
array,
object | A command string or list of command arguments to run on the **host machine** during initialization, including during container creation and on subsequent starts. The command may run more than once during a given session.

⚠️ The command is run wherever the source code is located on the host. For cloud services, this is in the cloud.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | +| `onCreateCommand` 🏷️ | string,
array,
object | This command is the first of three (along with `updateContentCommand` and `postCreateCommand`) that finalizes container setup when a dev container is created. It and subsequent commands execute **inside** the container immediately after it has started for the first time.

Cloud services can use this command when caching or prebuilding a container. This means that it will not typically have access to user-scoped assets or secrets.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | +| `updateContentCommand` 🏷️ | string,
array,
object | This command is the second of three that finalizes container setup when a dev container is created. It executes inside the container after `onCreateCommand` whenever new content is available in the source tree during the creation process.

It will execute at least once, but cloud services will also periodically execute the command to refresh cached or prebuilt containers. Like cloud services using `onCreateCommand`, it can only take advantage of repository and org scoped secrets or permissions.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | +| `postCreateCommand` 🏷️ | string,
array,
object | This command is the last of three that finalizes container setup when a dev container is created. It happens after `updateContentCommand` and once the dev container has been assigned to a user for the first time.

Cloud services can use this command to take advantage of user specific secrets and permissions.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | +| `postStartCommand` 🏷️ | string,
array,
object | A command to run each time the container is successfully started.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | +| `postAttachCommand` 🏷️ | string,
array,
object | A command to run each time a tool has successfully attached to the container.
Note that the array syntax will execute the command without a shell. You can [learn more](#formatting-string-vs-array-properties) about formatting string vs array vs object properties. | +| `waitFor` 🏷️ | enum | An enum that specifies the command any tool should wait for before connecting. Defaults to `updateContentCommand`. This allows you to use `onCreateCommand` or `updateContentCommand` for steps that must happen before `devcontainer.json` supporting tools connect while still using `postCreateCommand` for steps that can happen behind the scenes afterwards. | For each command property, if the value is a single string, it will be run in `/bin/sh`. Use `&&` in a string to execute multiple commands. For example, `"yarn install"` or `"apt-get update && apt-get install -y curl"`. The array syntax `["yarn", "install"]` will invoke the command (in this case `yarn`) directly without using a shell. Each fires after your source code has been mounted, so you can also run shell scripts from your source tree. For example: `bash scripts/install-dev-tools.sh` @@ -84,32 +85,33 @@ If one of the lifecycle scripts fails, any subsequent scripts will not be execut While `devcontainer.json` does not focus on hardware or VM provisioning, it can be useful to know your container's minimum RAM, CPU, and storage requirements. This is what the `hostRequirements` properties allow you to do. Cloud services can use these properties to automatically default to the best compute option available, while in other cases, you will be presented with a warning if the requirements are not met. -| Property | Type | Description | -|----------|------|-------------| -| `hostRequirements.cpus` 🏷️ | integer | Indicates the minimum required number of CPUs / virtual CPUs / cores. For example: `"hostRequirements": {"cpus": 2}` | -| `hostRequirements.memory` 🏷️ | string | A string indicating minimum memory requirements with a `tb`, `gb`, `mb`, or `kb` suffix. For example, `"hostRequirements": {"memory": "4gb"}` | -| `hostRequirements.storage` 🏷️ | string | A string indicating minimum storage requirements with a `tb`, `gb`, `mb`, or `kb` suffix. For example, `"hostRequirements": {"storage": "32gb"}` | +| Property | Type | Description | +| ----------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `hostRequirements.cpus` 🏷️ | integer | Indicates the minimum required number of CPUs / virtual CPUs / cores. For example: `"hostRequirements": {"cpus": 2}` | +| `hostRequirements.memory` 🏷️ | string | A string indicating minimum memory requirements with a `tb`, `gb`, `mb`, or `kb` suffix. For example, `"hostRequirements": {"memory": "4gb"}` | +| `hostRequirements.storage` 🏷️ | string | A string indicating minimum storage requirements with a `tb`, `gb`, `mb`, or `kb` suffix. For example, `"hostRequirements": {"storage": "32gb"}` | ## Port attributes The `portsAttributes` and `otherPortsAttributes` properties allow you to map default port options for one or more manually or automatically forwarded ports. The following is a list of options that can be set in the configuration object assigned to the property. -| Property | Type | Description | -|----------|------|-------------| -| `label` 🏷️ | string | Display name for the port in the ports view. Defaults to not set. | -| `protocol` 🏷️ | enum | Controls protocol handling for forwarded ports. When not set, the port is assumed to be a raw TCP stream which, if forwarded to `localhost`, supports any number of protocols. However, if the port is forwarded to a web URL (e.g. from a cloud service on the web), only HTTP ports in the container are supported. Setting this property to `https` alters handling by ignoring any SSL/TLS certificates present when communicating on the port and using the correct certificate for the forwarded URL instead (e.g. `https://*.githubpreview.dev`). If set to `http`, processing is the same as if the protocol is not set. Defaults to not set. | -| `onAutoForward` 🏷️ | enum | Controls what should happen when a port is auto-forwarded once you've connected to the container. `notify` is the default, and a notification will appear when the port is auto-forwarded. If set to `openBrowser`, the port will be opened in the system's default browser. A value of `openBrowserOnce` will open the browser only once. `openPreview` will open the URL in `devcontainer.json` supporting services' / tools' embedded preview browser. A value of `silent` will forward the port, but take no further action. A value of `ignore` means that this port should not be auto-forwarded at all. | -| `requireLocalPort` 🏷️ | boolean | Dictates when port forwarding is required to map the port in the container to the same port locally or not. If set to `false`, the `devcontainer.json` supporting services / tools will attempt to use the specified port forward to `localhost`, and silently map to a different one if it is unavailable. If set to `true`, you will be notified if it is not possible to use the same port. Defaults to `false`. | -| `elevateIfNeeded` 🏷️ | boolean | Forwarding low ports like 22, 80, or 443 to `localhost` on the same port from `devcontainer.json` supporting services / tools may require elevated permissions on certain operating systems. Setting this property to `true` will automatically try to elevate the `devcontainer.json` supporting tool's permissions in this situation. Defaults to `false`. | +| Property | Type | Description | +| --------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `label` 🏷️ | string | Display name for the port in the ports view. Defaults to not set. | +| `protocol` 🏷️ | enum | Controls protocol handling for forwarded ports. When not set, the port is assumed to be a raw TCP stream which, if forwarded to `localhost`, supports any number of protocols. However, if the port is forwarded to a web URL (e.g. from a cloud service on the web), only HTTP ports in the container are supported. Setting this property to `https` alters handling by ignoring any SSL/TLS certificates present when communicating on the port and using the correct certificate for the forwarded URL instead (e.g. `https://*.githubpreview.dev`). If set to `http`, processing is the same as if the protocol is not set. Defaults to not set. | +| `onAutoForward` 🏷️ | enum | Controls what should happen when a port is auto-forwarded once you've connected to the container. `notify` is the default, and a notification will appear when the port is auto-forwarded. If set to `openBrowser`, the port will be opened in the system's default browser. A value of `openBrowserOnce` will open the browser only once. `openPreview` will open the URL in `devcontainer.json` supporting services' / tools' embedded preview browser. A value of `silent` will forward the port, but take no further action. A value of `ignore` means that this port should not be auto-forwarded at all. | +| `requireLocalPort` 🏷️ | boolean | Dictates when port forwarding is required to map the port in the container to the same port locally or not. If set to `false`, the `devcontainer.json` supporting services / tools will attempt to use the specified port forward to `localhost`, and silently map to a different one if it is unavailable. If set to `true`, you will be notified if it is not possible to use the same port. Defaults to `false`. | +| `elevateIfNeeded` 🏷️ | boolean | Forwarding low ports like 22, 80, or 443 to `localhost` on the same port from `devcontainer.json` supporting services / tools may require elevated permissions on certain operating systems. Setting this property to `true` will automatically try to elevate the `devcontainer.json` supporting tool's permissions in this situation. Defaults to `false`. | ## Formatting string vs. array properties The format of certain properties will vary depending on the involvement of a shell. -`postCreateCommand`, `postStartCommand`, `postAttachCommand`, and `initializeCommand` all have 3 types: -* Array: Passed to the OS for execution without going through a shell -* String: Goes through a shell (it needs to be parsed into command and arguments) -* Object: All lifecycle scripts have been extended to support `object` types to allow for [parallel execution](../specs/devcontainer-reference.md/#parallel-lifecycle-script-execution) +`postCreateCommand`, `postStartCommand`, `postAttachCommand`, and `initializeCommand` all have 3 types: + +- Array: Passed to the OS for execution without going through a shell +- String: Goes through a shell (it needs to be parsed into command and arguments) +- Object: All lifecycle scripts have been extended to support `object` types to allow for [parallel execution](../specs/devcontainer-reference.md/#parallel-lifecycle-script-execution) `runArgs` only has the array type. Using `runArgs` via a typical command line, you'll need single quotes if the shell runs into parameters with spaces. However, these single quotes aren't passed on to the executable. Thus, in your `devcontainer.json`, you'd follow the array format and leave out the single quotes: @@ -150,21 +152,20 @@ Finally, you may use an object format: Variables can be referenced in certain string values in `devcontainer.json` in the following format: **${variableName}**. The following is a list of available variables you can use. -| Variable | Properties | Description | -|----------|---------|----------------------| -| `${localEnv:VARIABLE_NAME}` | Any | Value of an environment variable on the **host machine** (in the examples below, called `VARIABLE_NAME`). Unset variables are left blank.

⚠️ Clients (like VS Code) may need to be **restarted** to pick up newly set variables.

⚠️ For a cloud service, the host is in the cloud rather than your local machine.

**Examples**

**1.** Set a variable containing your local home folder on Linux / macOS or the user folder on Windows:
`"remoteEnv": { "LOCAL_USER_PATH": "${localEnv:HOME}${localEnv:USERPROFILE}" }`.

A default value for when the environment variable is not set can be given with `${localEnv:VARIABLE_NAME:default_value}`.

**2.** In modern versions of macOS, default configurations allow setting local variables with the command `echo 'export VARIABLE_NAME=my-value' >> ~/.zshenv`. | -| `${containerEnv:VARIABLE_NAME}` | `remoteEnv` | Value of an existing environment variable inside the container once it is up and running (in this case, called `VARIABLE_NAME`). For example:
`"remoteEnv": { "PATH": "${containerEnv:PATH}:/some/other/path" }`

A default value for when the environment variable is not set can be given with `${containerEnv:VARIABLE_NAME:default_value}`. | -| `${localWorkspaceFolder}` | Any | Path of the local folder that was opened in the `devcontainer.json` supporting service / tool (that contains `.devcontainer/devcontainer.json`). | -| `${containerWorkspaceFolder}` | Any | The path that the workspaces files can be found in the container. | -| `${localWorkspaceFolderBasename}` | Any | Name of the local folder that was opened in the `devcontainer.json` supporting service / tool (that contains `.devcontainer/devcontainer.json`). | -| `${containerWorkspaceFolderBasename}` | Any | Name of the folder where the workspace files can be found in the container. | -| `${devcontainerId}` | Any | Allow features to refer to an identifier that is unique to the dev container they are installed into and that is stable across rebuilds.
The properties supporting it in devcontainer.json are: `name`, `runArgs`, `initializeCommand`, `onCreateCommand`, `updateContentCommand`, `postCreateCommand`, `postStartCommand`, `postAttachCommand`, `workspaceFolder`, `workspaceMount`, `mounts`, `containerEnv`, `remoteEnv`, `containerUser`, `remoteUser`, and `customizations`. | +| Variable | Properties | Description | +| ------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `${localEnv:VARIABLE_NAME}` | Any | Value of an environment variable on the **host machine** (in the examples below, called `VARIABLE_NAME`). Unset variables are left blank.

⚠️ Clients (like VS Code) may need to be **restarted** to pick up newly set variables.

⚠️ For a cloud service, the host is in the cloud rather than your local machine.

**Examples**

**1.** Set a variable containing your local home folder on Linux / macOS or the user folder on Windows:
`"remoteEnv": { "LOCAL_USER_PATH": "${localEnv:HOME}${localEnv:USERPROFILE}" }`.

A default value for when the environment variable is not set can be given with `${localEnv:VARIABLE_NAME:default_value}`.

**2.** In modern versions of macOS, default configurations allow setting local variables with the command `echo 'export VARIABLE_NAME=my-value' >> ~/.zshenv`. | +| `${containerEnv:VARIABLE_NAME}` | `remoteEnv` | Value of an existing environment variable inside the container once it is up and running (in this case, called `VARIABLE_NAME`). For example:
`"remoteEnv": { "PATH": "${containerEnv:PATH}:/some/other/path" }`

A default value for when the environment variable is not set can be given with `${containerEnv:VARIABLE_NAME:default_value}`. | +| `${localWorkspaceFolder}` | Any | Path of the local folder that was opened in the `devcontainer.json` supporting service / tool (that contains `.devcontainer/devcontainer.json`). | +| `${containerWorkspaceFolder}` | Any | The path that the workspaces files can be found in the container. | +| `${localWorkspaceFolderBasename}` | Any | Name of the local folder that was opened in the `devcontainer.json` supporting service / tool (that contains `.devcontainer/devcontainer.json`). | +| `${containerWorkspaceFolderBasename}` | Any | Name of the folder where the workspace files can be found in the container. | +| `${devcontainerId}` | Any | Allow features to refer to an identifier that is unique to the dev container they are installed into and that is stable across rebuilds.
The properties supporting it in devcontainer.json are: `name`, `runArgs`, `initializeCommand`, `onCreateCommand`, `updateContentCommand`, `postCreateCommand`, `postStartCommand`, `postAttachCommand`, `workspaceFolder`, `workspaceMount`, `mounts`, `containerEnv`, `remoteEnv`, `containerUser`, `remoteUser`, and `customizations`. | ## Schema You can see the dev container schema [here](https://github.com/devcontainers/spec/blob/main/schemas/devContainer.base.schema.json). - ## Publishing vs forwarding ports Docker has the concept of "publishing" ports when the container is created. Published ports behave very much like ports you make available to your local network. If your application only accepts calls from `localhost`, it will reject connections from published ports just as your local machine would for network calls. Forwarded ports, on the other hand, actually look like `localhost` to the application. diff --git a/proposals/flexible-local-feature-paths.md b/proposals/flexible-local-feature-paths.md new file mode 100644 index 00000000..a9fa19e3 --- /dev/null +++ b/proposals/flexible-local-feature-paths.md @@ -0,0 +1,140 @@ +# Flexible Local Feature Paths + +ref: https://github.com/devcontainers/spec/issues/671 + +## Motivation + +Users who want to manage a collection of their own common dev container configurations and Features in a single repository face significant friction with the current specification. The current model requires local Features to be stored within the `.devcontainer/` folder at the project workspace folder root. + +**Problems with the current approach:** + +1. **Cannot share Features across configurations** - Local Features must be duplicated in each `.devcontainer/` folder +2. **Incompatible with VS Code's alternative configuration folders** - When using alternative dev container configuration paths, local Features in those locations aren't found +3. **Workarounds are cumbersome** - Users resort to hardlinking files or Git submodules to work around restrictions + +**Benefits of local Features** that users want to preserve: + +- Avoid building/publishing full-blown Feature packages for personal use +- Changes take effect on next build without additional steps +- Keep Features private without usage quotas/hosting costs + +## Goal + +Provide a way for users to specify a custom base directory for resolving local Feature paths, enabling Feature reuse across dev container configurations. + +## Proposal: Add `localFeaturesRoot` Property + +Add a new `localFeaturesRoot` property to `devcontainer.json` that specifies the base directory for resolving local Feature paths. + +### Property Definition + +| Property | Type | Description | +| ------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `localFeaturesRoot` | string | Optional path (relative to the folder containing `devcontainer.json`) that serves as the base for resolving local Feature paths. When specified, all local Feature paths (those prefixed with `./`) are resolved relative to this directory instead of the folder containing `devcontainer.json`. Defaults to `.` (current behavior). | + +### Constraints + +- The path must be relative (no absolute paths allowed) +- The resolved path must exist and be accessible during the build +- The path may use `..` to traverse upward from the `devcontainer.json` location + +### Examples + +#### Example 1: Shared Features in Parent Directory + +Repository structure: + +``` +my-workspace/ +├── shared-features/ +│ └── my-shell-config/ +│ ├── devcontainer-feature.json +│ └── install.sh +└── projects/ + └── project-a/ + └── .devcontainer/ + └── devcontainer.json +``` + +`devcontainer.json`: + +```jsonc +{ + "name": "Project A", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "localFeaturesRoot": "../../../shared-features", + "features": { + "./my-shell-config": {} // Resolves to shared-features/my-shell-config + } +} +``` + +#### Example 2: Centralized Feature Repository + +Repository structure: + +``` +dev-container-configs/ +├── features/ +│ ├── python-tools/ +│ │ ├── devcontainer-feature.json +│ │ └── install.sh +│ └── node-tools/ +│ ├── devcontainer-feature.json +│ └── install.sh +└── configs/ + ├── python-project/ + │ └── devcontainer.json + └── node-project/ + └── devcontainer.json +``` + +`configs/python-project/devcontainer.json`: + +```jsonc +{ + "name": "Python Development", + "image": "mcr.microsoft.com/devcontainers/python:3", + "localFeaturesRoot": "../../features", + "features": { + "./python-tools": {} + } +} +``` + +`configs/node-project/devcontainer.json`: + +```jsonc +{ + "name": "Node Development", + "image": "mcr.microsoft.com/devcontainers/javascript-node:20", + "localFeaturesRoot": "../../features", + "features": { + "./node-tools": {} + } +} +``` + +### Backward Compatibility + +This proposal is fully backward compatible: + +- The `localFeaturesRoot` property is optional +- When not specified, behavior is identical to current spec (local Features resolve relative to the `devcontainer.json` folder) +- Existing configurations continue to work without modification + +### Resolution Algorithm + +When resolving a local Feature path (one starting with `./`): + +1. If `localFeaturesRoot` is specified: + - Resolve `localFeaturesRoot` relative to the folder containing `devcontainer.json` + - Resolve the Feature path relative to the resolved `localFeaturesRoot` +2. If `localFeaturesRoot` is not specified: + - Resolve the Feature path relative to the folder containing `devcontainer.json` (current behavior) + +### Security Considerations + +- **No absolute paths**: Requiring relative paths prevents access to arbitrary filesystem locations +- **Build-time resolution**: Paths are resolved at build time within the context of the dev container build +- **User-controlled scope**: Users have full control over what paths are accessible via this property diff --git a/schemas/devContainer.base.schema.json b/schemas/devContainer.base.schema.json index 86709eca..18174f25 100644 --- a/schemas/devContainer.base.schema.json +++ b/schemas/devContainer.base.schema.json @@ -1,771 +1,674 @@ { - "$schema": "https://json-schema.org/draft/2019-09/schema", - "description": "Defines a dev container", - "allowComments": true, - "allowTrailingCommas": false, - "definitions": { - "devContainerCommon": { - "type": "object", - "properties": { - "$schema": { - "type": "string", - "format": "uri", - "description": "The JSON schema of the `devcontainer.json` file." - }, - "name": { - "type": "string", - "description": "A name for the dev container which can be displayed to the user." - }, - "features": { - "type": "object", - "description": "Features to add to the dev container.", - "properties": { - "fish": { - "deprecated": true, - "deprecationMessage": "Legacy feature not supported. Please check https://containers.dev/features for replacements." - }, - "maven": { - "deprecated": true, - "deprecationMessage": "Legacy feature will be removed in the future. Please check https://containers.dev/features for replacements. E.g., `ghcr.io/devcontainers/features/java` has an option to install Maven." - }, - "gradle": { - "deprecated": true, - "deprecationMessage": "Legacy feature will be removed in the future. Please check https://containers.dev/features for replacements. E.g., `ghcr.io/devcontainers/features/java` has an option to install Gradle." - }, - "homebrew": { - "deprecated": true, - "deprecationMessage": "Legacy feature not supported. Please check https://containers.dev/features for replacements." - }, - "jupyterlab": { - "deprecated": true, - "deprecationMessage": "Legacy feature will be removed in the future. Please check https://containers.dev/features for replacements. E.g., `ghcr.io/devcontainers/features/python` has an option to install JupyterLab." - } - }, - "additionalProperties": true - }, - "overrideFeatureInstallOrder": { - "type": "array", - "description": "Array consisting of the Feature id (without the semantic version) of Features in the order the user wants them to be installed.", - "items": { - "type": "string" - } - }, - "secrets": { - "type": "object", - "description": "Recommended secrets for this dev container. Recommendations are provided as environment variable keys with optional metadata.", - "patternProperties": { - "^[a-zA-Z_][a-zA-Z0-9_]*$": { - "type": "object", - "description": "Environment variable keys following unix-style naming conventions. eg: ^[a-zA-Z_][a-zA-Z0-9_]*$", - "properties": { - "description": { - "type": "string", - "description": "A description of the secret." - }, - "documentationUrl": { - "type": "string", - "format": "uri", - "description": "A URL to documentation about the secret." - } - }, - "additionalProperties": false - }, - "additionalProperties": false - }, - "additionalProperties": false - }, - "forwardPorts": { - "type": "array", - "description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".", - "items": { - "oneOf": [ - { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - { - "type": "string", - "pattern": "^([a-z0-9-]+):(\\d{1,5})$" - } - ] - } - }, - "portsAttributes": { - "type": "object", - "patternProperties": { - "(^\\d+(-\\d+)?$)|(.+)": { - "type": "object", - "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openBrowserOnce", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens the browser when the port is automatically forwarded, but only the first time the port is forward during a session. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "default": { - "label": "Application", - "onAutoForward": "notify" - } - } - }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", - "defaultSnippets": [ - { - "body": { - "${1:3000}": { - "label": "${2:Application}", - "onAutoForward": "notify" - } - } - } - ], - "additionalProperties": false - }, - "otherPortsAttributes": { - "type": "object", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "defaultSnippets": [ - { - "body": { - "onAutoForward": "ignore" - } - } - ], - "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", - "additionalProperties": false - }, - "updateRemoteUserUID": { - "type": "boolean", - "description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default when opening from a local folder." - }, - "containerEnv": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "description": "Container environment variables." - }, - "containerUser": { - "type": "string", - "description": "The user the container will be started with. The default is the user on the Docker image." - }, - "mounts": { - "type": "array", - "description": "Mount points to set up when creating the container. See Docker's documentation for the --mount option for the supported syntax.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/Mount" - }, - { - "type": "string" - } - ] - } - }, - "init": { - "type": "boolean", - "description": "Passes the --init flag when creating the dev container." - }, - "privileged": { - "type": "boolean", - "description": "Passes the --privileged flag when creating the dev container." - }, - "capAdd": { - "type": "array", - "description": "Passes docker capabilities to include when creating the dev container.", - "examples": [ - "SYS_PTRACE" - ], - "items": { - "type": "string" - } - }, - "securityOpt": { - "type": "array", - "description": "Passes docker security options to include when creating the dev container.", - "examples": [ - "seccomp=unconfined" - ], - "items": { - "type": "string" - } - }, - "remoteEnv": { - "type": "object", - "additionalProperties": { - "type": [ - "string", - "null" - ] - }, - "description": "Remote environment variables to set for processes spawned in the container including lifecycle scripts and any remote editor/IDE server process." - }, - "remoteUser": { - "type": "string", - "description": "The username to use for spawning processes in the container including lifecycle scripts and any remote editor/IDE server process. The default is the same user as the container." - }, - "initializeCommand": { - "type": [ - "string", - "array", - "object" - ], - "description": "A command to run locally (i.e Your host machine, cloud VM) before anything else. This command is run before \"onCreateCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", - "items": { - "type": "string" - }, - "additionalProperties": { - "type": [ - "string", - "array" - ], - "items": { - "type": "string" - } - } - }, - "onCreateCommand": { - "type": [ - "string", - "array", - "object" - ], - "description": "A command to run when creating the container. This command is run after \"initializeCommand\" and before \"updateContentCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", - "items": { - "type": "string" - }, - "additionalProperties": { - "type": [ - "string", - "array" - ], - "items": { - "type": "string" - } - } - }, - "updateContentCommand": { - "type": [ - "string", - "array", - "object" - ], - "description": "A command to run when creating the container and rerun when the workspace content was updated while creating the container. This command is run after \"onCreateCommand\" and before \"postCreateCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", - "items": { - "type": "string" - }, - "additionalProperties": { - "type": [ - "string", - "array" - ], - "items": { - "type": "string" - } - } - }, - "postCreateCommand": { - "type": [ - "string", - "array", - "object" - ], - "description": "A command to run after creating the container. This command is run after \"updateContentCommand\" and before \"postStartCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", - "items": { - "type": "string" - }, - "additionalProperties": { - "type": [ - "string", - "array" - ], - "items": { - "type": "string" - } - } - }, - "postStartCommand": { - "type": [ - "string", - "array", - "object" - ], - "description": "A command to run after starting the container. This command is run after \"postCreateCommand\" and before \"postAttachCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", - "items": { - "type": "string" - }, - "additionalProperties": { - "type": [ - "string", - "array" - ], - "items": { - "type": "string" - } - } - }, - "postAttachCommand": { - "type": [ - "string", - "array", - "object" - ], - "description": "A command to run when attaching to the container. This command is run after \"postStartCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", - "items": { - "type": "string" - }, - "additionalProperties": { - "type": [ - "string", - "array" - ], - "items": { - "type": "string" - } - } - }, - "waitFor": { - "type": "string", - "enum": [ - "initializeCommand", - "onCreateCommand", - "updateContentCommand", - "postCreateCommand", - "postStartCommand" - ], - "description": "The user command to wait for before continuing execution in the background while the UI is starting up. The default is \"updateContentCommand\"." - }, - "userEnvProbe": { - "type": "string", - "enum": [ - "none", - "loginShell", - "loginInteractiveShell", - "interactiveShell" - ], - "description": "User environment probe to run. The default is \"loginInteractiveShell\"." - }, - "hostRequirements": { - "type": "object", - "description": "Host hardware requirements.", - "properties": { - "cpus": { - "type": "integer", - "minimum": 1, - "description": "Number of required CPUs." - }, - "memory": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." - }, - "storage": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." - }, - "gpu": { - "oneOf": [ - { - "type": [ - "boolean", - "string" - ], - "enum": [ - true, - false, - "optional" - ], - "description": "Indicates whether a GPU is required. The string \"optional\" indicates that a GPU is optional. An object value can be used to configure more detailed requirements." - }, - { - "type": "object", - "properties": { - "cores": { - "type": "integer", - "minimum": 1, - "description": "Number of required cores." - }, - "memory": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." - } - }, - "description": "Indicates whether a GPU is required. The string \"optional\" indicates that a GPU is optional. An object value can be used to configure more detailed requirements.", - "additionalProperties": false - } - ] - } - }, - "unevaluatedProperties": false - }, - "customizations": { - "type": "object", - "description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations." - }, - "additionalProperties": { - "type": "object", - "additionalProperties": true - } - } - }, - "nonComposeBase": { - "type": "object", - "properties": { - "appPort": { - "type": [ - "integer", - "string", - "array" - ], - "description": "Application ports that are exposed by the container. This can be a single port or an array of ports. Each port can be a number or a string. A number is mapped to the same port on the host. A string is passed to Docker unchanged and can be used to map ports differently, e.g. \"8000:8010\".", - "items": { - "type": [ - "integer", - "string" - ] - } - }, - "runArgs": { - "type": "array", - "description": "The arguments required when starting in the container.", - "items": { - "type": "string" - } - }, - "shutdownAction": { - "type": "string", - "enum": [ - "none", - "stopContainer" - ], - "description": "Action to take when the user disconnects from the container in their editor. The default is to stop the container." - }, - "overrideCommand": { - "type": "boolean", - "description": "Whether to overwrite the command specified in the image. The default is true." - }, - "workspaceFolder": { - "type": "string", - "description": "The path of the workspace folder inside the container." - }, - "workspaceMount": { - "type": "string", - "description": "The --mount parameter for docker run. The default is to mount the project folder at /workspaces/$project." - } - } - }, - "dockerfileContainer": { - "oneOf": [ - { - "type": "object", - "properties": { - "build": { - "type": "object", - "description": "Docker build-related options.", - "allOf": [ - { - "type": "object", - "properties": { - "dockerfile": { - "type": "string", - "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." - }, - "context": { - "type": "string", - "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." - } - }, - "required": [ - "dockerfile" - ] - }, - { - "$ref": "#/definitions/buildOptions" - } - ], - "unevaluatedProperties": false - } - }, - "required": [ - "build" - ] - }, - { - "allOf": [ - { - "type": "object", - "properties": { - "dockerFile": { - "type": "string", - "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." - }, - "context": { - "type": "string", - "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." - } - }, - "required": [ - "dockerFile" - ] - }, - { - "type": "object", - "properties": { - "build": { - "description": "Docker build-related options.", - "$ref": "#/definitions/buildOptions" - } - } - } - ] - } - ] - }, - "buildOptions": { - "type": "object", - "properties": { - "target": { - "type": "string", - "description": "Target stage in a multi-stage build." - }, - "args": { - "type": "object", - "additionalProperties": { - "type": [ - "string" - ] - }, - "description": "Build arguments." - }, - "cacheFrom": { - "type": [ - "string", - "array" - ], - "description": "The image to consider as a cache. Use an array to specify multiple images.", - "items": { - "type": "string" - } - }, - "options": { - "type": "array", - "description": "Additional arguments passed to the build command.", - "items": { - "type": "string" - } - } - } - }, - "imageContainer": { - "type": "object", - "properties": { - "image": { - "type": "string", - "description": "The docker image that will be used to create the container." - } - }, - "required": [ - "image" - ] - }, - "composeContainer": { - "type": "object", - "properties": { - "dockerComposeFile": { - "type": [ - "string", - "array" - ], - "description": "The name of the docker-compose file(s) used to start the services.", - "items": { - "type": "string" - } - }, - "service": { - "type": "string", - "description": "The service you want to work on. This is considered the primary container for your dev environment which your editor will connect to." - }, - "runServices": { - "type": "array", - "description": "An array of services that should be started and stopped.", - "items": { - "type": "string" - } - }, - "workspaceFolder": { - "type": "string", - "description": "The path of the workspace folder inside the container. This is typically the target path of a volume mount in the docker-compose.yml." - }, - "shutdownAction": { - "type": "string", - "enum": [ - "none", - "stopCompose" - ], - "description": "Action to take when the user disconnects from the primary container in their editor. The default is to stop all of the compose containers." - }, - "overrideCommand": { - "type": "boolean", - "description": "Whether to overwrite the command specified in the image. The default is false." - } - }, - "required": [ - "dockerComposeFile", - "service", - "workspaceFolder" - ] - }, - "Mount": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "bind", - "volume" - ], - "description": "Mount type." - }, - "source": { - "type": "string", - "description": "Mount source." - }, - "target": { - "type": "string", - "description": "Mount target." - } - }, - "required": [ - "type", - "target" - ], - "additionalProperties": false - } - }, - "oneOf": [ - { - "allOf": [ - { - "oneOf": [ - { - "allOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/dockerfileContainer" - }, - { - "$ref": "#/definitions/imageContainer" - } - ] - }, - { - "$ref": "#/definitions/nonComposeBase" - } - ] - }, - { - "$ref": "#/definitions/composeContainer" - } - ] - }, - { - "$ref": "#/definitions/devContainerCommon" - } - ] - }, - { - "type": "object", - "$ref": "#/definitions/devContainerCommon", - "additionalProperties": false - } - ], - "unevaluatedProperties": false + "$schema": "https://json-schema.org/draft/2019-09/schema", + "description": "Defines a dev container", + "allowComments": true, + "allowTrailingCommas": false, + "definitions": { + "devContainerCommon": { + "type": "object", + "properties": { + "$schema": { + "type": "string", + "format": "uri", + "description": "The JSON schema of the `devcontainer.json` file." + }, + "name": { + "type": "string", + "description": "A name for the dev container which can be displayed to the user." + }, + "features": { + "type": "object", + "description": "Features to add to the dev container.", + "properties": { + "fish": { + "deprecated": true, + "deprecationMessage": "Legacy feature not supported. Please check https://containers.dev/features for replacements." + }, + "maven": { + "deprecated": true, + "deprecationMessage": "Legacy feature will be removed in the future. Please check https://containers.dev/features for replacements. E.g., `ghcr.io/devcontainers/features/java` has an option to install Maven." + }, + "gradle": { + "deprecated": true, + "deprecationMessage": "Legacy feature will be removed in the future. Please check https://containers.dev/features for replacements. E.g., `ghcr.io/devcontainers/features/java` has an option to install Gradle." + }, + "homebrew": { + "deprecated": true, + "deprecationMessage": "Legacy feature not supported. Please check https://containers.dev/features for replacements." + }, + "jupyterlab": { + "deprecated": true, + "deprecationMessage": "Legacy feature will be removed in the future. Please check https://containers.dev/features for replacements. E.g., `ghcr.io/devcontainers/features/python` has an option to install JupyterLab." + } + }, + "additionalProperties": true + }, + "overrideFeatureInstallOrder": { + "type": "array", + "description": "Array consisting of the Feature id (without the semantic version) of Features in the order the user wants them to be installed.", + "items": { + "type": "string" + } + }, + "localFeaturesRoot": { + "type": "string", + "description": "Path (relative to the folder containing devcontainer.json) that serves as the base directory for resolving local Feature paths. When specified, all local Feature paths (those prefixed with ./) are resolved relative to this directory. Defaults to the folder containing devcontainer.json." + }, + "secrets": { + "type": "object", + "description": "Recommended secrets for this dev container. Recommendations are provided as environment variable keys with optional metadata.", + "patternProperties": { + "^[a-zA-Z_][a-zA-Z0-9_]*$": { + "type": "object", + "description": "Environment variable keys following unix-style naming conventions. eg: ^[a-zA-Z_][a-zA-Z0-9_]*$", + "properties": { + "description": { + "type": "string", + "description": "A description of the secret." + }, + "documentationUrl": { + "type": "string", + "format": "uri", + "description": "A URL to documentation about the secret." + } + }, + "additionalProperties": false + }, + "additionalProperties": false + }, + "additionalProperties": false + }, + "forwardPorts": { + "type": "array", + "description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".", + "items": { + "oneOf": [ + { + "type": "integer", + "maximum": 65535, + "minimum": 0 + }, + { + "type": "string", + "pattern": "^([a-z0-9-]+):(\\d{1,5})$" + } + ] + } + }, + "portsAttributes": { + "type": "object", + "patternProperties": { + "(^\\d+(-\\d+)?$)|(.+)": { + "type": "object", + "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", + "properties": { + "onAutoForward": { + "type": "string", + "enum": [ + "notify", + "openBrowser", + "openBrowserOnce", + "openPreview", + "silent", + "ignore" + ], + "enumDescriptions": [ + "Shows a notification when a port is automatically forwarded.", + "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", + "Opens the browser when the port is automatically forwarded, but only the first time the port is forward during a session. Depending on your settings, this could open an embedded browser.", + "Opens a preview in the same window when the port is automatically forwarded.", + "Shows no notification and takes no action when this port is automatically forwarded.", + "This port will not be automatically forwarded." + ], + "description": "Defines the action that occurs when the port is discovered for automatic forwarding", + "default": "notify" + }, + "elevateIfNeeded": { + "type": "boolean", + "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", + "default": false + }, + "label": { + "type": "string", + "description": "Label that will be shown in the UI for this port.", + "default": "Application" + }, + "requireLocalPort": { + "type": "boolean", + "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", + "default": false + }, + "protocol": { + "type": "string", + "enum": ["http", "https"], + "description": "The protocol to use when forwarding this port." + } + }, + "default": { + "label": "Application", + "onAutoForward": "notify" + } + } + }, + "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", + "defaultSnippets": [ + { + "body": { + "${1:3000}": { + "label": "${2:Application}", + "onAutoForward": "notify" + } + } + } + ], + "additionalProperties": false + }, + "otherPortsAttributes": { + "type": "object", + "properties": { + "onAutoForward": { + "type": "string", + "enum": [ + "notify", + "openBrowser", + "openPreview", + "silent", + "ignore" + ], + "enumDescriptions": [ + "Shows a notification when a port is automatically forwarded.", + "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", + "Opens a preview in the same window when the port is automatically forwarded.", + "Shows no notification and takes no action when this port is automatically forwarded.", + "This port will not be automatically forwarded." + ], + "description": "Defines the action that occurs when the port is discovered for automatic forwarding", + "default": "notify" + }, + "elevateIfNeeded": { + "type": "boolean", + "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", + "default": false + }, + "label": { + "type": "string", + "description": "Label that will be shown in the UI for this port.", + "default": "Application" + }, + "requireLocalPort": { + "type": "boolean", + "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", + "default": false + }, + "protocol": { + "type": "string", + "enum": ["http", "https"], + "description": "The protocol to use when forwarding this port." + } + }, + "defaultSnippets": [ + { + "body": { + "onAutoForward": "ignore" + } + } + ], + "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", + "additionalProperties": false + }, + "updateRemoteUserUID": { + "type": "boolean", + "description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default when opening from a local folder." + }, + "containerEnv": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Container environment variables." + }, + "containerUser": { + "type": "string", + "description": "The user the container will be started with. The default is the user on the Docker image." + }, + "mounts": { + "type": "array", + "description": "Mount points to set up when creating the container. See Docker's documentation for the --mount option for the supported syntax.", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/Mount" + }, + { + "type": "string" + } + ] + } + }, + "init": { + "type": "boolean", + "description": "Passes the --init flag when creating the dev container." + }, + "privileged": { + "type": "boolean", + "description": "Passes the --privileged flag when creating the dev container." + }, + "capAdd": { + "type": "array", + "description": "Passes docker capabilities to include when creating the dev container.", + "examples": ["SYS_PTRACE"], + "items": { + "type": "string" + } + }, + "securityOpt": { + "type": "array", + "description": "Passes docker security options to include when creating the dev container.", + "examples": ["seccomp=unconfined"], + "items": { + "type": "string" + } + }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": ["string", "null"] + }, + "description": "Remote environment variables to set for processes spawned in the container including lifecycle scripts and any remote editor/IDE server process." + }, + "remoteUser": { + "type": "string", + "description": "The username to use for spawning processes in the container including lifecycle scripts and any remote editor/IDE server process. The default is the same user as the container." + }, + "initializeCommand": { + "type": ["string", "array", "object"], + "description": "A command to run locally (i.e Your host machine, cloud VM) before anything else. This command is run before \"onCreateCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", + "items": { + "type": "string" + }, + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "onCreateCommand": { + "type": ["string", "array", "object"], + "description": "A command to run when creating the container. This command is run after \"initializeCommand\" and before \"updateContentCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", + "items": { + "type": "string" + }, + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "updateContentCommand": { + "type": ["string", "array", "object"], + "description": "A command to run when creating the container and rerun when the workspace content was updated while creating the container. This command is run after \"onCreateCommand\" and before \"postCreateCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", + "items": { + "type": "string" + }, + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "postCreateCommand": { + "type": ["string", "array", "object"], + "description": "A command to run after creating the container. This command is run after \"updateContentCommand\" and before \"postStartCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", + "items": { + "type": "string" + }, + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "postStartCommand": { + "type": ["string", "array", "object"], + "description": "A command to run after starting the container. This command is run after \"postCreateCommand\" and before \"postAttachCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", + "items": { + "type": "string" + }, + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "postAttachCommand": { + "type": ["string", "array", "object"], + "description": "A command to run when attaching to the container. This command is run after \"postStartCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", + "items": { + "type": "string" + }, + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "waitFor": { + "type": "string", + "enum": [ + "initializeCommand", + "onCreateCommand", + "updateContentCommand", + "postCreateCommand", + "postStartCommand" + ], + "description": "The user command to wait for before continuing execution in the background while the UI is starting up. The default is \"updateContentCommand\"." + }, + "userEnvProbe": { + "type": "string", + "enum": [ + "none", + "loginShell", + "loginInteractiveShell", + "interactiveShell" + ], + "description": "User environment probe to run. The default is \"loginInteractiveShell\"." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + }, + "gpu": { + "oneOf": [ + { + "type": ["boolean", "string"], + "enum": [true, false, "optional"], + "description": "Indicates whether a GPU is required. The string \"optional\" indicates that a GPU is optional. An object value can be used to configure more detailed requirements." + }, + { + "type": "object", + "properties": { + "cores": { + "type": "integer", + "minimum": 1, + "description": "Number of required cores." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + } + }, + "description": "Indicates whether a GPU is required. The string \"optional\" indicates that a GPU is optional. An object value can be used to configure more detailed requirements.", + "additionalProperties": false + } + ] + } + }, + "unevaluatedProperties": false + }, + "customizations": { + "type": "object", + "description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations." + }, + "additionalProperties": { + "type": "object", + "additionalProperties": true + } + } + }, + "nonComposeBase": { + "type": "object", + "properties": { + "appPort": { + "type": ["integer", "string", "array"], + "description": "Application ports that are exposed by the container. This can be a single port or an array of ports. Each port can be a number or a string. A number is mapped to the same port on the host. A string is passed to Docker unchanged and can be used to map ports differently, e.g. \"8000:8010\".", + "items": { + "type": ["integer", "string"] + } + }, + "runArgs": { + "type": "array", + "description": "The arguments required when starting in the container.", + "items": { + "type": "string" + } + }, + "shutdownAction": { + "type": "string", + "enum": ["none", "stopContainer"], + "description": "Action to take when the user disconnects from the container in their editor. The default is to stop the container." + }, + "overrideCommand": { + "type": "boolean", + "description": "Whether to overwrite the command specified in the image. The default is true." + }, + "workspaceFolder": { + "type": "string", + "description": "The path of the workspace folder inside the container." + }, + "workspaceMount": { + "type": "string", + "description": "The --mount parameter for docker run. The default is to mount the project folder at /workspaces/$project." + } + } + }, + "dockerfileContainer": { + "oneOf": [ + { + "type": "object", + "properties": { + "build": { + "type": "object", + "description": "Docker build-related options.", + "allOf": [ + { + "type": "object", + "properties": { + "dockerfile": { + "type": "string", + "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." + }, + "context": { + "type": "string", + "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." + } + }, + "required": ["dockerfile"] + }, + { + "$ref": "#/definitions/buildOptions" + } + ], + "unevaluatedProperties": false + } + }, + "required": ["build"] + }, + { + "allOf": [ + { + "type": "object", + "properties": { + "dockerFile": { + "type": "string", + "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." + }, + "context": { + "type": "string", + "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." + } + }, + "required": ["dockerFile"] + }, + { + "type": "object", + "properties": { + "build": { + "description": "Docker build-related options.", + "$ref": "#/definitions/buildOptions" + } + } + } + ] + } + ] + }, + "buildOptions": { + "type": "object", + "properties": { + "target": { + "type": "string", + "description": "Target stage in a multi-stage build." + }, + "args": { + "type": "object", + "additionalProperties": { + "type": ["string"] + }, + "description": "Build arguments." + }, + "cacheFrom": { + "type": ["string", "array"], + "description": "The image to consider as a cache. Use an array to specify multiple images.", + "items": { + "type": "string" + } + }, + "options": { + "type": "array", + "description": "Additional arguments passed to the build command.", + "items": { + "type": "string" + } + } + } + }, + "imageContainer": { + "type": "object", + "properties": { + "image": { + "type": "string", + "description": "The docker image that will be used to create the container." + } + }, + "required": ["image"] + }, + "composeContainer": { + "type": "object", + "properties": { + "dockerComposeFile": { + "type": ["string", "array"], + "description": "The name of the docker-compose file(s) used to start the services.", + "items": { + "type": "string" + } + }, + "service": { + "type": "string", + "description": "The service you want to work on. This is considered the primary container for your dev environment which your editor will connect to." + }, + "runServices": { + "type": "array", + "description": "An array of services that should be started and stopped.", + "items": { + "type": "string" + } + }, + "workspaceFolder": { + "type": "string", + "description": "The path of the workspace folder inside the container. This is typically the target path of a volume mount in the docker-compose.yml." + }, + "shutdownAction": { + "type": "string", + "enum": ["none", "stopCompose"], + "description": "Action to take when the user disconnects from the primary container in their editor. The default is to stop all of the compose containers." + }, + "overrideCommand": { + "type": "boolean", + "description": "Whether to overwrite the command specified in the image. The default is false." + } + }, + "required": ["dockerComposeFile", "service", "workspaceFolder"] + }, + "Mount": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["bind", "volume"], + "description": "Mount type." + }, + "source": { + "type": "string", + "description": "Mount source." + }, + "target": { + "type": "string", + "description": "Mount target." + } + }, + "required": ["type", "target"], + "additionalProperties": false + } + }, + "oneOf": [ + { + "allOf": [ + { + "oneOf": [ + { + "allOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/dockerfileContainer" + }, + { + "$ref": "#/definitions/imageContainer" + } + ] + }, + { + "$ref": "#/definitions/nonComposeBase" + } + ] + }, + { + "$ref": "#/definitions/composeContainer" + } + ] + }, + { + "$ref": "#/definitions/devContainerCommon" + } + ] + }, + { + "type": "object", + "$ref": "#/definitions/devContainerCommon", + "additionalProperties": false + } + ], + "unevaluatedProperties": false }