wyrd/tools
esc

    ↑↓ Move ↵ Open dcb results first Search by Pagefind
    Releases

    Usage

    Quick start

    Put a configuration in .devcontainer/devcontainer.json:

    {
      "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
      "features": {
        "ghcr.io/devcontainers/features/common-utils:2": {}
      },
      "remoteUser": "vscode",
      "postCreateCommand": "echo ready > /tmp/ready"
    }

    Start the container, run a command in it, and remove it:

    dcb up --workspace-folder .
    dcb exec --workspace-folder . -- sh -c 'id && pwd'
    dcb down --workspace-folder .

    dcb up writes one result line:

    {"containerId":"3f1c…","outcome":"success","remoteUser":"vscode","remoteWorkspaceFolder":"/workspaces/project"}

    Create before first start

    Use dcb up --no-start --workspace-folder . to build and create a container before binding external sockets or other resources to it. The result includes containerId, outcome, and "started": false. Names, mounts, labels, run arguments, and metadata use the same creation path as ordinary up.

    Repeat --no-start safely to reuse the existing container. It does not stop an already running container. After binding resources, run dcb up --workspace-folder . to start the same container and run its lifecycle commands. Container setup, UID changes, probes, dotfiles, and lifecycle commands wait for ordinary up. The image entrypoint first runs when the container starts.

    initializeCommand runs once on the host for each up invocation, including --no-start and the later ordinary up, as it does on repeated upstream up. Library callers run host initialization explicitly with pkg/host/local.Initialize. The hosts do not run it internally.

    Compose creates all selected services in dependency order without starting any of them. Health and successful-completion dependency checks run during the later ordinary up. Runtime namespace dependencies that require a running peer can still prevent creation. Compose retains its ordinary configuration drift rules: changing the configuration between create and start can recreate a service.

    Bind sources must exist before creation. A missing source in a devcontainer mounts entry produces an Engine error; dcb does not replace it with a -v bind or create its directory. Create-only also requires existing sources for Compose bind volumes, including short volume syntax. Ordinary Compose up retains its configured host-path creation behavior. Create-only rejects Compose and Docker runArgs bind mounts with SELinux z or Z relabel options: the strict Engine mount API cannot preserve relabeling. The error names the service and mount for Compose, or the mount for Docker. It also rejects the explicit bind-create-src mount option. Ordinary up still supports these options.

    This mode extends the upstream CLI. Ordinary up behavior stays the same.

    Commands

    CommandWhat it does
    dcb upBuilds the image if the configuration needs it, creates and starts the container, and runs the lifecycle commands. An existing container for the workspace is started and reused.
    dcb execRuns a command in the workspace container.
    dcb downStops and removes the workspace container.
    dcb inspectReturns the Docker inspection of the workspace container.
    dcb buildBuilds the image and loads, pushes, or exports it.
    dcb outdatedReports declared OCI Feature versions as text or JSON.
    dcb read-configurationReturns the configuration with variables substituted.
    dcb run-user-commandsRuns the lifecycle commands in the existing workspace container.
    dcb set-upRuns the lifecycle commands of a configuration in a container that dcb did not create.
    dcb generateWrites the generated Dockerfile and build context, and does not build.
    dcb featuresResolves and downloads the configuration’s features into a directory.
    dcb upgradeGenerates a Feature lockfile and optionally updates a Feature requirement.
    dcb features resolve-dependenciesPrints the configuration Feature graph and installation order.
    dcb features info <mode> <feature>Reports a published Feature manifest, tags, or dependency graph.
    dcb features generate-docsGenerates README files from local Feature metadata and notes.
    dcb features package <target>Packages a local Feature or collection as archives with collection metadata.
    dcb templates generate-docsGenerates README files from local Template metadata and notes.
    dcb templates metadata <templateId>Fetches a published Template’s metadata as JSON.
    dcb serverDownloads a VS Code server archive.
    dcb skillsPrints or installs the bundled agent skill.
    dcb versionReturns the dcb version. dcb --version, or --version on any command, returns the same result in place of the command.

    dcb --help lists the commands with examples. dcb <command> --help lists the flags of one command. Help is plain text.

    Boolean flags accept a bare flag, an equals value, or a separated value: --prebuild, --prebuild=true, --prebuild=false, --prebuild true, and --prebuild false. Separated values must be lowercase true or false; other values remain positional arguments. The no- prefix does not negate boolean flags, as upstream. Arguments after -- pass through unchanged.

    Not yet supported

    These upstream command families are not yet supported:

    Workspace flags

    Commands that read a configuration take these flags:

    FlagMeaning
    --workspace-folderThe local workspace folder. The default is the current directory.
    --configThe path to devcontainer.json. The default is discovery in the workspace folder: .devcontainer/devcontainer.json, then .devcontainer.json.
    --override-configA file with alternate configuration content. Relative paths in it resolve against the --config location.

    read-configuration accepts common-only documents without an image, Dockerfile, or Compose source. build and up require a source. See Configuration for Compose discovery and environment types.

    dcb identifies the container of a workspace by its labels, so exec, down, inspect, and run-user-commands find the container that up created. exec, run-user-commands, set-up, and read-configuration also take --container-id to select a container directly. --id-label selects by other labels; see Id labels.

    Feature lockfiles

    build and up accept the hidden boolean flags --experimental-lockfile and --experimental-frozen-lockfile. Both default to false.

    dcb build --workspace-folder . --experimental-lockfile
    dcb up --workspace-folder . --experimental-frozen-lockfile

    When features exist, --experimental-lockfile creates devcontainer-lock.json next to the configuration. A dot-prefixed configuration such as .devcontainer.json uses .devcontainer-lock.json. An existing lockfile, including an empty initialization marker, updates by default after success. The file records remote feature versions, resolved references, integrity digests, and hard dependencies. Local features are omitted.

    Resolution uses the existing integrity digest to pin OCI features, including transitive dependencies. HTTPS feature archives must match their recorded archive digest. read-configuration --include-features-configuration uses the same pins and reports the resolved digest in each feature set.

    Frozen mode fails if the file is missing or the resolved feature graph differs. Whitespace changes do not count as drift. Frozen mode never writes, including when both flags are set. To resolve a newer version of the same reference, remove its lock entry before running a write-enabled build.

    Publication uses a complete temporary file beside the destination, then an atomic rename, only after the command succeeds. A failed or cancelled build or lifecycle command leaves the existing lockfile unchanged.

    Results

    Commands other than outdated write exactly one JSON object on one line to stdout, except help, a valid exec invocation, and archive exports to stdout (see Build exporters). An invalid invocation, including one of exec, writes a JSON error result and exits with 2. The outcome field is success or error. An error result also has message and description:

    {"description":"dcb could not complete the requested command","message":"docker host: container not found","outcome":"error"}

    build and up remove staged build artifacts after success or failure. Use --keep to retain them for inspection. A build error includes contextDir only when --keep retains the context. An explicit --work-dir has the same cleanup rules. generate retains its output folder.

    Other result fields by command:

    CommandFields
    upcontainerId, remoteUser, remoteWorkspaceFolder; composeProjectName for a Compose configuration
    inspectcontainer, and the up fields
    run-user-commandsresult, phasesRun, and the up fields
    set-upphasesRun, and the up fields
    buildimageName; cacheOnly for cache builds; output for explicit exports, plus outputDir for local exports
    generatedockerfile, contextDir
    read-configurationconfiguration, workspace; mergedConfiguration with --include-merged-configuration; featuresConfiguration with --include-features-configuration
    featuresfeatures
    servercommit, quality, path, url, bytes
    skillsskills; installed for skills install
    versionversion

    composeProjectName follows the upstream CLI order: COMPOSE_PROJECT_NAME in the host environment, then in the workspace’s .env, then the Compose name:. An authored name: devcontainer is retained. Without a name, a first Compose file in <workspace>/.devcontainer uses <workspace basename>_devcontainer; other locations use the basename of the first Compose file’s directory. Names are lowercase and retain -, _, letters, and digits. Workspace-only operations find the running or stopped project’s captured name through its container labels, including when the configuration file has been removed.

    Workspaces with the same basename can select the same Compose project. up refuses by default when that project contains a container labelled for another workspace. The text diagnostic lists every conflicting container’s name, full ID, and owning devcontainer.local_folder. The JSON error result includes composeProjectName and a projectConflicts array with name, id, and localFolder for each conflict.

    Use dcb up --allow-project-conflict --workspace-folder . to allow upstream behavior: containers in the project can be reused or recreated for this workspace. The override logs a warning naming the conflicting containers. It does not override the teardown refusal below.

    Set a distinct top-level name: in each workspace’s Compose file to keep their projects separate, for example:

    name: sample-alpha
    services:
      app:
        image: alpine:3.20

    Use another name, such as sample-beta, for the other workspace. A COMPOSE_PROJECT_NAME in the host environment or workspace .env takes precedence over name:. The library’s Stop and Remove, and CLI down, refuse a project that contains a container labelled for another workspace, so that its containers, networks, volumes, and materialized configuration files remain intact.

    phasesRun lists the lifecycle commands that ran, in order. Each entry has hook, the upstream name such as postCreateCommand, and origin, which is devcontainer.json or the reference of the feature that contributed it.

    A successful run-user-commands also returns result: skipNonBlocking, prebuild, stopForPersonalization, or done. It identifies the stop point, even when that hook has no command or markers skip the commands. At updateContentCommand, skipNonBlocking takes precedence over prebuild; prebuild takes precedence over personalization. Default execution returns done. A failed lifecycle command returns the error outcome without result.

    Exit codes

    CodeMeaning
    0Success.
    1The operation failed.
    2The invocation is not valid, for example an unknown flag.

    A valid exec invocation returns the exit code of the container command. See Run a command.

    Logs

    Build output, lifecycle command output, and diagnostics go to stderr. --log-level selects the diagnostics: trace, debug, info (the default), warn, error, or off. Build and lifecycle output is not filtered.

    --log-format json writes each stderr record as JSON. A diagnostic has type: diagnostic, level, and text. Build and lifecycle output has type: output and text.

    Run a command

    exec stops reading dcb options at the container command. All later arguments pass through unchanged. Both forms work:

    dcb exec --workspace-folder . ls -la /workspaces
    dcb exec --workspace-folder . -- ls -la /workspaces

    exec has three outcomes:

    • The command runs. exec writes no result line. The command’s stdout and stderr pass through unchanged, and dcb exits with the command’s exit code.
    • The invocation is not valid, for example no command or an unknown flag. stdout has one JSON error result, stderr is empty at the default log level, and the exit code is 2.
    • The operation fails before or outside the command. stdout is empty, stderr has the error, and the exit code is 1.

    With --tty, the command runs on a pseudo-terminal, and its output arrives on stdout.

    exec runs as the configured remoteUser with remoteEnv applied. Use --user, --remote-env KEY=VALUE, and --remote-workspace-folder to change the user, environment, and working directory for one command. Configuration remoteEnv replaces a --remote-env value with the same name, as upstream. Values set only by --remote-env are added to the command environment.

    A command can also exit with 1. To tell this apart from an operation failure, look for the dcb error diagnostic on stderr. In text mode it starts with dcb: error: . In JSON mode it is a record with type: diagnostic and level: error. The command can print the same text, so this check is a heuristic.

    Lifecycle commands

    dcb up runs onCreateCommand, updateContentCommand, postCreateCommand, postStartCommand, and postAttachCommand, including the ones that features contribute. Marker files in the container record the commands that ran, so a later up on the same container does not repeat creation commands.

    • --skip-post-create skips all lifecycle commands.
    • --skip-post-attach skips only postAttachCommand.
    • --remove-existing-container removes the workspace container and creates a new one.

    dcb run-user-commands runs the lifecycle commands in the existing container. The markers skip the creation commands that already ran. dcb set-up --container-id <id> applies the lifecycle commands of a configuration to a container that was started some other way.

    Lifecycle controls

    All lifecycle control flags are booleans and default to false.

    FlagCommandsMeaning
    --skip-non-blocking-commandsup, run-user-commands, set-upStop after the merged configuration’s waitFor hook, or updateContentCommand by default. Later hooks do not run in the background.
    --prebuildup, run-user-commandsStop after updateContentCommand. Rerun that hook even when its creation marker is current; onCreateCommand still uses its marker.
    --stop-for-personalizationrun-user-commandsRun through postCreateCommand and the dotfiles install, then stop before postStartCommand.
    --expect-existing-containerupFail before building or creating resources if the workspace container does not exist. A stopped container qualifies.

    The stop point applies even when that hook has no commands. All feature and configuration commands through the stop point use their normal markers. With --prebuild, all updateContentCommand contributions rerun. When stop controls are combined, the first stop point wins. A waitFor stop at postCreateCommand precedes dotfiles; personalization follows dotfiles. A waitFor stop at initializeCommand skips all container lifecycle hooks. --skip-post-create continues to skip all lifecycle commands and dotfiles.

    --expect-existing-container --remove-existing-container checks that a container exists, then replaces it. Without explicit removal, an expected Compose container is reused without recreation when its configuration changes.

    Dotfiles

    up, run-user-commands, and set-up can install a dotfiles repository after postCreateCommand and before postStartCommand:

    dcb up --workspace-folder . \
      --dotfiles-repository https://example.com/example-owner/dotfiles.git \
      --dotfiles-install-command install.sh
    FlagMeaning
    --dotfiles-repositoryA Git URL or a path in the container. A value such as example-owner/dotfiles, without : and not starting with /, ./, or ../, is a GitHub repository.
    --dotfiles-install-commandThe command to run in the clone. Without it, dcb runs the first of install.sh, install, bootstrap.sh, bootstrap, script/bootstrap, setup.sh, setup, and script/setup. Without any of them, it links the repository’s dot files into the home folder.
    --dotfiles-target-pathThe clone location. The default is ~/dotfiles. The container shell expands it, and a relative path starts at the container’s working folder. A location that exists is used as it is, without a clone, as in the upstream CLI.

    The clone and the install command run in the container as the remote user, with the remote environment, so the container needs git. dcb clones into a temporary folder beside the clone location and moves the clone into place only when it is complete, so a command never uses the partial clone of another command. After a successful install, dcb writes a marker file, so a later command on the same container does not repeat it. --skip-post-create also skips the dotfiles.

    A failed install does not fail the command, as in the upstream CLI: the result is success and stderr has a warning that names the repository. In JSON log mode the warning is a record with type: diagnostic and level: warn. The upstream CLI writes the marker before the install; dcb writes it only after a successful install, so the next up installs again after a failed or interrupted install. A missing git is a failure. If dcb cannot write the marker, it gives a warning and the next up installs again.

    Remote environment and secrets

    up, run-user-commands, and set-up take --remote-env KEY=VALUE. Repeat it for more variables. up and run-user-commands also take --secrets-file, the path of a JSON file of secret variables:

    {"SAMPLE_TOKEN": "example-value"}
    dcb up --workspace-folder . \
      --remote-env SAMPLE_MODE=test \
      --secrets-file ./secrets.json

    The variables apply to the lifecycle commands and the dotfiles install of this command only. dcb does not keep them: they are not in the container configuration, in a label, or in a file, and a later exec does not get them. The configuration and mergedConfiguration result fields do not contain them. For these three commands, as in the upstream CLI, a configuration remoteEnv value replaces a --remote-env value with the same name, and a secret replaces both.

    A --remote-env value with no =, or with no name before it, is an invalid invocation. The secrets file can have comments. Each value is a string, a number, or a boolean. As in the upstream CLI, a number or a boolean becomes its JavaScript text, for example 1.0 becomes 1 and true stays true. An object, an array, or null is not a valid value. If dcb cannot read or parse the file, the command fails with exit code 1, and the error gives the path and the reason but not the content. dcb reads the file once, so a pipe such as /dev/stdin works. An empty --secrets-file value means no secrets, as in the upstream CLI.

    dcb replaces each secret value with ******** in stderr and in the result, as the upstream CLI does in its log. This includes the output of lifecycle commands, Docker errors, and errors about other flags. dcb also replaces the value’s quoted, JSON-escaped, base64 (standard and URL, with and without padding), and URL-escaped forms. A value that is changed in another way, or that is part of a longer encoded text, is not replaced. A short value, such as 1, is replaced wherever that text appears. An invalid --remote-env, --build-arg, or --label value is reported by its flag and position, not by its text.

    Build an image

    dcb build loads the image into Docker by default:

    dcb build --workspace-folder . --image-name example:dev

    Push to a registry instead. Repeat --platform for a multi-platform image:

    dcb build --workspace-folder . \
      --image-name registry.example.com/team/dev:1.0 \
      --platform linux/amd64 --platform linux/arm64 \
      --push
    FlagMeaning
    --image-nameAn image name. Repeat for more names.
    --pushPushes the image to its registry.
    --loadLoads the image into Docker. This is the default when there is no --push or --output. --load=false builds into the cache only.
    --outputA buildx exporter specification or local directory. See Build exporters. It cannot be combined with --push or --load.
    --platformA target platform, such as linux/arm64. Docker cannot load a multi-platform image; use --push or --output.
    --build-argA Dockerfile build argument, KEY=VALUE.
    --labelAn image label, KEY=VALUE.
    --cache-from, --cache-toBuildKit cache import and export settings. Repeatable.
    --no-cacheDisables the build cache.
    --pullAlways pulls referenced images.
    --skip-featuresBuilds without the configuration’s features.
    --additional-featuresMore features as JSON in the form of the configuration’s features property. They follow the configuration’s features. For a feature the configuration already names, the configuration’s options are used. Invalid JSON is an invalid invocation.
    --vscode-serverInstalls the VS Code server into the image. See VS Code server.
    --progressBuild progress format: plain (the default), auto, tty, or rawjson.
    --buildkit-addrUses a separate BuildKit. See Docker and BuildKit.

    dcb up takes the same build flags except the export flags, and uses --build-no-cache in place of --no-cache. For example, add a feature to a configuration without editing it:

    dcb up --workspace-folder . \
      --additional-features '{"ghcr.io/devcontainers/features/common-utils:2":{}}'

    dcb read-configuration --include-merged-configuration also takes --additional-features. As in the upstream CLI, the features add their metadata to mergedConfiguration only when the workspace has no container, and mergedConfiguration.features lists only the configuration’s features.

    Configuration results

    up and set-up accept --include-configuration and --include-merged-configuration. Both default to false. Enable either flag to add configuration or mergedConfiguration to the success result. Configuration values include workspace and container environment substitutions. The merged result includes image and feature metadata, with collected lifecycle commands. Lifecycle secrets are separate from these configuration snapshots.

    dcb up --workspace-folder . --include-configuration --include-merged-configuration
    dcb set-up --workspace-folder . --container-id sample-container --include-configuration
    dcb read-configuration --workspace-folder . --include-features-configuration

    read-configuration applies workspace and host substitutions to configuration. When an existing container is selected by --container-id or workspace discovery, its environment is substituted before producing configuration, merged output, and resolved feature options, independently of the include flags. Without a container, container environment references remain unresolved. Ordinary reads without an explicit container ID also work when Docker is unreachable; discovery and inspection operation errors are reported. Merged and feature output retain their Docker requirements.

    read-configuration --include-features-configuration resolves declared features, additional features and their dependencies in installation order. Its optional featuresConfiguration result contains featureSets with feature metadata, option values and source information, plus the temporary staging dstFolder. The field is omitted when there are no features. Resolved files remain in the reported staging paths. Use dcb features --out ./staged-features to choose a staging location.

    Inspect what a build uses

    dcb generate writes the Dockerfile and build context for a configuration, and dcb features downloads its features, without a build:

    dcb generate --workspace-folder . --work-dir ./generated
    dcb features --workspace-folder . --out ./staged-features

    dcb server downloads a VS Code server archive:

    dcb server --out ./server --platform linux/amd64

    Package local Features

    dcb features package ./src --output-folder ./packaged
    dcb features package ./src/sample -o ./packaged -f

    The target is required. A target with devcontainer-feature.json is one Feature; otherwise its non-hidden child folders with that file form a collection. Collection entries must have install.sh. Each Feature must have id, version, and name metadata. An ID that would put an archive outside the output folder is rejected. Other metadata properties and source files are retained. Nonempty legacyIds adds currentId with the Feature ID to the packaged metadata. Source files are unchanged.

    --output-folder (-o) defaults to ./output. An existing output directory causes an operation failure. --force-clean-output-folder (-f) removes it before packaging. An error can leave archives from earlier collection entries; the collection metadata is written only after all entries succeed.

    The output contains devcontainer-collection.json and devcontainer-feature-<id>.tgz for a single Feature, or devcontainer-feature-<folder>.tgz for each collection entry. These are plain tar archives with the upstream .tgz name. File bytes, modes, and symlinks are preserved. dcb returns its usual JSON result with a features metadata array; upstream logs progress without a stdout result. Packaging uses local files and does not need Docker or a registry. See TestPackageSingle, TestPackageCollection, and TestFeaturesPackageCommand for the contract checks.

    Generate Feature documentation

    dcb features generate-docs --project-folder ./src --namespace example/collection

    --project-folder (-p, default .) contains the Feature child folders; the command does not append src or test. Hidden entries are skipped. --namespace (-n) is required, and --registry (-r) defaults to ghcr.io. A string flag without a value uses its declared default: an empty namespace or GitHub owner/repository, . for the project folder, and ghcr.io for the registry. An explicit empty string stays empty. Arguments after -- are ignored; positional arguments before it are rejected. Grouped short flags use the upstream defaults for earlier flags in the group. The camel-case aliases --projectFolder, --githubOwner, --githubRepo, and --logLevel select the same values as their hyphenated names. The example reference uses the Feature ID and the major part of its version, or latest when the version is absent. Supply both --github-owner and --github-repo to link metadata to that repository’s main branch.

    Each README includes the name, description, options in metadata order, VS Code extensions, deprecation and legacy-ID notices, and optional NOTES.md. Numeric option keys follow JavaScript key order. An empty option default is -; false and zero remain values, and an absent default renders undefined, as upstream. Markdown and replacement-string dollar patterns are retained with upstream’s JavaScript replacement semantics. Metadata uses the upstream tolerant JSONC recovery behavior. Metadata and notes use Node-compatible UTF-8 replacement for malformed byte sequences. Object values retain JavaScript own property and prototype conversion, including operation errors when conversion fails. A list’s length property controls whether its map operation is attempted; invalid map operations also fail before replacing that entry’s README. String-valued options enumerate UTF-16 indices, as upstream. Distinct escaped UTF-16 property keys remain distinct; isolated surrogate units become replacement characters when the README is written. Equivalent recovered UTF-16 keys select the same property. Repeated dotted option assignments update one object until ordinary repeated values form an array. Dotted properties are retained until a README uses the publication value. An own toString property fails conversion and preserves that entry’s README. Empty collections and skipped entries do not convert publication values. An owner or repository value is unused when its counterpart is the empty string.

    Existing READMEs are replaced. An existing README symlink is unlinked; a dangling symlink is followed, as upstream. Source metadata and notes stay unchanged. Missing metadata, unreadable metadata, or a missing ID logs a diagnostic and skips that entry with exit 0. A folder, notes, or README write failure exits 1; other entries can already have completed. There is no collection rollback. The command uses local files without Docker or registry access.

    Progress and skip diagnostics use stderr. dcb retains its JSON result on stdout and invocation-error exit 2; upstream emits no stdout result and uses exit 1 for invocation errors. --log-level accepts the shared dcb levels, including upstream’s info, debug, and trace, and --log-format remains available. Repeated publication strings are comma-joined as upstream; repeated project folders fail. See TestGenerateDocumentationPinnedReadmes, TestGenerateDocumentationFilesystem, TestGenerateDocumentationConversionReadmes, TestFeaturesDocumentationCommand, TestFeaturesDocumentationOptionalStringValues, TestGenerateDocumentationDeferredValues, and TestFeaturesDocumentationDeferredValues.

    Agent skills

    dcb includes an agent skill that tells a coding agent how to use the commands, results, and exit codes.

    dcb skills --list
    dcb skills dcb
    dcb skills install --dir .agents/skills

    dcb skills dcb returns the skill text in the result’s skills[].content. dcb skills install writes dcb/SKILL.md below the directory given with --dir. The default is .agents/skills.

    Build exporters

    dcb build --output accepts a short directory path or a CSV exporter specification. Quote an attribute that contains commas, for example 'type=image,"name=example.test/sample:one,example.test/sample:two"'. Exporter attributes pass to BuildKit. Available exporters and attributes depend on its worker and the Docker image store.

    ValueResult
    results or type=local,dest=resultsImage filesystem in a local directory
    type=tar,dest=result.tarImage filesystem as a tar archive
    type=oci,dest=image.tarOCI image archive
    type=oci,tar=false,dest=layoutOCI image layout directory
    type=docker,dest=image.tarDocker image archive through the Engine API on the Docker daemon
    type=dockerLoad into Docker
    type=image,name=example.test/sample:latestExport a named image
    type=registry,name=example.test/sample:latestPush a named image, using existing registry credentials
    type=image,name=example.test/sample:latest,push=truePush a named image, using existing registry credentials
    type=cacheonlyKeep the build result in the build cache

    --image-name supplies the default image name when name= is absent. type=registry is shorthand for the image exporter with push=true. The Docker daemon path uses the Engine API for push and Docker archive export. A separate BuildKit uses its native exporters. The classic Docker image store does not provide the OCI exporter. Enable Docker’s containerd image store or use a separate BuildKit worker for OCI export.

    --output - means a filesystem tar archive on stdout. dest=- also streams archives to stdout; omitted destinations stream tar and OCI archives to stdout. In these modes, stdout contains only archive bytes, with no JSON result line. Archive output to an interactive terminal is refused with exit code 2 and a JSON error result. Redirect stdout to a pipe or file, or set a file destination. Errors and progress go to stderr. type=docker without a destination loads into Docker and retains the JSON result line. Other explicit exports return output in the JSON result; local exports also return outputDir.

    Output files are opened when the exporter writes them. Existing output files can be replaced, as with buildx. Local exports use BuildKit’s directory merge behavior.

    GPU availability

    dcb up --gpu-availability detect checks the Docker Engine NVIDIA runtime when hostRequirements.gpu is true, "optional", or an object. The default is detect. When support is found, dcb requests all GPUs through Engine device requests. Without support, creation continues; a required GPU produces a warning.

    --gpu-availability all requests GPUs without detection when the configuration requires them. Docker rejects creation if it cannot satisfy the request. --gpu-availability none suppresses automatic GPU requests. Explicit GPU selection in runArgs is preserved in every mode. For Compose, dcb adds a deploy.resources.reservations.devices GPU reservation to the primary service under the same availability rules. Explicit Compose GPU reservations and gpus selection take precedence and are preserved in every mode. Detection uses the Engine API without a host command.

    Data folders

    --user-data-folder selects the host folder for CLI state on build, exec, read-configuration, run-user-commands, set-up, and up. Feature staging and build staging use a unique private directory inside this folder. Explicit --work-dir selects the build staging location instead. Existing contents of these folders are preserved. Feature configuration results retain their staging paths; temporary metadata staging is removed after use.

    Compose up also writes inline configs and environment-backed configs/secrets in a private directory inside --user-data-folder. Without this option it uses $TMPDIR/dcb-compose. File-backed sources are mounted directly and preserved. An unmounted Docker volume records each private directory’s ownership, without secret values. down discovers these records without the creation-time folder option or source configuration, removes the containers and their materialized files, then removes the records. Cleanup failures return an error and retain the record for a later down retry. Successful recreation removes previous materialized roots once no container mounts them; a reused service or another container can keep an older root. An interrupted or failed up can leave materialized files; run down for that workspace to remove them.

    --container-data-folder selects lifecycle and dotfiles marker storage on up, set-up, run-user-commands, and exec. Relative paths resolve from the container user’s home. The default is .devcontainer under that home. exec writes no lifecycle markers. Lifecycle hook markers retain upstream’s write-before-hook behavior; a failed hook remains marked for that container creation or start. Dotfiles completion markers are written after a successful install.

    --container-system-data-folder is accepted on those same commands. Its upstream default is /var/devcontainer. On up and set-up, dcb appends the container environment to /etc/environment, then preserves the container PATH in /etc/profile. These root edits run once per container, with markers in this folder, including when --skip-post-create is set. That flag skips the user environment probe.

    --container-session-data-folder is accepted on up, set-up, and run-user-commands, with no default. When supplied, dcb stores the probed environment in env-<probe-mode>.json inside this container folder and reuses it on later calls with the same folder and mode. Read, parse, or write failures are non-fatal. No cache is read or written when the probe mode is none or lifecycle commands are skipped. Remote environment overrides and lifecycle secrets are not added to the cached probe result.

    Terminal dimensions

    up, set-up, run-user-commands, read-configuration, and exec accept --terminal-columns and --terminal-rows. Both are numbers with no explicit size by default. Supply both flags together; a missing partner is a usage error (exit 2), including when the supplied value is zero. If either value is zero, the explicit size is not used.

    Lifecycle commands use the supplied size for their pseudo-terminals in both single-container and Compose mode, including named parallel commands. Internal environment and marker probes keep their ordinary streams. read-configuration accepts the same output settings without changing its configuration result. For exec, dimensions size an allocated terminal; use --tty to allocate it. Piped exec output retains separate stdout and stderr. Initial PTY sizing needs Engine API 1.42 or later; the Docker SDK omits explicit sizes when it negotiates an older API.

    dcb up --workspace-folder . --terminal-columns 100 --terminal-rows 40
    dcb set-up --container-id sample --workspace-folder . \
      --terminal-columns 100 --terminal-rows 40
    dcb run-user-commands --workspace-folder . \
      --terminal-columns 100 --terminal-rows 40
    dcb read-configuration --workspace-folder . \
      --terminal-columns 100 --terminal-rows 40
    dcb exec --workspace-folder . --tty \
      --terminal-columns 100 --terminal-rows 40 -- sh

    Workspace mounts and container selection

    dcb up bind-mounts the workspace into a single container. These flags change the mount, add mounts, and select the container. Compose configurations mount what their Compose files declare; --mount and --id-label also apply to the primary Compose service.

    FlagCommandsDefaultMeaning
    --mount-workspace-git-rootup, exec, read-configuration, run-user-commandstrueMount the top folder of the workspace’s Git working tree at /workspaces/<name>. The workspace folder is its subfolder there, for example /workspaces/repository/app. Outside a Git working tree, the workspace folder is mounted. --mount-workspace-git-root=false mounts only the workspace folder.
    --mount-git-worktree-common-dirup, exec, read-configuration, run-user-commandsfalseFor a linked worktree added with git worktree add --relative-paths, also mount the repository’s common Git folder, so Git works in the container. Applies only with Git-root mounting.
    --git-prefixup, exec, read-configuration, run-user-commandsnoneThe prefix of the host Git’s build: the folder that Git puts in place of %(prefix) in an include.path. Without it, such an include followed by a relative path is not read. A relative value starts at the current folder. The upstream CLI has no such flag; it runs git, which knows its prefix.
    --workspace-mount-consistencyupcachedconsistent, cached, or delegated. On macOS and Windows hosts the workspace mount gets this consistency; Linux hosts ignore it, as upstream does.
    --mountupnoneAn additional mount, type=<bind|volume>,source=<source>,target=<target>[,external=<true|false>]. Repeat the flag for more mounts. external=true marks a Compose named volume as external.
    --id-labelup, exec, read-configuration, run-user-commands, down, inspectnoneA name=value label. Repeat the flag for more labels.

    The Git root is found from the files in the workspace’s parent folders and Git’s system and global config files; dcb does not run git. It reads these files with the credentials of its own process; see Process credentials. Discovery follows the latest stable Git release, except where this section names another Git release. dcb does not check the version of the installed git; the upstream CLI runs it, so an older Git can mount a different folder. As Git does when it starts, dcb first reads the system config file and then the global config files. The system file is the file that GIT_CONFIG_SYSTEM names, unless GIT_CONFIG_NOSYSTEM is true; a GIT_CONFIG_NOSYSTEM value that Git does not read as a boolean gives no Git root. As Git does, dcb removes . components and repeated separators from GIT_CONFIG_SYSTEM, and removes each .. component with the component before it, without reading the file system. When a .. component has no component before it, the path keeps the changes made until then. Git 2.32 opens GIT_CONFIG_SYSTEM as given, so link/../system.cfg names a file beside the folder that link points to, and the upstream CLI with that Git can mount a different folder. Without GIT_CONFIG_SYSTEM, Git reads the system file that its build names, such as /etc/gitconfig; dcb does not read that file. The global config files are the file that GIT_CONFIG_GLOBAL names, or else $XDG_CONFIG_HOME/git/config, or $HOME/.config/git/config when XDG_CONFIG_HOME is empty or unset, and then $HOME/.gitconfig. Relative paths start at the workspace folder. A config file that Git rejects gives no Git root: a syntax error, or an access failure other than a missing file or denied permission. Git reads the files again after setup, from its working folder: the working tree, after symbolic links are resolved, when the workspace folder is inside it, and otherwise the workspace folder. Relative paths then start at that folder. A folder or another file that Git cannot open then also gives no Git root, and so does a system file that the process cannot read because permission is denied. Each read takes a special file, such as a terminal or a FIFO, until it ends, and a read error ends the file, as in Git. As in Git, a FIFO read waits, with no time limit, until a writer opens the FIFO, and the file ends when the last writer closes it. Both reads follow include.path entries when Git reads them, as Git does. A relative include path starts at the including file’s folder, ~/ starts at HOME, and ~name/ at the home folder of that account. Git asks every account service that the host names, such as /etc/passwd, LDAP or systemd. The Linux release builds look up accounts in /etc/passwd only. They skip a ~name/ include whose account is not there, as if the included file were missing. Git reads that file for an account that another service knows, and rejects a name that no service knows. When Git rejects the name or that file, the upstream CLI mounts the workspace folder, and dcb mounts the Git root. This is an approved difference; see Relation to the Dev Container CLI. Builds with cgo and macOS builds ask the account services, as Git does. A %(prefix)/ path followed by an absolute path names that path, as in Git. Git reads %(prefix)/ followed by a relative path at the prefix of its build, a separator and the rest. That prefix is the folder that Git was built to be installed in, usually the folder above the bin folder of the git command, such as /usr for /usr/bin/git; Homebrew builds name their opt folder, such as /opt/homebrew/opt/git. dcb does not run git, so --git-prefix names that prefix, and dcb then reads that path, as Git does. A relative --git-prefix starts at the current folder of the command. A relative Options.GitPrefix of the library gives a relative path, which starts at the including file’s folder. A command-line include has no including file, so a relative expanded path is rejected. Without --git-prefix, dcb does not read such an include, as if the included file were missing. When Git rejects that file, the upstream CLI mounts the workspace folder, and dcb mounts the Git root. This is an approved difference; see Relation to the Dev Container CLI. A missing included file is skipped. An include without a value, a ~ path that cannot be expanded, an access failure other than a missing file, a file that Git cannot open, such as a folder, a syntax error, and more than 10 nested includes give no Git root. As in Git, a relative config file name, and a relative include in that file, is looked up from the folder that the read starts at, so the length of that folder’s path does not limit it, and the process needs only search permission on that folder, as Git’s chdir does. On macOS, a thread of its own changes to that folder to look the name up. On hosts other than Linux and macOS, dcb joins the name to the folder’s path instead. The read after setup then also reads the repository config files of the format check, described below, with their includes. Last, it reads command-line config from the environment: GIT_CONFIG_COUNT pairs named GIT_CONFIG_KEY_0 and GIT_CONFIG_VALUE_0, and the following indices, then GIT_CONFIG_PARAMETERS. A count, missing pair, key or parameter quoting that Git rejects gives no Git root. GIT_CONFIG_PARAMETERS accepts both 'key=value' and 'key'='value' forms. These entries also follow include.path, with the account and prefix rules above. The expanded path must be absolute: a relative include has no file whose folder can be its base, and gives no Git root. A relative include inside that file starts at that file’s folder. A missing absolute include is skipped. After the full read finishes, any core.ignorecase, core.filemode, core.symlinks, core.trustctime, core.quotepath, core.ignorestat, core.sparseCheckout, core.sparseCheckoutCone, core.protectHFS, core.protectNTFS, core.precomposeUnicode, core.fsyncObjectFiles, core.safecrlf, core.autocrlf, core.lockfilePid, sparse.expectFilesOutsideOfPatterns, pager.color, color.pager, user.useConfigOnly, advice.detachedHead or advice.statusHints value that Git cannot read as a boolean gives no Git root, even if a later value is valid. core.safecrlf also accepts warn, without regard to case. core.autocrlf also accepts input, without regard to case. Native discovery follows current Git for core.lockfilePid; Git before 2.54 ignores that key. A key without a value is true; an empty value is false. Boolean words and integers follow Git’s boolean rules. The initial read and repository-format read do not apply these keys’ types. Other typed values from the full config read and includeIf conditions are not applied. As Git does, discovery walks the physical parent folders, after symbolic links in the workspace folder’s path are resolved. The Git root keeps the workspace folder’s path, up as many folders as Git walked, as the upstream CLI resolves Git’s relative result. GIT_CEILING_DIRECTORIES stops parent discovery before a listed ancestor. Entries must be absolute paths. Git resolves symlinks in entries until an empty path-list entry disables that resolution. Entries after that marker retain literal path components. A ceiling equal to the starting folder does not stop discovery. Parent discovery also stops at the first physical parent folder on another file system, unless GIT_DISCOVERY_ACROSS_FILESYSTEM is true. Git compares device numbers, so a bind mount of the same file system is not a boundary. A value that Git does not read as a boolean gives no Git root. After a repository is found, GIT_WORK_TREE selects its working tree. Relative values start at the workspace folder; Git resolves symlinks and path components. An empty value or an invalid intermediate component gives no Git root. For an outside-tree override, trailing whitespace is removed from the resolved path as in the upstream CLI probe. An explicitly empty GIT_DIR, or one that names a missing repository or an invalid gitfile target, gives no Git root; parent discovery does not replace it. As in Git 2.54 and later, when stat fails on a .git entry, parent discovery continues only if the entry is missing, which includes a symbolic link to a missing path or through a regular file. Any other stat failure gives no Git root: for example a symbolic link loop, a name that is too long, or a link into a folder that cannot be searched. A .git entry that is not a folder must be a gitfile: a gitfile that cannot be opened or read, or a special file such as a FIFO, also gives no Git root. A .git folder is tested as a repository instead, and a folder that fails that test, such as one that cannot be read, does not stop parent discovery. Gitfiles must start with literal gitdir: followed by one space and a repository path. Only final CR and LF bytes are removed; other whitespace and interior newlines belong to the path. Gitfiles larger than 1 MiB give no Git root. A relative gitfile target starts at the gitfile’s folder and keeps its path components, as Git does: a symbolic link is resolved before a later .., and a regular file or a missing folder before .. gives no Git root. Repository admission checks the first 255 bytes of HEAD for a ref: target starting with refs/ or a 40-digit hexadecimal object ID. A symbolic-link HEAD must point to a path starting with refs/; its target need not exist. A commondir pointer selects the common folder whose objects and refs paths must pass Git’s executable-access check. Relative pointers start at the administrative folder; final CR and LF bytes are removed. A failed pointer read or path resolution ends discovery. A resolved folder without those signatures is not a repository; parent discovery can continue past a .git directory. After the .git entry, each folder is also tested as a repository, as Git does. When the workspace is in a bare repository or in a .git folder, Git reports no working tree, and the workspace folder is the Git root. The global safe.bareRepository setting is not read. GIT_OBJECT_DIRECTORY replaces the objects signature path during admission. Relative values start at the workspace folder and retain physical path traversal. An empty or inaccessible value fails admission; refs is still checked in the common folder. GIT_COMMON_DIR replaces the commondir pointer, which is then not read. Relative values start at the workspace folder and retain physical path traversal; an empty value checks /objects and /refs, as Git does. Git keeps the value as given: its repository format check starts a relative value at the folder where it found the repository, or at the workspace folder for GIT_DIR, and its config read after setup at its working folder. The common-folder mount option reads pointer lines separately, as upstream does. Git’s repository format check then reads the common folder’s config, and config.worktree when extensions.worktreeConfig is set. Config that Git rejects gives no Git root: a syntax error, a directory or an unreadable file, a core.repositoryformatversion that is not an integer or is above 1, an invalid core.bare, extensions.worktreeConfig or extensions.preciousObjects boolean, and core.worktree or extensions.partialClone without a value. A special file, such as a FIFO or a device, is read as a stream. A FIFO waits for a writer with no time limit. The format check stops at the first syntax or typed value error, or at the end of the stream; a read error also ends the stream. The format check does not follow includes. The full read after setup opens these files again and follows their includes. Extensions follow Git 2.55 built without Rust. Git checks each extension value whatever the repository version: extensions.objectFormat and extensions.compatObjectFormat must be sha1 or sha256, extensions.refStorage must name files or reftable, optionally followed by :// and a payload, and extensions.relativeWorktrees and extensions.submodulePathConfig must be booleans. A second extensions.compatObjectFormat gives no Git root. When core.repositoryformatversion is 1, an extension that Git does not know gives no Git root, and so does any extensions.compatObjectFormat. When it is 0, extensions.noop-v1 or one of these version 1 extensions gives no Git root; other unknown extensions are ignored. dcb applies these rules whatever Git is installed. The upstream CLI runs the installed git, and other Git versions and builds accept other repositories, so the two can mount different folders: Git before 2.54 does not know extensions.submodulePathConfig or the :// payload form, Git before 2.48 extensions.relativeWorktrees, Git before 2.45 extensions.compatObjectFormat and Git before 2.44 extensions.refStorage. Git 2.45 to 2.53, and Git built with Rust, accept a compatibility object format that differs from the object format. Integers are read as Git built against a C23 C library, such as glibc 2.38 or later, reads them: decimal, octal with a leading 0, hexadecimal with 0x and binary with 0b, with an optional k, m or g unit. When core.repositoryformatversion is 0 or 1, a true core.bare leaves a discovered repository without a working tree, and the workspace folder is the Git root. A linked worktree and GIT_COMMON_DIR ignore core.bare in the common config. Otherwise core.worktree selects the working tree, with the same version and common-config rules; GIT_WORK_TREE and a true core.bare take precedence. An absolute value is resolved as GIT_WORK_TREE is. A relative value starts at the repository folder and must name an existing folder that the effective user and capabilities can search, as Git’s chdir requires; its symbolic links are resolved before .. components. Git changes to the repository folder first, so the folder and the value are separate lookups, each with its own symbolic link and path length limits. A .git folder above the workspace folder and a gitfile target first pass through Git’s own path resolution, which allows 33 symbolic links. On Linux, the selected folder is named from /proc, or without it by a thread that unshares its working folder. Where a seccomp profile also blocks unshare, as Docker’s default profile does, both lookups are repeated one component at a time, so the value can pass a folder whose physical path exceeds PATH_MAX; the selected folder’s own path must fit in PATH_MAX, as Git’s working folder must. Builds for hosts other than Linux and macOS instead join the repository folder’s physical path and the value, which must fit the host’s path length limit. An empty value or one that Git cannot resolve gives no Git root. Inside that working tree, the Git root keeps the workspace folder’s path; outside it, the Git root is the working tree’s physical path. The format check does not follow includes, and other config keys are not interpreted. Relative values start at the workspace folder. A valid GIT_DIR selects the workspace folder without parent discovery; GIT_WORK_TREE or the repository config can select its working tree. Other Git environment overrides and ownership checks are not applied. exec, run-user-commands, set-up, and an up that reuses a container use the folder where that container mounts the workspace, read from its bind mounts, whatever flags created it. A configured workspaceFolder still wins. When the container mounts no folder that holds the workspace, these commands calculate the folder from the flags, as read-configuration does. read-configuration returns the folder and mounts in its workspace object. A configuration that sets workspaceMount keeps its mount. An explicitly empty "workspaceMount": "" disables the default workspace mount; additional mounts and --mount values still apply. read-configuration keeps the empty workspaceMount value. A configuration that sets workspaceFolder without workspaceMount keeps that folder and mounts the workspace or its Git root at /workspaces/<name>, according to the Git-root and worktree flags. Set workspaceMount to mount at a specific target.

    Apart from the workspace mount, dcb mounts no host path that is not in the configuration or in a flag. The default Git-root mount exposes the Git root, which can hold more than the workspace folder; use --mount-workspace-git-root=false to keep the mount to the workspace folder. A relative --mount bind source resolves against the current directory, or against the Compose project folder for a Compose configuration.

    Id labels

    --id-label labels also derive ${devcontainerId} in place of the workspace labels devcontainer.local_folder and devcontainer.config_file. How they select a container depends on the configuration mode.

    For a single-container configuration, the id labels select the container in place of the workspace labels. A container qualifies only if it carries every given label.

    • up reuses a container with every label, or creates one with exactly these labels and no workspace labels. inspect --id-label finds that container; down --id-label removes only a container that carries every given label. Both commands accept repeated labels and work without a configuration file.
    • up --remove-existing-container removes only a container that has every label. A container that has only some of the labels is not removed.

    For a Compose configuration, up selects and removes as it does without id labels, as upstream does:

    • up finds the primary container by its Compose project and service, and reuses it whatever its labels are. The id labels are set only when up creates the primary container; a changed id label does not recreate it.
    • up --remove-existing-container and down with a loaded Compose configuration remove the workspace’s Compose project, even with --id-label; id labels do not widen or narrow that removal. The project-conflict check still refuses a project that holds another workspace’s containers.
    • The id labels are added to the primary container beside the workspace labels, so the conflict check and workspace-scoped removal also cover it.

    With id labels and no loaded configuration, down selects a container by every label. If it is a Compose primary, down removes the project named by that container’s project label, using its workspace label for the project-conflict check. The primary must carry a workspace ownership label; otherwise down fails. The current folder never supplies the project to remove.

    In both modes, inspect, exec, read-configuration, and run-user-commands select the container that carries every given label. When these commands or down use --id-label without --workspace-folder, --config, or --override-config, they read no configuration. This follows upstream’s label-only selection. The configuration in the current directory is not used.

    Feature controls

    These upstream compatibility controls are hidden from command help. All default to false.

    OptionCommandsEffect when enabled
    --skip-feature-auto-mappingbuild, exec, read-configuration, run-user-commands, upLeave legacy feature identifiers unchanged during resolution
    --skip-persisting-customizations-from-featuresbuildOmit referenced feature customizations from new image metadata
    --omit-config-remote-env-from-metadataupOmit the current configuration’s remoteEnv from new metadata labels
    --omit-syntax-directivebuild, upOmit generated Dockerfile syntax directives and ignore authored syntax directives during builds

    Legacy identifiers such as node, golang, and common map to upstream’s version 1 feature references by default. Explicit OCI references and local feature paths stay unchanged. The skip flag disables this mapping wherever features must be resolved; it does not change an existing container’s features. Unmapped bare legacy identifiers fail as unsupported, matching upstream. The deprecated gradle, maven, and jupyterlab features are first folded into Java or Python feature options, including when identifier mapping is skipped.

    Configuration remoteEnv is persisted in metadata by default, matching upstream. The omit flag affects only the current configuration’s metadata contribution. Runtime commands still receive configured remoteEnv values. Inherited metadata stays intact. The customization control also preserves inherited and configuration customizations, and features still install.

    Syntax omission uses Docker’s bundled Dockerfile frontend. It preserves the build context, other parser directives, and the authored Dockerfile on disk.

    dcb read-configuration --workspace-folder . --include-features-configuration --skip-feature-auto-mapping
    dcb build --workspace-folder . --skip-persisting-customizations-from-features --omit-syntax-directive
    dcb up --workspace-folder . --omit-config-remote-env-from-metadata

    up, set-up, run-user-commands, and exec use --default-user-env-probe=loginInteractiveShell. Select none, interactiveShell, or loginShell to change the default. Configuration userEnvProbe takes precedence. The probe reads the container user’s shell environment before applying remote environment values. If shell startup takes more than 10 seconds, dcb warns and continues with no probed environment.

    Git workspace discovery

    dcbUpstream CLI
    Finds the Git root with the credentials of its own process. See Process credentials.Runs git, which can start with different credentials.
    Finds the Git root as the latest stable Git release does, with the repository format rules of Git 2.55 built without Rust. See Workspace mounts and the decision.Runs the installed git, whose version and build decide which configuration and repository formats it accepts.
    Linux release builds look up accounts in /etc/passwd only. They skip an include.path of ~name/ whose account is not there, as if the included file were missing. Builds with cgo and macOS builds ask the account services. Bob approved this difference. See Workspace mounts.Runs git, which asks every account service of the host. It rejects a name that no service knows, and an included file that it cannot parse, so the upstream CLI finds no Git root.
    Reads an include.path of %(prefix)/ followed by a relative path below the prefix that --git-prefix names, as Git reads it below the prefix of its build. Without --git-prefix, it does not read that file, as if it were missing. Bob approved this default. See Workspace mounts and the decision.Runs git, which reads that file below the prefix of its own build and rejects a file that it cannot parse, so the upstream CLI finds no Git root. Has no --git-prefix flag.

    Process credentials

    dcb and its Go packages read workspace, configuration and Git files with the credentials that the process has at the time of each read: its user and groups, and on Linux its capabilities. They do not change these credentials. A permission that the caller dropped stays dropped, and a permission that the caller holds is used.

    To find the Git root, the upstream CLI starts git. Starting a program can change its credentials, and dcb does not copy these changes:

    • A root process that cleared its effective capabilities can get them back in git, unless SECBIT_NOROOT is set.
    • With SECBIT_NOROOT, the git of a root process starts with only the ambient capabilities of that process.
    • File capabilities or a set-user-ID or set-group-ID bit on git, and a security module that changes domain when git starts, also change its credentials.

    In these cases, dcb and the upstream CLI can find different Git roots and mount different folders. When the process has the credentials that a started git gets, credentials cause no difference between them. A dcb started from a shell has these credentials when neither dcb nor git has file capabilities, a set-user-ID or set-group-ID bit, or a security module domain change when it starts. The Dev Container specification does not define how the Git root is found.

    Published Template metadata

    dcb templates metadata ghcr.io/example/templates/sample:1

    templates metadata reads the dev.containers.metadata annotation from an OCI artifact manifest. It writes the annotation’s JSON value directly to stdout, without the usual result envelope. Missing references or metadata write {} and exit 1. Diagnostics use stderr. It does not download Template files. References include a registry and repository, use latest when no tag is given, and accept @sha256:... to verify a specific manifest. Reference text is lowercased, as upstream. A reference includes a collection namespace before its Template name. Registry authentication uses Docker client credentials; exactly localhost uses plain HTTP, as upstream. Malformed metadata, missing manifest configuration, and digest mismatches exit 1 without JSON on stdout.

    Generate Template documentation

    dcb templates generate-docs --project-folder ./templates

    Each visible child of --project-folder (alias -p, default .) supplies a devcontainer-template.json and optional NOTES.md. The command replaces its README.md with the Template name, description, options, notes and metadata link. It skips children without an ID or metadata file. It does not search src automatically. --github-owner and --github-repo set the metadata link. Existing README symlinks are unlinked before writing. A failure can leave other children already written. Success writes no stdout; progress uses stderr. The library entry point is templates.GenerateDocumentation.

    Report Feature versions

    dcb outdated --workspace-folder .
    dcb outdated --config .devcontainer/devcontainer.json --output-format json

    The default text table has Feature, Current, Wanted, and Latest columns. Unavailable values appear as -. JSON returns a features object keyed by the configured identifiers, in configuration order. Each entry can have current, wanted, wantedMajor, latest, and latestMajor; unavailable fields are omitted. The report has no outcome field. Operation failures use stderr and exit 1.

    Current is the lockfile version, or wanted when there is no locked version. Wanted is the newest full semver tag that satisfies the configured tag; latest selects the highest published semver, including prereleases. Latest is the highest published full semver regardless of the configured tag. Major and minor alias tags are excluded from the version list. Digest references use the locked version, then manifest metadata, then metadata in the first Feature layer. Local paths and identifiers that cannot be versioned are omitted. Unavailable or syntactically invalid registry tag responses leave version fields unavailable. A valid response with a non-array tags value fails the operation when that value is truthy, as upstream does.

    Discovery checks .devcontainer/devcontainer.json, then .devcontainer.json in --workspace-folder (default current folder). --config is relative to the current folder. A reporting configuration does not require image or build fields. Feature identifiers are object keys and are not substituted; options do not change the report. Lockfiles are read beside the configuration and are unchanged.

    --user-data-folder is accepted and unused, as upstream. --log-level accepts info, debug, or trace; --log-format accepts text or json. --terminal-columns and --terminal-rows must be supplied together. Reporting uses native OCI HTTP and existing registry credentials; no Docker daemon is needed.

    Published Feature information

    dcb features info manifest ghcr.io/example/collection/sample:1 --output-format json
    dcb features info tags ghcr.io/example/collection/sample:1
    dcb features info dependencies ghcr.io/example/collection/sample:1
    dcb features info verbose ghcr.io/example/collection/sample:1

    Every mode requires a published Feature manifest. manifest includes the canonical digest identifier. tags preserves registry order and duplicate tags. dependencies renders a Mermaid flowchart with hard and soft dependencies. verbose combines these reports. Text is the default; --output-format json emits manifest, canonicalId, and publishedTags when requested. JSON dependencies emits {} without loading dependency metadata. Registry authentication uses the existing Docker credential configuration. These reports do not stage Features or write a lockfile.

    Resolve Feature dependencies

    dcb features resolve-dependencies --workspace-folder .

    This command reads .devcontainer.json first, then .devcontainer/devcontainer.json. It prints a Mermaid flowchart followed by pretty JSON with an ordered installOrder array. Each entry has id and options; OCI identifiers use the resolved manifest digest. Options retain object property order and scalar string or boolean values.

    Resolution supports local Features, HTTPS archives, OCI metadata annotations, and metadata from OCI layers. It applies configuration substitutions, lockfile integrity pins, hard dependencies, soft dependencies, aliases and overrideFeatureInstallOrder. It does not install Features or write lockfiles. A dependency cycle returns exit code 1 after printing the graph. An absent or empty features object fails before graph output. --log-level accepts error (default), info, debug, or trace.

    If HTTPS archive metadata cannot be retrieved or its integrity check fails, reporting retains the archive entry without dependency metadata, as upstream does. The report does not establish that a later installation will succeed.

    Upgrade Feature requirements and lockfiles

    dcb upgrade --workspace-folder .
    dcb upgrade --workspace-folder . --dry-run
    dcb upgrade --config .devcontainer/devcontainer.json \
      --feature registry.example/collection/sample --target-version 2

    upgrade resolves configured Features and their dependencies and writes the lockfile beside the configuration. Discovery checks .devcontainer/devcontainer.json before .devcontainer.json. Existing lockfile integrity pins remain in use. The paired --feature (-f) and --target-version (-v) flags change the first matching Feature requirement. The target accepts x, x.y, or x.y.z. Configuration comments and formatting remain, with upstream’s global regular expression replacement of the matching identifier throughout the file.

    --dry-run prints pretty lockfile JSON with a trailing newline. It still writes a requested configuration change and refreshes an existing lockfile during resolution. Without --dry-run, success writes no stdout and forces a canonical lockfile rewrite. A resolution failure can leave the configuration update in place. An absent or empty Features object fails. Operation errors go to stderr and exit 1; invalid invocations use the normal JSON error result and exit 2.