Why this matters
Teams standardizing on Dev Containers need the CLI path (devcontainer up /
devcontainer exec) to behave like the IDE path. A container definition that
works when opened in VS Code should work the same way under the CLI. Today it
doesn't for anything that depends on host↔container interactivity.
Without that parity, each CLI user ends up reproducing the IDE's behavior by
hand — working out per-platform socket paths, hand-writing mounts entries,
wiring up ad-hoc port publishing — or reaching for a non-standard alternative
tool. Either way, work that should live once in the shared container
definition gets pushed onto individual machines instead.
This issue is intentionally proposal-level: it names the pattern rather than
proposing a single implementable change. If the project agrees with the
premise, we'd expect it to be split into individual issues per use case for
actual implementation.
The gap
When an IDE (e.g. VS Code Remote - Containers) opens a Dev Container, it does a
significant amount of host integration work that makes the container feel
local:
- it forwards ports the container starts listening on back to the host,
- it provides a mechanism for processes in the container to open a URL in the
host's browser,
- and it auto-mounts host files and sockets the container needs to act like
the host's environment — the SSH agent socket, ~/.gitconfig,
~/.ssh/known_hosts, credential-helper state — without any of this being
declared in devcontainer.json.
It also manages the lifecycle of all of these bridges for as long as the
window is connected.
The Dev Containers CLI (@devcontainers/cli, devcontainer up / devcontainer exec) builds the container, applies declared mounts, propagates
containerEnv / remoteEnv, and runs the lifecycle commands — but it does
not provide equivalents for that host-integration layer. What exists is
limited and client-oriented:
appPort is published statically as -p 127.0.0.1:<port>:<port> at
container-create time. This only works for ports known ahead of time; it
cannot cover a port chosen at runtime inside the container.
forwardPorts / portsAttributes are parsed and merged into the resolved
configuration, but the CLI takes no action on them.
- The CLI emits
tunnelInformation describing already-published ports, for a
client to act on — it does not itself forward anything.
mounts (and Feature-contributed mounts) are applied as static
--mount/compose-volume entries, exactly like appPort — but only for
mounts the user explicitly declares. The CLI has no notion of "mount the
host's SSH agent socket" or "mount the host's git identity" the way VS
Code's extension does automatically; reproducing that today means manually
working out host-specific paths (Docker Desktop vs. OrbStack vs. Colima vs.
Linux all differ) and hand-writing the equivalent mounts entries yourself,
per machine.
There is no dynamic port forwarding (watching for newly-listening ports,
forwarding ephemeral ones, tearing down on disconnect), no host-browser
bridge, and no host-credential/file auto-mounting. That active layer lives
entirely in the IDE clients.
For workflows that only need to build and exec, this is fine. But for anything
that depends on host↔container interactivity — networking, browser-based auth,
or using the host's SSH/git identity inside the container — there is no
supported path to parity with the IDE experience, and no documented pattern
for achieving it.
Related existing issues
Pieces of this gap already have open issues, filed independently and without a
common frame:
- SSH agent forwarding: cli#441
— maintainers confirmed this is "part of the extension, not the CLI."
known_hosts import: cli#439
- Global
~/.gitconfig not copied (only local config is): cli#787
- Dynamic / runtime mount support: cli#1110,
cli#601
- Port forwarding reference implementation: cli#22
(maintainers noted it "requires NodeJS inside the container")
forwardPorts not honored: cli#186
- No
stop/down command: cli#386
This issue isn't meant to duplicate those — it's meant to name the pattern
they share (host-integration behavior VS Code's extension does implicitly,
that the CLI has no equivalent for) and ask for a single, coherent answer
about whether/how the CLI intends to address it, rather than resolving each
symptom in isolation.
Concrete scenario
A common failure case is a CLI tool, run inside the container, that performs a
browser-based OAuth login:
- The tool starts a temporary loopback callback server on
127.0.0.1:<random_port> inside the container.
- It opens (or prints) an authorization URL for the user to complete in a
browser.
- The identity provider redirects to
http://127.0.0.1:<port>/callback.
Under an IDE this "just works": the browser opens on the host, and the IDE has
already forwarded the container's 127.0.0.1:<port> to the host loopback, so
the callback reaches the listener.
Under devcontainer up (or a bare docker run the CLI wraps), neither piece is
present:
- The URL may open in the container's context, where there is no browser.
- Even if the user opens it on the host,
127.0.0.1:<port> on the host is not
the container's loopback, so the callback never reaches the listener
("connection refused").
The same class of gap affects any dev server, debugger, or webhook receiver that
expects the IDE's automatic port forwarding.
What IDEs do that the CLI doesn't
- Dynamic port forwarding — auto-forwarding ports the container begins
listening on, plus honoring forwardPorts / portsAttributes, and tearing
the forwards down on disconnect.
- Host browser bridge — a mechanism (e.g. surfacing a helper as
$BROWSER)
so a URL "opened" inside the container is actually opened on the host.
- Host file/socket auto-mounting — the SSH agent socket,
~/.gitconfig,
~/.ssh/known_hosts, and credential-helper state are mounted or copied in
automatically, without a mounts entry, and adapted per-platform (the
socket path for Docker Desktop, OrbStack, and native Linux all differ).
Where this shows up in the source
References below are to @devcontainers/cli v0.88.0.
appPort is the only port setting that produces a runtime action — published
statically as -p 127.0.0.1:<port>:<port> in spawnDevContainer
(src/spec-node/singleContainer.ts).
forwardPorts / portsAttributes / otherPortsAttributes are defined in the
config schema (src/spec-configuration/configuration.ts) and merged via
mergeForwardPorts (src/spec-node/imageMetadata.ts), but forwardPorts is
never read outside that merge — nothing forwards based on it.
getTunnelInformation (src/spec-node/utils.ts) maps already-published
container.Ports into environmentTunnels metadata on the resolver result
(consumed in singleContainer.ts / dockerCompose.ts), and only for local
containers. It reports published ports; it does not forward.
- No host-browser handling is present — the source has no
openExternal,
$BROWSER, or xdg-open path.
mounts (src/spec-configuration/configuration.ts) — including
Feature-contributed mounts — are resolved into --mount args in
spawnDevContainer (src/spec-node/singleContainer.ts) or compose volume
entries (src/spec-node/dockerCompose.ts), but only for entries present in
the resolved config. There is no built-in mount (or equivalent copy step)
for the SSH agent socket, host git identity, known_hosts, or credential
helpers — those must be declared by hand, and the correct host path is
platform-dependent (Docker Desktop vs. OrbStack vs. Colima vs. Linux), which
the CLI has no logic to resolve.
Ask
Since the goal is CLI/IDE parity, we'd like guidance and, ideally, first-class
support, on all three host-integration gaps (port forwarding, browser bridge,
credential/file mounts) at the proposal level — they share the same shape and
we think a single answer should cover them, rather than resolving each in
isolation as its own issue. If the project agrees with the premise, we expect
the following directions to become individual implementation issues split by
use case; here they're presented together for the proposal-level discussion,
roughly in order of preference:
- Build (a subset of) these into the CLI — e.g. an opt-in
devcontainer up --forward-ports ... and/or honoring forwardPorts; a
documented host-browser hook; and an opt-in flag or config surface that
mounts the host's SSH agent socket / git identity / known_hosts, with the
CLI resolving the correct host path per platform the way the VS Code
extension does.
- A supported wrapper / extension point — a stable interface the CLI
exposes so a small host-side wrapper can implement forwarding,
browser-opening, and credential mounting without reimplementing container
orchestration. (Third-party tools like
cella already do this outside the
CLI, as a native-binary wrapper — evidence this is solvable without
requiring Node.js inside the container.)
- Documentation — if this is intentionally out of scope for the CLI, a
canonical guide on how to reproduce the IDE's forwarding, browser, and
credential-mounting behavior around devcontainer up (e.g. recommended
docker flags, socat/SSH patterns, per-platform socket paths) would
close the knowledge gap.
Questions for maintainers
- Is host integration (forwarding, browser, credential/file mounts)
considered in-scope for the CLI, or intentionally left to IDE clients? If
it's intentionally out of scope, is that documented anywhere we can link to
and close related issues against (441, 439, 787, 1110, 601, 22, 186)?
- Is there an existing recommended pattern for the OAuth-loopback case above,
or for forwarding the host's SSH agent / git identity into a CLI-managed
container?
- Would a
forwardPorts-honoring flag on up, or a flag to auto-mount the
host's SSH agent socket and git identity, be a welcome contribution?
Co-Authored-By: Claude Code
Why this matters
Teams standardizing on Dev Containers need the CLI path (
devcontainer up/devcontainer exec) to behave like the IDE path. A container definition thatworks when opened in VS Code should work the same way under the CLI. Today it
doesn't for anything that depends on host↔container interactivity.
Without that parity, each CLI user ends up reproducing the IDE's behavior by
hand — working out per-platform socket paths, hand-writing
mountsentries,wiring up ad-hoc port publishing — or reaching for a non-standard alternative
tool. Either way, work that should live once in the shared container
definition gets pushed onto individual machines instead.
This issue is intentionally proposal-level: it names the pattern rather than
proposing a single implementable change. If the project agrees with the
premise, we'd expect it to be split into individual issues per use case for
actual implementation.
The gap
When an IDE (e.g. VS Code Remote - Containers) opens a Dev Container, it does a
significant amount of host integration work that makes the container feel
local:
host's browser,
the host's environment — the SSH agent socket,
~/.gitconfig,~/.ssh/known_hosts, credential-helper state — without any of this beingdeclared in
devcontainer.json.It also manages the lifecycle of all of these bridges for as long as the
window is connected.
The Dev Containers CLI (
@devcontainers/cli,devcontainer up/devcontainer exec) builds the container, applies declaredmounts, propagatescontainerEnv/remoteEnv, and runs the lifecycle commands — but it doesnot provide equivalents for that host-integration layer. What exists is
limited and client-oriented:
appPortis published statically as-p 127.0.0.1:<port>:<port>atcontainer-create time. This only works for ports known ahead of time; it
cannot cover a port chosen at runtime inside the container.
forwardPorts/portsAttributesare parsed and merged into the resolvedconfiguration, but the CLI takes no action on them.
tunnelInformationdescribing already-published ports, for aclient to act on — it does not itself forward anything.
mounts(and Feature-contributed mounts) are applied as static--mount/compose-volume entries, exactly likeappPort— but only formounts the user explicitly declares. The CLI has no notion of "mount the
host's SSH agent socket" or "mount the host's git identity" the way VS
Code's extension does automatically; reproducing that today means manually
working out host-specific paths (Docker Desktop vs. OrbStack vs. Colima vs.
Linux all differ) and hand-writing the equivalent
mountsentries yourself,per machine.
There is no dynamic port forwarding (watching for newly-listening ports,
forwarding ephemeral ones, tearing down on disconnect), no host-browser
bridge, and no host-credential/file auto-mounting. That active layer lives
entirely in the IDE clients.
For workflows that only need to build and exec, this is fine. But for anything
that depends on host↔container interactivity — networking, browser-based auth,
or using the host's SSH/git identity inside the container — there is no
supported path to parity with the IDE experience, and no documented pattern
for achieving it.
Related existing issues
Pieces of this gap already have open issues, filed independently and without a
common frame:
— maintainers confirmed this is "part of the extension, not the CLI."
known_hostsimport: cli#439~/.gitconfignot copied (only local config is): cli#787cli#601
(maintainers noted it "requires NodeJS inside the container")
forwardPortsnot honored: cli#186stop/downcommand: cli#386This issue isn't meant to duplicate those — it's meant to name the pattern
they share (host-integration behavior VS Code's extension does implicitly,
that the CLI has no equivalent for) and ask for a single, coherent answer
about whether/how the CLI intends to address it, rather than resolving each
symptom in isolation.
Concrete scenario
A common failure case is a CLI tool, run inside the container, that performs a
browser-based OAuth login:
127.0.0.1:<random_port>inside the container.browser.
http://127.0.0.1:<port>/callback.Under an IDE this "just works": the browser opens on the host, and the IDE has
already forwarded the container's
127.0.0.1:<port>to the host loopback, sothe callback reaches the listener.
Under
devcontainer up(or a baredocker runthe CLI wraps), neither piece ispresent:
127.0.0.1:<port>on the host is notthe container's loopback, so the callback never reaches the listener
("connection refused").
The same class of gap affects any dev server, debugger, or webhook receiver that
expects the IDE's automatic port forwarding.
What IDEs do that the CLI doesn't
listening on, plus honoring
forwardPorts/portsAttributes, and tearingthe forwards down on disconnect.
$BROWSER)so a URL "opened" inside the container is actually opened on the host.
~/.gitconfig,~/.ssh/known_hosts, and credential-helper state are mounted or copied inautomatically, without a
mountsentry, and adapted per-platform (thesocket path for Docker Desktop, OrbStack, and native Linux all differ).
Where this shows up in the source
References below are to
@devcontainers/cliv0.88.0.appPortis the only port setting that produces a runtime action — publishedstatically as
-p 127.0.0.1:<port>:<port>inspawnDevContainer(
src/spec-node/singleContainer.ts).forwardPorts/portsAttributes/otherPortsAttributesare defined in theconfig schema (
src/spec-configuration/configuration.ts) and merged viamergeForwardPorts(src/spec-node/imageMetadata.ts), butforwardPortsisnever read outside that merge — nothing forwards based on it.
getTunnelInformation(src/spec-node/utils.ts) maps already-publishedcontainer.PortsintoenvironmentTunnelsmetadata on the resolver result(consumed in
singleContainer.ts/dockerCompose.ts), and only for localcontainers. It reports published ports; it does not forward.
openExternal,$BROWSER, orxdg-openpath.mounts(src/spec-configuration/configuration.ts) — includingFeature-contributed mounts — are resolved into
--mountargs inspawnDevContainer(src/spec-node/singleContainer.ts) or compose volumeentries (
src/spec-node/dockerCompose.ts), but only for entries present inthe resolved config. There is no built-in mount (or equivalent copy step)
for the SSH agent socket, host git identity,
known_hosts, or credentialhelpers — those must be declared by hand, and the correct host path is
platform-dependent (Docker Desktop vs. OrbStack vs. Colima vs. Linux), which
the CLI has no logic to resolve.
Ask
Since the goal is CLI/IDE parity, we'd like guidance and, ideally, first-class
support, on all three host-integration gaps (port forwarding, browser bridge,
credential/file mounts) at the proposal level — they share the same shape and
we think a single answer should cover them, rather than resolving each in
isolation as its own issue. If the project agrees with the premise, we expect
the following directions to become individual implementation issues split by
use case; here they're presented together for the proposal-level discussion,
roughly in order of preference:
devcontainer up --forward-ports ...and/or honoringforwardPorts; adocumented host-browser hook; and an opt-in flag or config surface that
mounts the host's SSH agent socket / git identity /
known_hosts, with theCLI resolving the correct host path per platform the way the VS Code
extension does.
exposes so a small host-side wrapper can implement forwarding,
browser-opening, and credential mounting without reimplementing container
orchestration. (Third-party tools like
cella already do this outside the
CLI, as a native-binary wrapper — evidence this is solvable without
requiring Node.js inside the container.)
canonical guide on how to reproduce the IDE's forwarding, browser, and
credential-mounting behavior around
devcontainer up(e.g. recommendeddockerflags,socat/SSH patterns, per-platform socket paths) wouldclose the knowledge gap.
Questions for maintainers
considered in-scope for the CLI, or intentionally left to IDE clients? If
it's intentionally out of scope, is that documented anywhere we can link to
and close related issues against (441, 439, 787, 1110, 601, 22, 186)?
or for forwarding the host's SSH agent / git identity into a CLI-managed
container?
forwardPorts-honoring flag onup, or a flag to auto-mount thehost's SSH agent socket and git identity, be a welcome contribution?
Co-Authored-By: Claude Code