Summary
Docker workspace credential sharing currently assumes that the host global Git configuration can be copied into a container unchanged. That assumption fails in two related ways:
DockerRuntime.setupCredentials() copies the host config to <container>:/root/.gitconfig, while Docker workspace images can run as a non-root default user.
- Even when the file is written to the effective container user’s home directory, a host Git config can reference credential helpers, includes, commands, sockets, files, keyrings, or network configuration that are not available inside the container.
The first is a concrete bug. The second is a broader design question about the supported contract for Git and SSH credential forwarding in Docker and Dev Container workspaces.
Current behavior
src/node/runtime/DockerRuntime.ts calls setupCredentials() both after fresh provisioning and when postCreateSetup() reuses an already-valid forked container.
setupCredentials() currently uses docker cp to copy the host config to /root/.gitconfig, then runs gh auth setup-git when GH_TOKEN is available.
- Fresh provisioning detects the container uid/gid/home before credential setup, but the reuse/fork branch invokes credential setup before
containerHome has been cached.
DevcontainerRuntime.setupCredentials() already uses a more user-correct transport: it reads the host config and writes it through cat > "$HOME/.gitconfig" as the configured container user. It also forwards a broader set of Coder-related environment/mount inputs.
Relevant prior work to re-evaluate:
Example: Coder workspace credentials
In some Coder-backed environments, the host Git config contains a credential helper that depends on Coder-specific executables, agent state, environment variables, or mounts. Copying that config into a generic Docker workspace does not make those dependencies available. The helper may fail, block a supported fallback, or point Git at paths that do not exist in the container.
This is not unique to Coder. Similar examples include OS keychain helpers, Git Credential Manager, libsecret, password-manager helpers, enterprise helpers, custom shell helpers, and helpers backed by local files or sockets.
Why copying a global Git config is not a portable credential transport
Potentially non-portable configuration includes:
credential.helper, including multiple helpers and URL-scoped helpers;
- shell or absolute-path helpers;
- helper state files, daemons, sockets, and browser/OAuth flows;
[include] and [includeIf] files, including conditional configuration activated by the container’s Git directory, branch, or remote URL;
core.sshCommand, IdentityFile, ProxyCommand, SSH certificate/known-host paths, and other SSH-wrapper configuration;
- custom CA bundles, proxies, client certificates, VPN/DNS assumptions, or URL rewrites;
- signing configuration (
gpg.program, SSH signing) and Git external tools/filters/hooks that point to host-only executables.
Copying the file can therefore produce a configuration that is syntactically valid but operationally broken. Blind filtering is also non-trivial: Git config supports quoting, repeated values, URL subsections, and includes, and removing all credential.* settings could discard valid provider-specific username or policy configuration.
Lifecycle impact
The issue should consider at least these paths:
| Lifecycle |
Risk |
| Fresh provisioning |
Non-root home/ownership and helper portability |
| Reuse/fork |
Credential setup can run before user-home metadata is cached |
| Runtime recreated for an existing workspace |
In-memory home metadata is absent |
| Restart/upgrade of an image |
Helper binaries, daemons, and sockets may no longer exist |
| Private submodules during provisioning |
.mux/init runs too late to repair credentials needed for bundle sync, checkout, or submodule materialization |
Existing supported mechanisms vs. opaque host helpers
Mux already has mechanisms with an explicit transport contract:
GH_TOKEN followed by gh auth setup-git;
- SSH agent forwarding;
- for Dev Containers, selected
GIT_ASKPASS/CODER_* environment forwarding and an optional /.coder-agent mount.
These should be considered separately from copying arbitrary host configuration. A copied Git config says which helper Git should invoke; it does not provide the helper’s executable, dependencies, session, or secrets.
.mux/init as an escape hatch
A repository can provide an executable .mux/init script. In Docker and Dev Container runtimes it runs in the workspace directory as the effective container user and receives workspace/project secrets via environment variables.
This can help teams with known, container-native setups, for example:
- run
gh auth setup-git against an intentionally supplied GH_TOKEN;
- configure a credential helper that is installed in the image;
- remove or replace a known incompatible helper;
- configure an enterprise provider from project secrets;
- verify or adjust a forwarded SSH agent.
However, it is not a complete solution:
- it is non-interactive (
GIT_TERMINAL_PROMPT=0, no TTY);
- it cannot reconstruct an opaque host helper or host keyring/session;
- it runs after Docker provisioning, checkout, and submodule materialization, so it cannot repair credentials needed before those stages;
- it is repository-controlled and only appropriate for trusted projects.
Questions for design discussion
- What is the supported credential-portability contract for Docker versus Dev Container workspaces?
- Should Mux distinguish between portable Git preferences, explicitly supported credential transports, and host-specific helper configuration that is intentionally not imported?
- Should Docker adopt the DevcontainerRuntime transport pattern (
readHostGitconfig() + this.exec('cat > "$HOME/.gitconfig"')) as the minimal fix for the non-root/reuse bug, while treating helper portability as a separate problem?
- Should the default behavior be full config copy, selective/sanitized copy, only explicitly supported transports, or opt-in integrations for known helpers/providers?
- If
.mux/init is the intended extensibility point, do we need an earlier lifecycle hook for credentials required during clone/submodule setup?
- Would a redacted credential diagnostic be useful (effective user/home, configured helper origins, active includes,
gh/GH_TOKEN/SSH-agent/Coder-agent availability, and lifecycle phase of failure)?
Suggested acceptance criteria for an initial narrow fix
- Docker writes any imported Git config as the actual default container user, using
$HOME, not hardcoded /root.
- The reuse/fork path works when
containerHome has not yet been cached.
- Raw config bytes are streamed via stdin rather than interpolated into a shell command.
- Existing
GH_TOKEN and SSH-agent behavior remains intact.
- Tests cover root and non-root users, fresh provisioning and reused/forked containers, including the uncached-home reuse case.
- The implementation and documentation explicitly state that this alone does not make host-specific credential helpers portable.
Generated with mux • Model: openai:gpt-5.6-terra • Thinking: high • Cost: $0.15
Summary
Docker workspace credential sharing currently assumes that the host global Git configuration can be copied into a container unchanged. That assumption fails in two related ways:
DockerRuntime.setupCredentials()copies the host config to<container>:/root/.gitconfig, while Docker workspace images can run as a non-root default user.The first is a concrete bug. The second is a broader design question about the supported contract for Git and SSH credential forwarding in Docker and Dev Container workspaces.
Current behavior
src/node/runtime/DockerRuntime.tscallssetupCredentials()both after fresh provisioning and whenpostCreateSetup()reuses an already-valid forked container.setupCredentials()currently usesdocker cpto copy the host config to/root/.gitconfig, then runsgh auth setup-gitwhenGH_TOKENis available.containerHomehas been cached.DevcontainerRuntime.setupCredentials()already uses a more user-correct transport: it reads the host config and writes it throughcat > "$HOME/.gitconfig"as the configured container user. It also forwards a broader set of Coder-related environment/mount inputs.Relevant prior work to re-evaluate:
Example: Coder workspace credentials
In some Coder-backed environments, the host Git config contains a credential helper that depends on Coder-specific executables, agent state, environment variables, or mounts. Copying that config into a generic Docker workspace does not make those dependencies available. The helper may fail, block a supported fallback, or point Git at paths that do not exist in the container.
This is not unique to Coder. Similar examples include OS keychain helpers, Git Credential Manager, libsecret, password-manager helpers, enterprise helpers, custom shell helpers, and helpers backed by local files or sockets.
Why copying a global Git config is not a portable credential transport
Potentially non-portable configuration includes:
credential.helper, including multiple helpers and URL-scoped helpers;[include]and[includeIf]files, including conditional configuration activated by the container’s Git directory, branch, or remote URL;core.sshCommand,IdentityFile,ProxyCommand, SSH certificate/known-host paths, and other SSH-wrapper configuration;gpg.program, SSH signing) and Git external tools/filters/hooks that point to host-only executables.Copying the file can therefore produce a configuration that is syntactically valid but operationally broken. Blind filtering is also non-trivial: Git config supports quoting, repeated values, URL subsections, and includes, and removing all
credential.*settings could discard valid provider-specific username or policy configuration.Lifecycle impact
The issue should consider at least these paths:
.mux/initruns too late to repair credentials needed for bundle sync, checkout, or submodule materializationExisting supported mechanisms vs. opaque host helpers
Mux already has mechanisms with an explicit transport contract:
GH_TOKENfollowed bygh auth setup-git;GIT_ASKPASS/CODER_*environment forwarding and an optional/.coder-agentmount.These should be considered separately from copying arbitrary host configuration. A copied Git config says which helper Git should invoke; it does not provide the helper’s executable, dependencies, session, or secrets.
.mux/initas an escape hatchA repository can provide an executable
.mux/initscript. In Docker and Dev Container runtimes it runs in the workspace directory as the effective container user and receives workspace/project secrets via environment variables.This can help teams with known, container-native setups, for example:
gh auth setup-gitagainst an intentionally suppliedGH_TOKEN;However, it is not a complete solution:
GIT_TERMINAL_PROMPT=0, no TTY);Questions for design discussion
readHostGitconfig()+this.exec('cat > "$HOME/.gitconfig"')) as the minimal fix for the non-root/reuse bug, while treating helper portability as a separate problem?.mux/initis the intended extensibility point, do we need an earlier lifecycle hook for credentials required during clone/submodule setup?gh/GH_TOKEN/SSH-agent/Coder-agent availability, and lifecycle phase of failure)?Suggested acceptance criteria for an initial narrow fix
$HOME, not hardcoded/root.containerHomehas not yet been cached.GH_TOKENand SSH-agent behavior remains intact.Generated with
mux• Model:openai:gpt-5.6-terra• Thinking:high• Cost:$0.15