Skip to main content

Investigate: Cross-Platform Docker Socket Access from Devcontainer

IMPLEMENTATION RULES: Before implementing this plan, read and follow:

Status: ✅ Completed (solution applied 2026-04-05)

Goal: Find a way to reliably mount the Docker socket into the devcontainer across all platforms (Mac, Windows, Linux) and Docker runtimes (Docker Desktop, Rancher Desktop, Colima, Podman), without breaking the "one standard devcontainer.json" principle.

Priority: High — blocks services (otel, nginx), Docker CLI, uis-bridge, Phase B

Last Updated: 2026-04-04


Problem

DCT's philosophy is one standard devcontainer-user-template.json for all users. No per-project customization. Everything installed via scripts.

But Docker CLI and services inside the devcontainer need the Docker socket to communicate with the host's Docker daemon. The socket path varies by platform and Docker runtime:

PlatformRuntimeSocket path
LinuxDocker Engine/var/run/docker.sock
macOSDocker Desktop/var/run/docker.sock
macOSRancher Desktop~/.rd/docker.sock
macOSColima~/.colima/default/docker.sock
macOSOrbStack/var/run/docker.sock
WindowsDocker Desktop (WSL2)/var/run/docker.sock (inside WSL)
WindowsRancher DesktopVaries by WSL distro

The DCT development devcontainer.json hardcodes /var/run/docker.sock:

"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
],
"remoteEnv": {
"DOCKER_HOST": "unix:///var/run/docker.sock"
}

This works on Docker Desktop but breaks on Rancher Desktop (confirmed on the maintainer's Mac where socket is at ~/.rd/docker.sock).

The user template (devcontainer-user-template.json) has no Docker socket mount at all — Docker access simply doesn't work.


What's Blocked

FeatureWhy it needs Docker socket
service-otel-monitoring.shWas working on Mac (Docker Desktop), broke on Windows
service-nginx.shSame issue
install-tool-docker-cli.shCLI installed but can't reach daemon without socket
uis-bridge.shdocker exec needs socket
dev-template configureCalls UIS via docker exec
dev-env.shShows Docker stats, container info

How VS Code Finds Docker

VS Code Dev Containers extension already solves this problem — it starts the container, so it knows where Docker is.

VS Code uses Docker contexts:

$ docker context ls
NAME DOCKER ENDPOINT
default unix:///var/run/docker.sock
rancher-desktop * unix:///Users/terje.christensen/.rd/docker.sock

The active context (*) tells VS Code the socket path. But this information is NOT automatically passed into the devcontainer.


Current State on Maintainer's Machine

Platform: macOS (Darwin 25.3.0)
Runtime: Rancher Desktop
Socket: /Users/terje.christensen/.rd/docker.sock
DOCKER_HOST env: not set (Docker uses contexts instead)
Docker context: rancher-desktop → unix:///Users/terje.christensen/.rd/docker.sock

/var/run/docker.sock does NOT exist on this machine. Any mount pointing to it fails silently (the mount succeeds but the file doesn't exist inside the container).


Options

Option A: Mount multiple socket paths

Mount all common socket locations, then detect which one exists inside the container:

"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind",
"source=${localEnv:HOME}/.rd/docker.sock,target=/var/run/docker-rd.sock,type=bind",
"source=${localEnv:HOME}/.colima/default/docker.sock,target=/var/run/docker-colima.sock,type=bind"
]

Entrypoint detects which one exists and sets DOCKER_HOST.

Pros: No user configuration needed Cons: Mounts may fail on platforms where the path doesn't exist. Need to test behavior on Win/Mac/Linux when source path is missing. Brittle — new runtimes need new mount entries.

Option B: Use ${localEnv:DOCKER_HOST} in devcontainer.json

"mounts": [
"source=${localEnv:DOCKER_HOST:-/var/run/docker.sock},target=/var/run/docker.sock,type=bind"
]

Problem: DOCKER_HOST is usually not set — Docker uses contexts instead. On the maintainer's machine, DOCKER_HOST is empty.

Pros: Standard devcontainer approach Cons: Doesn't work when DOCKER_HOST isn't set (most Rancher Desktop setups)

Option C: install.sh detects socket and writes devcontainer.json

The install.sh script (runs on the host before the devcontainer starts) could detect the Docker socket path and write it into devcontainer.json:

# In install.sh
DOCKER_SOCK=$(docker context inspect --format '{{.Endpoints.docker.Host}}' 2>/dev/null | sed 's|unix://||')
DOCKER_SOCK="${DOCKER_SOCK:-/var/run/docker.sock}"
# Write into devcontainer.json mounts

Pros: Detects the actual socket at install time, works with any runtime Cons: devcontainer.json is no longer identical across all installs (but the difference is minimal — just the socket path)

Option D: Entrypoint discovers socket from host

The entrypoint script could run a check at container startup to find which socket path was mounted:

for sock in /var/run/docker.sock /var/run/docker-rd.sock /var/run/docker-colima.sock; do
if [ -S "$sock" ]; then
export DOCKER_HOST="unix://$sock"
break
fi
done

Requires Option A's multiple mounts to work first.

Pros: Automatic detection, no user action Cons: Depends on mounts being present, which is the original problem

Option E: dev-update handles socket configuration

dev-update already runs on the host to update DCT. It could also detect the socket path and update devcontainer.json:

# In dev-update
DOCKER_SOCK=$(docker context inspect --format '{{.Endpoints.docker.Host}}' 2>/dev/null | sed 's|unix://||')
# Update .devcontainer/devcontainer.json with the correct mount

Pros: Uses existing update mechanism, runs on host where detection works Cons: Requires running dev-update before Docker works, not automatic on first install

Option F: VS Code Dev Containers feature

"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
}

This handles everything automatically — socket detection, CLI install, permissions.

Pros: Solves all problems, maintained by VS Code team, works everywhere Cons: Adds a "feature" to devcontainer.json that DCT doesn't control via scripts


Real-World Evidence: noveiry/devcontainer-showcasing

A colleague (noveiry) used DCT to build a .NET microservices project with 4 services running via docker compose inside the devcontainer. Repo: https://github.com/noveiry/devcontainer-showcasing

What he changed from the standard DCT template

1. Added docker-outside-of-docker feature:

"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
}

This was the only change that made Docker work. It automatically detected the host's Docker socket (on Windows with Docker Desktop or Rancher Desktop), mounted it, installed Docker CLI, and set up permissions. Without it, docker compose up wouldn't work.

2. Added Windows-specific kubeconfig mount:

"mounts": [
"source=${localEnv:USERPROFILE}\\.kube\\config,target=/root/.kube/config,type=bind"
]

This mounts the Windows kubeconfig. Uses ${localEnv:USERPROFILE} which is Windows-only — would fail on Mac/Linux. Shows the cross-platform mount problem in practice.

What his project does

  • 4 ASP.NET Core microservices (Order, Product, Inventory, Payment)
  • docker-compose.yml runs all 4 services with networking
  • K8s deployment manifests in k8s/ for cluster deployment
  • C# tools installed via DCT (enabled-tools.conf has dev-csharp)
  • Running on Windows

Key insight

The docker-outside-of-docker feature was the only way he could get Docker working inside the devcontainer on Windows. No mount configuration, no socket path detection — the feature handled everything.

His devcontainer.json is now different from the standard DCT template in two ways:

  1. The feature (Docker access)
  2. The Windows mount (kubeconfig)

Both break the "identical devcontainer.json" principle, but both were necessary for his use case.

Implications for DCT

This real-world case suggests that Option F may be the pragmatic choice:

  • It works on all platforms without user configuration
  • It's maintained by the VS Code team (not our code to maintain)
  • The alternative is building our own cross-platform socket detection (Options A-E), which is complex and brittle
  • The "one devcontainer.json" principle is already challenged by platform differences (Windows kubeconfig path vs Mac/Linux)
  • Adding the feature to the standard template means ALL users get Docker access — which is increasingly needed (services, uis-bridge, docker compose)

The trade-off: we add one line to devcontainer.json that we don't control via DCT scripts. But it solves a real problem that our script-based approach can't easily solve.


Investigation: Option F Deeper Analysis (2026-04-04)

This section documents what was discovered from reading the feature source, related issues, and DCT codebase to understand if Option F is truly viable for DCT.

How docker-outside-of-docker actually works

The feature has two phases:

Phase 1 — Build time (install.sh, runs as root inside container layer):

  • Installs Docker CLI (or Moby CLI)
  • Optionally installs Docker Compose and Buildx
  • Installs docker-init.sh as the container entrypoint at /usr/local/share/docker-init.sh
  • Creates the docker group and adds the container user to it
  • Installs socat for socket proxying (needed for non-root users)

Phase 2 — Runtime (docker-init.sh, runs on every container start as PID 1):

  • Reads from /var/run/docker-host.sock (where the feature mounts the host socket)
  • For root users: creates a symlink docker.sock → docker-host.sock
  • For non-root users: uses socat to proxy from docker-host.sock to docker.sock with correct group permissions
  • Then execs the original CMD (e.g., sleep infinity)

The feature's default mount (in devcontainer-feature.json):

"mounts": [
{
"source": "/var/run/docker.sock",
"target": "/var/run/docker-host.sock",
"type": "bind"
}
]

Key: the feature mounts host socket → /var/run/docker-host.sock, not /var/run/docker.sock.
The docker-init.sh entrypoint then bridges docker-host.sockdocker.sock at runtime.

Critical conflict: DCT's custom ENTRYPOINT

DCT uses a custom ENTRYPOINT at /opt/devcontainer-toolbox/entrypoint.sh and relies on overrideCommand: false to ensure it runs. This is essential — the entrypoint handles supervisord services, git config, tool installation, and more.

The problem: docker-outside-of-docker installs its own entrypoint (docker-init.sh) into the container during the feature build phase. When both exist, there is a conflict. The feature's entrypoint must run first to set up the socket bridge, but DCT's entrypoint also needs to run.

Evidence of this conflict: There is an open GitHub issue (devcontainers/features#954) documenting that the feature incorrectly updates the container entrypoint when used with custom compose/entrypoint setups.

The feature is designed for images that don't have a custom ENTRYPOINT. When an image already has one, the feature's docker-init.sh may replace it or chain incorrectly.

The feature assumes /var/run/docker.sock on the host

The feature's built-in mount hardcodes source=/var/run/docker.sock. This is the same problem DCT already has — it works for Docker Desktop, but Rancher Desktop on macOS puts the socket at ~/.rd/docker.sock.

For non-standard socket paths, the feature documentation says you must override the mount manually:

{
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
},
"mounts": [
{
"source": "/run/user/1000/docker.sock",
"target": "/var/run/docker-host.sock",
"type": "bind"
}
]
}

This means Option F does NOT fully solve the Rancher Desktop problem on macOS without user-specific configuration — exactly what DCT is trying to avoid.

What noveiry's success tells us (revisited)

noveiry ran on Windows with Docker Desktop or Rancher Desktop (WSL2). On Windows, both runtimes expose the socket at /var/run/docker.sock inside WSL2 — which is where the devcontainer runs. So the feature's default mount worked for him.

The maintainer (Terje) runs on macOS with Rancher Desktop where the socket is at ~/.rd/docker.sock on the macOS host (not in WSL). The feature's default mount would fail here, just like the current hardcoded mount fails.

Option G: DCT's own socket detection entrypoint script

Rather than using the external feature, DCT could implement the same pattern natively in its own entrypoint. The entrypoint already runs on every start — it can detect and bridge the socket itself:

# In entrypoint.sh — Docker socket detection and setup
_setup_docker_socket() {
local source_sock=""

# Check common socket paths in priority order
for candidate in \
/var/run/docker-host.sock \
/var/run/docker.sock \
/var/run/docker-rd.sock \
/var/run/docker-colima.sock; do
if [ -S "$candidate" ]; then
source_sock="$candidate"
break
fi
done

if [ -z "$source_sock" ]; then
echo "⚠️ No Docker socket found — Docker CLI won't work"
return 0
fi

local target_sock="/var/run/docker.sock"

if [ "$source_sock" != "$target_sock" ]; then
# Non-root: use socat proxy (like docker-init.sh does)
if command -v socat >/dev/null 2>&1; then
sudo socat UNIX-LISTEN:$target_sock,fork,mode=660,user=vscode \
UNIX-CONNECT:$source_sock &
else
sudo ln -sf "$source_sock" "$target_sock" 2>/dev/null || true
fi
fi

export DOCKER_HOST="unix://$target_sock"
echo "✅ Docker socket: $source_sock$target_sock"
}

This still requires the socket to be mounted — which is the core problem. But it decouples socket bridging from the mount configuration.

Revised approach: Option A + Option G combined

The most DCT-native solution is:

  1. devcontainer-user-template.json: Mount multiple candidate socket paths (Option A), tolerating missing sources
  2. entrypoint.sh: Detect which socket is actually present and set it up (Option G)

The unresolved question is: what happens when a mounts source path doesn't exist?

Behavior depends on Docker runtime:

  • Docker Desktop (Mac/Win): Missing bind-mount source → container start fails with an error
  • Rancher Desktop (macOS): Same — fails if source doesn't exist
  • Linux Docker Engine: Same behavior

This means Option A (multiple mounts) is not viable as written — mounting /var/run/docker.sock on a Rancher Desktop mac where it doesn't exist will break container startup.

However, there is a workaround: use optional mounts or pre-create the socket path as a placeholder. Some runtimes allow missing bind sources if the path is pre-created as an empty file or directory. This needs testing.

Option H: initializeCommand socket detection (most promising)

initializeCommand runs on the host before the container starts — this is the right place to detect the socket. Unlike install.sh which runs once at install time, initializeCommand runs on every container rebuild.

"initializeCommand": "bash .devcontainer/manage/detect-docker-socket.sh"

The script runs on the host, detects the socket path, and writes a .devcontainer.secrets/env-vars/docker-socket file:

#!/bin/bash
# detect-docker-socket.sh — runs on host before container starts
SOCKET=$(docker context inspect --format '{{.Endpoints.docker.Host}}' 2>/dev/null \
| sed 's|unix://||' \
| tr -d '\n')
SOCKET="${SOCKET:-/var/run/docker.sock}"
mkdir -p .devcontainer.secrets/env-vars
echo "$SOCKET" > .devcontainer.secrets/env-vars/docker-socket

Then devcontainer.json uses ${localEnv:...} or the entrypoint reads the file and sets up the mount/symlink at runtime.

Problem: You can't dynamically change mounts from initializeCommand output — mounts is evaluated before initializeCommand runs. So this can't inject a new mount. It can only pass information that the entrypoint reads.

But: if the socket file is volume-mounted (not bind-mounted from the host socket path), the entrypoint can use the stored path to create a symlink inside the container.

Revised Option H: Two-socket approach

Mount a fixed well-known path for the socket (not the actual host socket), plus store the real path from initializeCommand:

Actually this doesn't work either — you still need to bind-mount the actual socket file from the host into the container. You cannot symlink to a path that doesn't exist inside the container.

The actual root solution: initializeCommand + shell wrapper

VS Code evaluates ${localEnv:...} at container start time. If DOCKER_HOST is set in the environment before VS Code launches (or before the devcontainer extension runs), it will be picked up.

A better approach: use initializeCommand to write a small shell file that sets DOCKER_HOST, and source it in the host shell profile. But this is too invasive for a general-purpose tool.

Conclusion: The cleanest available options

After deep analysis, three approaches remain viable:

Approach 1: Rancher Desktop compatibility mode (user action, one-time)

Rancher Desktop has a setting: Preferences → Application → General → "Enable Docker compatibility mode" (or similar — exact name varies by version). When enabled, it creates /var/run/docker.sock as a symlink to the Rancher socket. This makes the existing DCT hardcoded mount work without any changes to DCT.

Verification needed: Does current Rancher Desktop (2.x) still offer this? Does it create /var/run/docker.sock?

This is the lowest-effort fix for Rancher Desktop users — one checkbox, documented in DCT's setup guide.

Approach 2: Option F (feature) with ENTRYPOINT chain fix

Add docker-outside-of-docker to the user template, but fix the ENTRYPOINT conflict by having DCT's entrypoint call docker-init.sh first:

# At the top of DCT's entrypoint.sh
if [ -f /usr/local/share/docker-init.sh ]; then
source /usr/local/share/docker-init.sh # or call it differently
fi

This requires understanding exactly how docker-init.sh hands off to its CMD and whether DCT can wrap it safely. The feature installs docker-init.sh expecting to be PID 1 — DCT's entrypoint is already PID 1.

The feature is designed for images without a custom ENTRYPOINT. Using it with DCT requires custom integration work, and still doesn't solve the Rancher Desktop macOS socket path (the feature's default mount also hardcodes /var/run/docker.sock).

Approach 3: Socket path from Docker context, injected via initializeCommand + workspace volume

initializeCommand writes the detected socket path into the workspace (already mounted). The entrypoint reads this file and uses socat to forward it.

But this still doesn't mount the actual socket file. You cannot use socat to forward to a path that doesn't exist inside the container.

The fundamental constraint: There is no way to dynamically change what gets bind-mounted into a running container. The socket path must be known at container creation time.


Questions to Answer (Updated)

  1. Rancher Desktop compatibility mode: Does enabling it on current Rancher Desktop (2.x) create /var/run/docker.sock on macOS? → Test on maintainer's machine

  2. Missing mount source behavior: What exactly happens on macOS/Windows/Linux when a bind-mount source path doesn't exist?

    • Does it error at container start?
    • Does it silently skip?
    • Is there a way to mark a mount as optional? → Test by adding a non-existent path to mounts in DCT dev container
  3. ${localEnv:} with docker context: Can devcontainer.json use:

    "source=${localEnv:DOCKER_CONTEXT_SOCKET:-/var/run/docker.sock}"

    if we pre-set DOCKER_CONTEXT_SOCKET in the host shell profile via initializeCommand? → VS Code evaluates ${localEnv:...} from the process environment. initializeCommand runs too late to affect this.

  4. Feature ENTRYPOINT conflict: Does adding docker-outside-of-docker to a DCT image (with its own ENTRYPOINT) break DCT's startup? → Test by adding feature to DCT dev devcontainer.json and rebuilding

  5. Rancher Desktop Windows behavior: On Windows, does Rancher Desktop expose the socket at /var/run/docker.sock inside WSL2? → noveiry's success suggests yes — confirm


Recommendation (Updated)

The investigation has revealed that no single option cleanly solves all platforms without user action. The revised recommendation is a two-tier strategy:

Tier 1: Document the easy fix (Rancher Desktop users)

For macOS users on Rancher Desktop: Enable Docker compatibility mode in Rancher Desktop settings. This creates /var/run/docker.sock as a symlink, making DCT's existing mount work immediately. No code changes required.

This covers the maintainer's own machine and most Rancher Desktop users.

Tier 2: Implement Option C for install.sh and install.ps1

Detect the active Docker context socket at install time and write it into .devcontainer/devcontainer.json:

install.sh (macOS/Linux):

# Detect Docker socket from active context
DOCKER_SOCK=$(docker context inspect \
$(docker context ls --format '{{if .Current}}{{.Name}}{{end}}') \
--format '{{.Endpoints.docker.Host}}' 2>/dev/null \
| sed 's|unix://||' \
| tr -d '\n')
DOCKER_SOCK="${DOCKER_SOCK:-/var/run/docker.sock}"

# Inject into devcontainer.json mounts
# (use python3/jq to edit JSON, or sed if neither available)

install.ps1 (Windows):

$dockerSock = (docker context inspect `
(docker context ls --format '{{if .Current}}{{.Name}}{{end}}') `
--format '{{.Endpoints.docker.Host}}') -replace 'unix://', ''
if (-not $dockerSock) { $dockerSock = '/var/run/docker.sock' }
# Write into devcontainer.json

This means installed devcontainer.json files have a platform-specific socket path — a minor deviation from "identical" template, but the only reliable cross-platform approach that doesn't add runtime complexity.

Defer Option F

Option F (docker-outside-of-docker feature) has a fundamental conflict with DCT's custom ENTRYPOINT and still doesn't solve Rancher Desktop on macOS. Defer until the ENTRYPOINT interaction is fully tested and understood.


Community Research: What Do Others Do? (2026-04-04)

Extensive research across GitHub issues, official docs, and community forums reveals a clear, consistent pattern. DCT is not alone — this is an extremely common problem. The community has converged on well-known solutions.

Finding 1: Rancher Desktop Admin Access is the Official Fix

The official Rancher Desktop documentation explicitly states:

"Enabling Administrative Access allows Rancher Desktop to create the Docker socket at the default location: /var/run/docker.sock. Without this access, the socket is instead created at ~/.rd/docker.sock and is accessible via the rancher-desktop Docker context."Rancher Desktop Docs: Application > General

Key details:

  • Setting: Preferences → Application → General → Administrative Access (enable the checkbox)
  • Click Apply, then manually restart Rancher Desktop
  • On first enable, and again after each system reboot, Rancher Desktop prompts for admin password
  • /var/run/docker.sock is deleted on every macOS boot (it lives in a tmpfs mount)
  • Rancher Desktop uses a privileged helper to recreate it on each startup when admin access is enabled

This is exactly what testcontainers docs, LocalStack docs, and countless community posts recommend. It is the canonical solution for Rancher Desktop on macOS.

Implication for DCT: The Tier 1 fix (document admin access) is confirmed as the correct approach. DCT should document this in the setup guide, since it affects all tools that bind-mount /var/run/docker.sock — not just DCT.

Finding 2: Every Runtime Uses a Different Socket Path Without Admin

When running without admin/elevated privileges, socket paths across runtimes:

RuntimemacOS socket (no admin)macOS socket (admin enabled)
Docker Desktop~/.docker/run/docker.sock/var/run/docker.sock (via launchd symlink)
Rancher Desktop~/.rd/docker.sock/var/run/docker.sock (via privileged helper)
OrbStack~/.orbstack/run/docker.sock/var/run/docker.sock (if admin granted)
Colima~/.colima/default/docker.sockno admin mechanism — stays at colima path
Linux Docker Engine/var/run/docker.sock/var/run/docker.sock
Windows WSL2 (Docker Desktop / Rancher)/var/run/docker.sock inside WSL/var/run/docker.sock

Critical insight: Docker Desktop and OrbStack also have their own custom socket paths (~/.docker/run/docker.sock, ~/.orbstack/run/docker.sock). They create the /var/run/docker.sock symlink via system startup tasks — so most users never notice. The socket is only reliably at /var/run/docker.sock across all runtimes when admin/elevated privileges are granted.

Rancher Desktop is the most painful because:

  1. Admin mode is opt-in (not default)
  2. The symlink is deleted on every reboot and must be recreated
  3. Many users install Rancher Desktop without enabling admin mode

Finding 3: Tools That Need /var/run/docker.sock All Document the Same Fix

Every major tool that needs the Docker socket inside a container documents the admin access approach for Rancher Desktop:

The pattern is universal: tools that need /var/run/docker.sock tell their users to enable admin/privileged mode, or set DOCKER_HOST in their shell.

Finding 4: The DOCKER_HOST Env Var Approach Is for Legacy Tools

Modern Docker tooling uses Docker contexts — no DOCKER_HOST env var needed. Contexts are per-user and Docker CLI reads the active context to find the socket. The Rancher Desktop Slack community explicitly notes:

"This is only needed for old-style tools that don't know about Docker contexts."

Setting DOCKER_HOST manually (e.g. export DOCKER_HOST=unix://$HOME/.rd/docker.sock) is a workaround for tools that don't use Docker contexts. It breaks context switching.

DCT's devcontainer.json currently sets DOCKER_HOST inside the container to hardcoded /var/run/docker.sock. This is fine — it's inside the container, not on the host. But the socket must be mounted from the host first.

Finding 5: Missing Bind-Mount Sources Cause Hard Container Failures

From VS Code issue #8306, when a bind-mount source path doesn't exist:

docker: Error response from daemon: invalid mount config for type "bind":
bind source path does not exist: /path/that/doesnt/exist

This is a hard failure — the container does not start. Option A (multiple mounts) would break container startup for users whose runtime doesn't have one of the listed socket paths.

Confirmed: Option A is NOT viable as a cross-platform default. Every mount in devcontainer.json must either exist on every host or be guarded at a layer before the mount happens.

Finding 6: OrbStack is the Cleanest Alternative to Docker Desktop

OrbStack (docs) explicitly creates /var/run/docker.sock when admin access is granted:

"If you have admin access, OrbStack will also create a symlink at /var/run/docker.sock to improve compatibility with some third-party tools."

OrbStack is positioned as a drop-in Docker Desktop replacement that works with all tools including Dev Containers. It's the fastest alternative on Apple Silicon. For Testcontainers, DevContainers, LocalStack etc., it works identically to Docker Desktop.

Implication for DCT: DCT should recommend OrbStack as the preferred runtime for macOS users who want zero configuration (or point to it as a Rancher Desktop alternative if admin access is inconvenient).

Finding 7: Colima Has No Standard Socket Fix — Users Must Set DOCKER_HOST

Colima places its socket at ~/.colima/default/docker.sock (or ~/.colima/<profile>/docker.sock). There is no admin mode, no automatic symlink. Colima users who need /var/run/docker.sock must either:

  1. Manually symlink: sudo ln -sf ~/.colima/default/docker.sock /var/run/docker.sock
  2. Set DOCKER_HOST=unix://$HOME/.colima/default/docker.sock in their shell
  3. Use the colima Docker context (which VS Code Dev Containers honors for starting containers)

Note that VS Code uses the active Docker context to start the devcontainer, so Colima users can open devcontainers via VS Code. But inside the container, the socket must be mounted from the host. Since Colima's socket path is in ~, it can be mounted via ${localEnv:HOME} — this is more tractable than other alternatives.

Finding 8: The Community Pattern for Tools That Need Cross-Platform Sockets

Looking at what mature tools (DDEV, Testcontainers, LocalStack) do:

  1. Document the admin access setting per runtime (the user-action fix)
  2. Support DOCKER_HOST env var override for non-standard setups
  3. Never try to auto-detect inside devcontainer.json — it's always done at the application layer or documented as user action
  4. Recommend specific runtimes for best compatibility (OrbStack, Docker Desktop)

None of these tools try to solve it via devcontainer.json magic. They all rely on the socket being at /var/run/docker.sock and tell users to make it so.

Finding 9: Rancher Desktop Admin Access is NOT a Persistent Solution

Critical correction from the previous section: the Rancher Desktop admin access approach requires re-entering the admin password on every system reboot. This is because /var/run is a tmpfs mount on macOS — its entire contents are deleted at boot. Rancher Desktop's privileged helper recreates the socket symlink on startup, but prompts for sudo credentials each time.

From the Rancher Desktop community (2022):

"/var/run will be deleted on each boot, so you will be prompted for the admin password again every time you reboot the machine"

From a Rancher Desktop developer:

"One day we'll do this via a privileged helper process, but we don't have anybody who has time to work on that in the near future"

This makes admin access unacceptable as a solution for a developer tool. Requiring users to enter their password on every reboot to use their dev environment is friction that no one will tolerate. The previous recommendation to document admin access as the "Tier 1 fix" is withdrawn.

Finding 10: The ${localEnv:HOME} Mount Pattern IS Valid in devcontainer.json

The official VS Code documentation (Add local file mount) explicitly shows mounting from ~ using ${localEnv:HOME}:

"mounts": [
"source=${localEnv:HOME}${localEnv:USERPROFILE},target=/host-home-folder,type=bind,consistency=cached"
]

${localEnv:HOME} expands to the host's home directory before the container starts. This is evaluated by VS Code, not the container. This means DCT can legally mount ${localEnv:HOME}/.rd/docker.sock as a source. The question is only whether the path exists at container start time.

Finding 11: The Real Solution — Mount from $HOME Where Sockets Live

All non-standard sockets live inside the user's home directory, which VS Code already mounts and which ${localEnv:HOME} resolves to. This enables a new mount strategy:

"mounts": [
"source=${localEnv:HOME}/.rd/docker.sock,target=/var/run/docker-rd.sock,type=bind,consistency=cached"
]

But we still hit the hard-failure problem: if ~/.rd/docker.sock doesn't exist (e.g. on Docker Desktop, where the socket is elsewhere), the container fails to start.

The only working approach with static mounts in devcontainer.json: The socket path must be known at container creation time AND the source must exist. Static multi-socket mounts cannot be made optional in the devcontainer spec.

Finding 12: The Real-World Practical Answer — OrbStack

This research reveals that the real community solution for macOS developers who want zero-friction Docker socket access in devcontainers is OrbStack:

  • OrbStack creates /var/run/docker.sock permanently via launchd (not just at first startup)
  • It does not require re-entering admin credentials on reboot — it installs a persistent system daemon
  • It is drop-in compatible with Docker Desktop and Dev Containers
  • It is free for individual/personal use; paid for professional use
  • It is the most recommended Docker Desktop alternative across the macOS dev community in 2024-2025

For open-source first / sovereignty-aligned projects that cannot recommend OrbStack (proprietary), the next best options are:

  • Colima with DOCKER_HOST set in shell profile (free, open source) — but then install.sh/install.ps1 must detect and inject the socket path
  • Rancher Desktop with admin access (acceptable if users understand the password prompt on first start after reboot)

Updated Recommendation (Final)

The admin-access-on-every-reboot problem means there is no perfect zero-user-action solution for all runtimes. The strategy must be:

Tier 1 — Runtime recommendation (documentation only): DCT's setup guide should explicitly state which runtimes work well with DCT's Docker socket feature:

  • Docker Desktop — works out of the box (persistent /var/run/docker.sock via launchd)
  • OrbStack — works out of the box (persistent /var/run/docker.sock via launchd)
  • ⚠️ Rancher Desktop — requires enabling Administrative Access (password prompt on first start after each reboot)
  • ⚠️ Colima — requires DOCKER_HOST export in shell profile, then install.sh socket detection
  • Linux Docker Engine — works out of the box
  • Windows WSL2 (any runtime) — works out of the box

Tier 2 — Option C: install.sh / install.ps1 socket detection (code change, medium priority): Detect active Docker context socket at install time, inject the real path into the generated devcontainer.json:

# In install.sh — detect socket from active docker context
DOCKER_SOCK=$(docker context inspect \
"$(docker context ls --format '{{if .Current}}{{.Name}}{{end}}')" \
--format '{{.Endpoints.docker.Host}}' 2>/dev/null \
| sed 's|unix://||' | tr -d '\n')
DOCKER_SOCK="${DOCKER_SOCK:-/var/run/docker.sock}"
# Inject DOCKER_SOCK into .devcontainer/devcontainer.json mounts section

This is the most reliable cross-platform approach for first install. It correctly handles Rancher Desktop, Colima, OrbStack, and Docker Desktop by reading the active Docker context. It only runs at install time — after reboots where Rancher's socket path changes, users need to re-run install (or dev-update).

Tier 3 — Option G: entrypoint socket detection (code change, lower priority): Add to entrypoint.sh: check all known socket paths, log which one was found (or warn clearly if none). Helps users diagnose the problem without silent failures.


Finding 13: The Existing Solution — dev.containers.dockerSocketPath (2026-04-04)

This VS Code setting is the community-standard answer to this problem. It was designed exactly for this.

What it is

dev.containers.dockerSocketPath is a VS Code user setting (not in devcontainer.json) that tells the Dev Containers extension which host socket to mount into every devcontainer. It lives in the user's local VS Code settings file, not in the repo.

From the Bluefin Linux devcontainers guide, for Podman:

// ~/.config/Code/User/settings.json — per-machine, never committed
{
"dev.containers.dockerSocketPath": "/run/user/1000/podman/podman.sock"
}

For DCT users on Rancher Desktop:

{
"dev.containers.dockerSocketPath": "/Users/terje/.rd/docker.sock"
}

Why this solves it

  • Per-machine setting, not per-repo — exactly the right layer. Socket path is a machine concern, not a project concern.
  • Persists across reboots~/.rd/docker.sock always exists while Rancher Desktop is running; no password prompt, no admin needed.
  • Zero changes to devcontainer.json — templates stay identical for all users.
  • Works for every runtime — Rancher, Colima, Podman, OrbStack without admin.

How VS Code uses it

When this setting is set, the Dev Containers extension uses the specified host path as the source when mounting the Docker socket into the container. Inside the container, the socket is available at /var/run/docker.sock (the standard path) regardless of what the host path was.

This means the socket appears at /var/run/docker.sock inside the container automatically — no explicit mounts entry in devcontainer.json needed at all.

The critical interaction with DCT's current mounts entry

Looking at the current DCT development devcontainer.json:

"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind,consistency=cached"
],
"remoteEnv": {
"DOCKER_HOST": "unix:///var/run/docker.sock",
...
}

This explicit mounts entry is the problem. Even with dev.containers.dockerSocketPath set correctly, Docker itself processes the mounts array verbatim — if source=/var/run/docker.sock doesn't exist on the host (Rancher Desktop), the container fails to start with a hard bind-mount error before dev.containers.dockerSocketPath can help.

The fix is to remove the hardcoded mounts entry and remoteEnv from devcontainer.json entirely, and let dev.containers.dockerSocketPath + entrypoint.sh detection handle it.

Note: devcontainer-user-template.json already has no Docker socket mount — it is already correct. Only the DCT development devcontainer.json needs fixing.

Developer journey in practice (after the fix)

Developer A — Docker Desktop or OrbStack (macOS/Windows/Linux):

  1. Installs Docker Desktop or OrbStack
  2. Checks out repo, opens VS Code → "Reopen in Container"
  3. Container starts. /var/run/docker.sock exists via persistent launchd daemon.
  4. Docker CLI works inside container. Zero configuration needed.

Developer B — Rancher Desktop (macOS), first time:

  1. Installs Rancher Desktop, selects dockerd (moby) as container runtime
  2. Opens VS Code settings (Cmd+,), searches "docker socket", sets: dev.containers.dockerSocketPath~/.rd/docker.sock
  3. Checks out repo, opens VS Code → "Reopen in Container"
  4. VS Code mounts ~/.rd/docker.sock/var/run/docker.sock inside container
  5. entrypoint.sh detects /var/run/docker.sock, exports DOCKER_HOST
  6. Docker CLI, otel, nginx, uis-bridge all work. One-time setup only.

Developer B — Rancher Desktop, after reboot:

  1. Rancher Desktop auto-starts (if configured at login), recreates ~/.rd/docker.sock
  2. Opens VS Code → "Reopen in Container"
  3. Setting still points to ~/.rd/docker.sock, which now exists again
  4. Everything works. No password prompt. No manual action.

Developer C — Colima (macOS):

  1. Installs Colima, runs colima start
  2. Sets in VS Code: dev.containers.dockerSocketPath~/.colima/default/docker.sock
  3. Checks out repo, opens VS Code → "Reopen in Container"
  4. Works. One-time setup only.

Developer D — Linux Docker Engine:

  1. /var/run/docker.sock exists natively.
  2. Checks out repo, opens VS Code → "Reopen in Container"
  3. Zero configuration needed.

Developer E — Windows WSL2 (any runtime):

  1. /var/run/docker.sock exists inside WSL2 for all runtimes.
  2. Checks out repo, opens VS Code → "Reopen in Container"
  3. Zero configuration needed.

What needs to change in DCT

Change 1: DCT development devcontainer.json — remove the hardcoded socket mount and remoteEnv:

// REMOVE these lines:
"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind,consistency=cached"
],
"remoteEnv": {
"DOCKER_HOST": "unix:///var/run/docker.sock",
"DOCKER_HOST_URI": "/var/run/docker.sock",
"PODMAN_SOCKET": "/var/run/docker.sock"
},

Replace with nothing — dev.containers.dockerSocketPath on the maintainer's machine handles it.

Change 2: entrypoint.sh — detect the socket dynamically instead of assuming a path:

# Replace any hardcoded DOCKER_HOST assumptions with:
_setup_docker_socket() {
for sock in /var/run/docker.sock /var/run/docker-host.sock; do
if [ -S "$sock" ]; then
export DOCKER_HOST="unix://$sock"
export DOCKER_HOST_URI="$sock"
export PODMAN_SOCKET="$sock"
echo "✅ Docker socket: $sock"
return 0
fi
done
echo "⚠️ No Docker socket found — Docker CLI won't work inside the container."
echo " Set 'dev.containers.dockerSocketPath' in VS Code user settings for your runtime:"
echo " Rancher Desktop: ~/.rd/docker.sock"
echo " Colima: ~/.colima/default/docker.sock"
echo " OrbStack: ~/.orbstack/run/docker.sock"
return 1
}
_setup_docker_socket

Change 3: DCT setup documentation — add a "Docker socket setup" section:

RuntimeVS Code setting dev.containers.dockerSocketPathNotes
Docker Desktop(not needed)Works out of the box
OrbStack(not needed)Works out of the box
Rancher Desktop~/.rd/docker.sockSet once; survives reboots
Colima~/.colima/default/docker.sockSet once; run colima start before opening VS Code
Linux Docker Engine(not needed)Works out of the box
Windows WSL2(not needed)Works out of the box

No changes needed to devcontainer-user-template.json — it already has no socket mount, which is correct.

Open question before implementing

One thing to verify with a quick test: does dev.containers.dockerSocketPath mount the socket at /var/run/docker.sock inside the container, or at the same path as on the host? All evidence points to /var/run/docker.sock (that's the normalization the setting provides), but this should be confirmed before entrypoint.sh is written.


✅ TESTING COMPLETE (2026-04-04)

All critical tests performed on maintainer's Mac (Rancher Desktop, macOS, arm64). Results:

Test 1: No mount, no setting, no feature

Setup: Clean devcontainer.json — no mounts, no remoteEnv for Docker, no features. Docker CLI installed via install-tool-docker-cli.sh.

Result:/var/run/docker.sock does not exist inside container. docker ps fails.

Conclusion: VS Code does NOT auto-mount the Docker socket. The active Docker context is only used by VS Code to start the container, not to provide Docker inside it.

Test 2: dev.containers.dockerSocketPath VS Code setting

Setup: Set dev.containers.dockerSocketPath: ~/.rd/docker.sock in VS Code user settings. No mounts in devcontainer.json. Full VS Code restart + container rebuild.

Result:/var/run/docker.sock still does not exist inside container. docker ps still fails.

Conclusion: dev.containers.dockerSocketPath does NOT mount the socket into the container. Finding 13 was wrong — this setting appears to only affect volume-based dev containers, not general socket injection.

Test 3: Explicit bind mount from detected host path

Setup: Added "source=/Users/terje.christensen/.rd/docker.sock,target=/var/run/docker.sock,type=bind" to mounts in devcontainer.json.

Result: ❌ Container fails to start: "A mount config is invalid. Make sure it has the right format and a source folder that exists on the machine where the Docker daemon is running."

Conclusion: Rancher Desktop's Docker daemon runs inside a Linux VM. Host macOS paths like ~/.rd/docker.sock are not directly accessible as bind mount sources from within the VM. This rules out Options A, C, and any explicit mount approach on Rancher Desktop macOS.

Test 4: docker-outside-of-docker feature (Option F)

Setup: Added "features": {"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}} to devcontainer.json. No explicit mounts.

Result: ✅ Everything works:

  • /var/run/docker.sock exists (symlink → /var/run/docker-host.sock)
  • docker ps shows all host containers including uis-provision-host
  • docker exec uis-provision-host echo "hello from UIS" returns hello from UIS
  • Docker CLI installed by feature at /usr/bin/docker (v29.3.1)
  • DCT's custom ENTRYPOINT still works (startup sequence ran normally)

Conclusion: Option F is the only tested approach that works on Rancher Desktop macOS. The feature handles the VM socket bridging internally — something no explicit mount can do.

Test 5: Feature makes install-tool-docker-cli.sh redundant

Observation: The feature installs Docker CLI at /usr/bin/docker. DCT's install-tool-docker-cli.sh installs at /usr/local/bin/docker. Both are v29.3.1. The feature's install takes precedence on PATH.

Conclusion: install-tool-docker-cli.sh is not needed when the feature is present. It should be removed or made optional (only for users who don't use the feature).

ENTRYPOINT conflict — NOT observed

The investigation predicted a conflict between the feature's docker-init.sh entrypoint and DCT's custom entrypoint. In testing, both ran correctly — DCT's full startup sequence (git config, tool install, services) completed normally, and the Docker socket was available. The feature appears to chain correctly with existing entrypoints when overrideCommand: false is set.


Final Recommendation (Tested and Confirmed)

Add docker-outside-of-docker feature to devcontainer-user-template.json.

This is the only approach that:

  • Works on all platforms (Docker Desktop, Rancher Desktop, OrbStack, Colima, Linux, Windows WSL2)
  • Requires zero user configuration
  • Handles the VM socket bridging that explicit mounts cannot
  • Installs Docker CLI automatically
  • Does not conflict with DCT's custom ENTRYPOINT
  • Is maintained by the VS Code team

Changes needed:

  1. Add feature to devcontainer-user-template.json
  2. Remove install-tool-docker-cli.sh (feature provides Docker CLI)
  3. Remove hardcoded socket mount and DOCKER_HOST from DCT development devcontainer.json
  4. Update documentation

What this means for DCT's philosophy: One line added to devcontainer.json that DCT doesn't control via scripts. This is the pragmatic trade-off — the alternative (custom cross-platform socket bridging) is not viable on Rancher Desktop macOS where the Docker daemon runs in a VM.


What the Developer Actually Does (Plain Language)

This section describes what a real developer has to do — from zero to working Docker inside DCT — for each runtime. No background knowledge assumed.


Docker Desktop (Mac, Windows) — Nothing

  1. Install Docker Desktop. Start it.
  2. Clone the repo. Open VS Code. Click "Reopen in Container".
  3. Done. Docker works inside the container.

User action count: 0


OrbStack (Mac) — Nothing

  1. Install OrbStack. Start it.
  2. Clone the repo. Open VS Code. Click "Reopen in Container".
  3. Done. Docker works inside the container.

User action count: 0


Linux Docker Engine — Nothing

  1. Install Docker Engine (already standard on most Linux machines).
  2. Clone the repo. Open VS Code. Click "Reopen in Container".
  3. Done. Docker works inside the container.

User action count: 0


Windows with Rancher Desktop or Docker Desktop (WSL2) — Nothing

  1. Install Rancher Desktop or Docker Desktop on Windows.
  2. Clone the repo. Open VS Code. Click "Reopen in Container".
  3. Done. Docker works inside the container.

(On Windows, both runtimes expose the socket at /var/run/docker.sock inside WSL2 automatically.)

User action count: 0


Rancher Desktop (Mac) — One setting, set once

First time only:

  1. Install Rancher Desktop. In Preferences → Container Engine, select dockerd (moby). Start it.
  2. In VS Code, press Cmd+, (comma) — this opens Settings. (Not Cmd+Shift+P — that's the Command Palette, which won't work.)
  3. In the Settings search box, type: docker socket
  4. Find the setting Dev > Containers: Docker Socket Path — it's a text input field.
  5. Set it to: ~/.rd/docker.sock
  6. Close settings.

Every time after that (including after reboots):

  1. Rancher Desktop starts automatically at login and creates ~/.rd/docker.sock.
  2. Clone the repo (or open it). Open VS Code. Click "Reopen in Container".
  3. Done. Docker works inside the container.

User action count: 5 clicks/keystrokes, once ever. Zero after that.


Colima (Mac) — One setting + one command

First time only:

  1. Install Colima (brew install colima docker). Run colima start.
  2. In VS Code, press Cmd+, (comma) — this opens Settings.
  3. In the Settings search box, type: docker socket
  4. Find the setting Dev > Containers: Docker Socket Path — it's a text input field.
  5. Set it to: ~/.colima/default/docker.sock
  6. Close settings.

Every time after that:

  1. Run colima start in a terminal (Colima doesn't auto-start; add it to a login script to avoid this).
  2. Open VS Code. Click "Reopen in Container".
  3. Done. Docker works inside the container.

User action count: 5 clicks/keystrokes once, then colima start before each session.


Summary table

RuntimePlatformUser action required
Docker DesktopMac, WindowsNone
OrbStackMacNone
Docker EngineLinuxNone
Rancher DesktopWindows (WSL2)None
Docker DesktopWindows (WSL2)None
Rancher DesktopMacSet VS Code socket path once
ColimaMacSet VS Code socket path once + colima start each session

Where to find the VS Code setting

  1. Press Cmd+, (comma) — this opens Settings
  2. In the search box type: dev containers docker
  3. Find Dev › Containers: Docker Socket Path — currently shows /var/run/docker.sock
  4. Clear it and type the path for your runtime (see table above)

⚠️ Do NOT use Cmd+Shift+P (Command Palette) — that runs commands, not settings.

Or edit ~/.config/Code/User/settings.json directly:

// Rancher Desktop on Mac:
"dev.containers.dockerSocketPath": "~/.rd/docker.sock"

// Colima on Mac:
"dev.containers.dockerSocketPath": "~/.colima/default/docker.sock"

This file is on the developer's local machine, never committed to the repo.


All Tests Completed

  • TEST 1: No mount, no setting → socket NOT available (Possibility B confirmed)
  • TEST 2: dev.containers.dockerSocketPath setting → does NOT mount socket (Finding 13 wrong)
  • TEST 3: Explicit bind mount → fails on Rancher Desktop (VM can't see host paths)
  • TEST 4: docker-outside-of-docker feature → ✅ WORKS on Rancher Desktop macOS
  • TEST 5: ENTRYPOINT conflict → NOT observed, both entrypoints chain correctly
  • TEST 6: Feature installs Docker CLI → install-tool-docker-cli.sh is redundant
  • TEST 7: Without install-tool-docker-cli.sh in enabled-tools.conf, feature's Docker CLI works alone
  • TEST 8: With dev.containers.dockerSocketPath cleared (empty/default), feature still works → the VS Code setting is irrelevant when the feature is used

Conclusion

The docker-outside-of-docker feature is the only viable solution. All other approaches failed on Rancher Desktop macOS:

ApproachResultWhy
No mount, no featureVS Code does NOT auto-mount the socket
dev.containers.dockerSocketPath VS Code settingSetting does not mount socket into container
Explicit bind mount (host path)Rancher Desktop runs Docker in a VM — host macOS paths aren't accessible
docker-outside-of-docker featureFeature bridges socket through the VM layer internally

What the feature does that we can't replicate: The Docker daemon on Rancher Desktop macOS runs inside a Linux VM. The feature uses an internal mechanism (likely QEMU/virtio socket forwarding or shared memory) to bridge the socket from the VM into the container. This cannot be done with a simple bind mount because the host macOS filesystem path (~/.rd/docker.sock) is not visible from inside the VM where Docker creates containers.

The dev.containers.dockerSocketPath VS Code setting is irrelevant. Testing confirmed it does nothing when the feature is present, and does nothing when the feature is absent. It appears to only affect a narrow edge case (Docker volume-based workspaces), not general socket injection.


Image Size Impact

ImageSizeDescription
devcontainer-toolbox:local (base)2.86GBDCT image without the feature
vsc-delete-test-*-features (with feature)3.1GBAfter VS Code applies the feature
Feature cost~240MBDocker CLI + Docker Compose + socat + image layer overhead

For comparison, our deleted install-tool-docker-cli.sh added only 38MB (just the Docker CLI binary). The feature adds 6x more because it includes Docker Compose, socat (for socket proxying to non-root users), and builds an extra container layer.

The 240MB is the cost of cross-platform Docker socket support that actually works. There is no lighter alternative that handles the Rancher Desktop VM layer.

Note: the feature is applied by VS Code at container start time, not baked into the DCT base image. The ghcr.io/helpers-no/devcontainer-toolbox:latest image stays at 2.86GB. The 240MB overhead is added locally when VS Code builds the feature layer.


What Must Change in DCT

1. devcontainer-user-template.json — add the feature

"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
},

This one line gives every DCT user Docker CLI and socket access on all platforms, zero configuration.

2. .devcontainer/devcontainer.json (DCT development) — remove hardcoded Docker config

Remove these lines:

// In remoteEnv:
"DOCKER_HOST": "unix:///var/run/docker.sock",
"DOCKER_HOST_URI": "/var/run/docker.sock",
"PODMAN_SOCKET": "/var/run/docker.sock"

// In mounts:
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind,consistency=cached"

Add the feature instead (same as user template).

The feature creates /var/run/docker.sock as a symlink inside the container — no hardcoded mount or env vars needed.

3. install-tool-docker-cli.sh — deleted

Already deleted. The feature installs Docker CLI at /usr/bin/docker. No DCT install script needed.

4. uis-bridge.sh — no changes needed

The bridge uses docker exec which works because the feature provides both the CLI and the socket. No bridge changes required.

5. Documentation

Add to DCT getting started guide: Docker works automatically for all platforms. No user configuration needed. The docker-outside-of-docker feature handles socket detection and CLI installation.


Impact on DCT Philosophy

One line added to devcontainer.json that DCT doesn't control via scripts.

This is the only deviation from the "everything via scripts" principle. The trade-off:

  • Without the feature: Docker doesn't work on Rancher Desktop macOS (and potentially other VM-based runtimes). Services broken, uis-bridge broken, dev-template configure broken.
  • With the feature: Docker works everywhere, zero configuration, maintained by VS Code team.

The feature is part of the official devcontainer specification (ghcr.io/devcontainers/features/). It's not a third-party plugin. It's the standard way to provide Docker inside devcontainers.


Next Steps

  • IMPLEMENT: Add feature to devcontainer-user-template.json (2026-04-05)
  • IMPLEMENT: Remove hardcoded socket mount and DOCKER_HOST/PODMAN_SOCKET from .devcontainer/devcontainer.json, add feature (2026-04-05)
  • DOC: Update getting started guide (follow-up)
  • install-tool-docker-cli.sh removed — superseded by the feature