Skip to content

Commit f04fa56

Browse files
Add rootless Docker support and update documentation
1 parent eea561a commit f04fa56

9 files changed

Lines changed: 272 additions & 3 deletions

File tree

src/docker-outside-of-docker/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Re-use the host docker socket, adding the Docker CLI to a container. Feature inv
2323
| dockerDashComposeVersion | Compose version to use for docker-compose (v1 or v2 or none) | string | v2 |
2424
| installDockerBuildx | Install Docker Buildx | boolean | true |
2525
| installDockerComposeSwitch | Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter. | boolean | true |
26+
| socketPath | Path where the Docker socket is mounted inside the container. For rootless Docker, override the mount in devcontainer.json to map your host socket to this path. | string | /var/run/docker-host.sock |
2627

2728
## Customizations
2829

@@ -36,6 +37,30 @@ Re-use the host docker socket, adding the Docker CLI to a container. Feature inv
3637
- The host and the container must be running on the same chip architecture. You will not be able to use it with an emulated x86 image with Docker Desktop on an Apple Silicon Mac, for example.
3738
- This approach does not currently enable bind mounting the workspace folder by default, and cannot support folders outside of the workspace folder. Consider whether the [Docker-in-Docker Feature](../docker-in-docker) would better meet your needs given it does not have this limitation.
3839

40+
## Rootless Docker Support
41+
42+
By default, this feature expects the Docker socket at `/var/run/docker.sock` on the host, which works for standard (root) Docker installations. For **rootless Docker** setups where the socket is located at `/run/user/$UID/docker.sock` or `$XDG_RUNTIME_DIR/docker.sock`, you need to override the mount in your `devcontainer.json`:
43+
44+
```json
45+
{
46+
"features": {
47+
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
48+
},
49+
"mounts": [
50+
{
51+
"source": "/run/user/1000/docker.sock",
52+
"target": "/var/run/docker-host.sock",
53+
"type": "bind"
54+
}
55+
]
56+
}
57+
```
58+
59+
**Notes:**
60+
- Replace `1000` with your actual user ID (run `id -u` to find it)
61+
- The feature will automatically detect the socket at `/var/run/docker-host.sock`
62+
- Your custom mount will override the feature's default mount
63+
3964
## Supporting bind mounts from the workspace folder
4065

4166
A common question that comes up is how you can use `bind` mounts from the Docker CLI from within the a dev container using this Feature (e.g. via `-v`). If you cannot use the [Docker-in-Docker Feature](../docker-in-docker), the only way to work around this is to use the **host**'s folder paths instead of the container's paths. There are 2 ways to do this

src/docker-outside-of-docker/devcontainer-feature.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"id": "docker-outside-of-docker",
3-
"version": "1.6.5",
3+
"version": "1.7.0",
44
"name": "Docker (docker-outside-of-docker)",
55
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-outside-of-docker",
66
"description": "Re-use the host docker socket, adding the Docker CLI to a container. Feature invokes a script to enable using a forwarded Docker socket within a container to run Docker commands.",
@@ -44,6 +44,11 @@
4444
"type": "boolean",
4545
"default": true,
4646
"description": "Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter."
47+
},
48+
"socketPath": {
49+
"type": "string",
50+
"default": "/var/run/docker-host.sock",
51+
"description": "Path where the Docker socket is mounted inside the container. For rootless Docker, override the mount in devcontainer.json to map your host socket to this path."
4752
}
4853
},
4954
"entrypoint": "/usr/local/share/docker-init.sh",

src/docker-outside-of-docker/install.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ MOBY_BUILDX_VERSION="${MOBYBUILDXVERSION:-"latest"}"
1313
DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"v2"}" # v1 or v2 or none
1414

1515
ENABLE_NONROOT_DOCKER="${ENABLE_NONROOT_DOCKER:-"true"}"
16-
SOURCE_SOCKET="${SOURCE_SOCKET:-"/var/run/docker-host.sock"}"
16+
SOCKET_PATH="${SOCKETPATH:-"/var/run/docker-host.sock"}" # From feature option
17+
SOURCE_SOCKET="${SOURCE_SOCKET:-"${SOCKET_PATH}"}"
1718
TARGET_SOCKET="${TARGET_SOCKET:-"/var/run/docker.sock"}"
1819
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
1920
INSTALL_DOCKER_BUILDX="${INSTALLDOCKERBUILDX:-"true"}"
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Docker Outside-of-Docker with Rootless Docker
2+
3+
This document shows how to configure the `docker-outside-of-docker` feature with rootless Docker installations.
4+
5+
## Standard Rootless Docker Configuration
6+
7+
For a typical rootless Docker setup:
8+
9+
```json
10+
{
11+
"features": {
12+
"ghcr.io/devcontainers/features/docker-outside-of-docker": {
13+
"socketPath": "/var/run/docker-rootless.sock"
14+
}
15+
},
16+
"mounts": [
17+
{
18+
"source": "${env:XDG_RUNTIME_DIR}/docker.sock",
19+
"target": "/var/run/docker-rootless.sock",
20+
"type": "bind"
21+
}
22+
]
23+
}
24+
```
25+
26+
## Custom Socket Path Configuration
27+
28+
For rootless Docker with custom socket location:
29+
30+
```json
31+
{
32+
"features": {
33+
"ghcr.io/devcontainers/features/docker-outside-of-docker": {
34+
"socketPath": "/custom/docker/socket.sock"
35+
}
36+
},
37+
"mounts": [
38+
{
39+
"source": "/run/user/1000/docker.sock",
40+
"target": "/custom/docker/socket.sock",
41+
"type": "bind"
42+
}
43+
]
44+
}
45+
```
46+
47+
## Detecting Your Docker Socket
48+
49+
To find your Docker socket location:
50+
51+
```bash
52+
# Check for rootless Docker socket
53+
ls -la ${XDG_RUNTIME_DIR}/docker.sock
54+
# Typically: /run/user/1000/docker.sock
55+
56+
# Check Docker context
57+
docker context ls
58+
```
59+
60+
## Key Points
61+
62+
1. **socketPath option**: Configures where the feature expects the socket inside the container
63+
2. **Mount source**: Must match your host's actual Docker socket location
64+
3. **Mount target**: Must match the `socketPath` option value
65+
4. **XDG_RUNTIME_DIR**: Usually `/run/user/{uid}` for rootless Docker
66+
67+
## Test Scenarios
68+
69+
The test scenarios demonstrate:
70+
- `rootless_docker_socket`: Standard rootless configuration
71+
- `custom_rootless_socket_path`: Custom socket path
72+
- `xdg_runtime_dir_socket`: XDG runtime directory style
73+
- `root_docker_socket`: Standard root Docker (for comparison)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
set -e
3+
4+
source dev-container-features-test-lib
5+
6+
echo "=== Custom Rootless Docker Socket Path Test ==="
7+
8+
# Test that the custom socket path is properly configured
9+
EXPECTED_SOCKET="/custom/docker/rootless.sock"
10+
11+
# Check if the custom socket exists and is accessible
12+
check "custom-socket-exists" test -S "$EXPECTED_SOCKET"
13+
check "custom-socket-readable" test -r "$EXPECTED_SOCKET"
14+
15+
# Verify Docker functionality using the custom socket
16+
export DOCKER_HOST="unix://$EXPECTED_SOCKET"
17+
check "docker-functional-custom" docker ps >/dev/null
18+
19+
# Verify that DOCKER_HOST is properly set by the feature
20+
check "docker-host-env-set" [ ! -z "$DOCKER_HOST" ]
21+
22+
# Test basic Docker operations
23+
check "docker-version" docker version --format '{{.Client.Version}}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' >/dev/null
24+
check "docker-info" docker info >/dev/null
25+
26+
echo "Custom socket path: $EXPECTED_SOCKET"
27+
echo "Docker host: $DOCKER_HOST"
28+
29+
reportResults
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
# Test script to detect Docker type
3+
4+
if [ -S "/var/run/docker.sock" ]; then
5+
echo "Root Docker detected"
6+
export DOCKER_HOST="unix:///var/run/docker-host.sock"
7+
elif [ -S "/var/run/docker-rootless.sock" ]; then
8+
echo "Rootless Docker detected"
9+
export DOCKER_HOST="unix:///var/run/docker-rootless.sock"
10+
else
11+
echo "No Docker socket found"
12+
exit 1
13+
fi
14+
15+
docker --version
16+
docker info --format '{{.SecurityOptions}}'
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
set -e
3+
4+
source dev-container-features-test-lib
5+
6+
echo "=== Rootless Docker Socket Configuration Test ==="
7+
8+
# Test the custom rootless socket path
9+
EXPECTED_SOCKET="/var/run/docker-rootless.sock"
10+
11+
# Check if the configured rootless socket exists and is accessible
12+
check "rootless-socket-exists" test -S "$EXPECTED_SOCKET"
13+
check "rootless-socket-readable" test -r "$EXPECTED_SOCKET"
14+
15+
# Verify Docker functionality using the rootless socket
16+
export DOCKER_HOST="unix://$EXPECTED_SOCKET"
17+
check "docker-functional-rootless" docker ps >/dev/null
18+
19+
# Test basic Docker operations with rootless configuration
20+
check "docker-version-rootless" docker version --format '{{.Client.Version}}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' >/dev/null
21+
check "docker-info-rootless" docker info >/dev/null
22+
23+
# Demonstrate that customers can configure custom socket paths
24+
echo "Configured rootless socket path: $EXPECTED_SOCKET"
25+
echo "Docker host: $DOCKER_HOST"
26+
27+
reportResults

test/docker-outside-of-docker/scenarios.json

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,5 +180,72 @@
180180
"moby": false
181181
}
182182
}
183+
},
184+
"rootless_docker_socket": {
185+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
186+
"features": {
187+
"docker-outside-of-docker": {
188+
"moby": false,
189+
"socketPath": "/var/run/docker-rootless.sock"
190+
}
191+
},
192+
"mounts": [
193+
{
194+
"source": "/var/run/docker.sock",
195+
"target": "/var/run/docker-rootless.sock",
196+
"type": "bind"
197+
}
198+
],
199+
"containerUser": "vscode"
200+
},
201+
"root_docker_socket": {
202+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
203+
"features": {
204+
"docker-outside-of-docker": {
205+
"moby": false
206+
}
207+
},
208+
"mounts": [
209+
{
210+
"source": "/var/run/docker.sock",
211+
"target": "/var/run/docker-host.sock",
212+
"type": "bind"
213+
}
214+
],
215+
"containerUser": "vscode"
216+
},
217+
"custom_rootless_socket_path": {
218+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
219+
"features": {
220+
"docker-outside-of-docker": {
221+
"moby": false,
222+
"socketPath": "/custom/docker/rootless.sock"
223+
}
224+
},
225+
"mounts": [
226+
{
227+
"source": "/var/run/docker.sock",
228+
"target": "/custom/docker/rootless.sock",
229+
"type": "bind"
230+
}
231+
],
232+
"containerUser": "vscode"
233+
},
234+
"xdg_runtime_dir_socket": {
235+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
236+
"features": {
237+
"docker-outside-of-docker": {
238+
"moby": false,
239+
"socketPath": "/var/run/user-docker.sock"
240+
}
241+
},
242+
"mounts": [
243+
{
244+
"source": "/var/run/docker.sock",
245+
"target": "/var/run/user-docker.sock",
246+
"type": "bind"
247+
}
248+
],
249+
"containerUser": "vscode"
183250
}
184-
}
251+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/bash
2+
set -e
3+
4+
source dev-container-features-test-lib
5+
6+
echo "=== XDG Runtime Directory Socket Test ==="
7+
8+
# Test XDG_RUNTIME_DIR style socket configuration
9+
EXPECTED_SOCKET="/var/run/user-docker.sock"
10+
11+
# Check if the socket exists and is accessible
12+
check "xdg-socket-exists" test -S "$EXPECTED_SOCKET"
13+
check "xdg-socket-readable" test -r "$EXPECTED_SOCKET"
14+
15+
# Verify Docker functionality using the XDG-style socket
16+
export DOCKER_HOST="unix://$EXPECTED_SOCKET"
17+
check "docker-functional-xdg" docker ps >/dev/null
18+
19+
# Test that this works for rootless-style configurations
20+
check "docker-version-xdg" docker version --format '{{.Client.Version}}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' >/dev/null
21+
22+
# Verify the socket path matches what a customer would configure
23+
echo "XDG-style socket path: $EXPECTED_SOCKET"
24+
echo "Docker host: $DOCKER_HOST"
25+
26+
reportResults

0 commit comments

Comments
 (0)