diff --git a/src/docker-outside-of-docker/README.md b/src/docker-outside-of-docker/README.md index e4fe0c446..3b37028e2 100644 --- a/src/docker-outside-of-docker/README.md +++ b/src/docker-outside-of-docker/README.md @@ -23,6 +23,7 @@ Re-use the host docker socket, adding the Docker CLI to a container. Feature inv | dockerDashComposeVersion | Compose version to use for docker-compose (v1 or v2 or none or latest) | string | latest | | installDockerBuildx | Install Docker Buildx | boolean | true | | 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 | +| 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 | ## Customizations @@ -36,6 +37,30 @@ Re-use the host docker socket, adding the Docker CLI to a container. Feature inv - 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. - 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. +## Rootless Docker Support + +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`: + +```json +{ + "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" + } + ] +} +``` + +**Notes:** +- Replace `1000` with your actual user ID (run `id -u` to find it) +- The feature will automatically detect the socket at `/var/run/docker-host.sock` +- Your custom mount will override the feature's default mount + ## Supporting bind mounts from the workspace folder 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 diff --git a/src/docker-outside-of-docker/devcontainer-feature.json b/src/docker-outside-of-docker/devcontainer-feature.json index f2e57bd43..d98b8021c 100644 --- a/src/docker-outside-of-docker/devcontainer-feature.json +++ b/src/docker-outside-of-docker/devcontainer-feature.json @@ -1,53 +1,58 @@ { - "id": "docker-outside-of-docker", - "version": "1.9.0", - "name": "Docker (docker-outside-of-docker)", - "documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-outside-of-docker", - "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.", - "options": { - "version": { - "type": "string", - "proposals": [ - "latest", - "none", - "20.10" - ], - "default": "latest", - "description": "Select or enter a Docker/Moby CLI version. (Availability can vary by OS version.)" - }, - "moby": { - "type": "boolean", - "default": true, - "description": "Install OSS Moby build instead of Docker CE" - }, - "mobyBuildxVersion": { - "type": "string", - "default": "latest", - "description": "Install a specific version of moby-buildx when using Moby" - }, - "dockerDashComposeVersion": { - "type": "string", - "enum": [ - "none", - "latest", - "v1", - "v2" - ], - "default": "latest", - "description": "Compose version to use for docker-compose (v1 or v2 or none or latest)" - }, - "installDockerBuildx": { - "type": "boolean", - "default": true, - "description": "Install Docker Buildx" + "id": "docker-outside-of-docker", + "version": "1.9.1", + "name": "Docker (docker-outside-of-docker)", + "documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-outside-of-docker", + "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.", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest", + "none", + "20.10" + ], + "default": "latest", + "description": "Select or enter a Docker/Moby CLI version. (Availability can vary by OS version.)" + }, + "moby": { + "type": "boolean", + "default": true, + "description": "Install OSS Moby build instead of Docker CE" + }, + "mobyBuildxVersion": { + "type": "string", + "default": "latest", + "description": "Install a specific version of moby-buildx when using Moby" + }, + "dockerDashComposeVersion": { + "type": "string", + "enum": [ + "none", + "latest", + "v1", + "v2" + ], + "default": "latest", + "description": "Compose version to use for docker-compose (v1 or v2 or none or latest)" + }, + "installDockerBuildx": { + "type": "boolean", + "default": true, + "description": "Install Docker Buildx" + }, + "installDockerComposeSwitch": { + "type": "boolean", + "default": false, + "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." + }, + "socketPath": { + "type": "string", + "default": "/var/run/docker-host.sock", + "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." + } }, - "installDockerComposeSwitch": { - "type": "boolean", - "default": true, - "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." - } - }, "entrypoint": "/usr/local/share/docker-init.sh", "customizations": { "vscode": { diff --git a/src/docker-outside-of-docker/install.sh b/src/docker-outside-of-docker/install.sh index 6e26b13ac..ac4561e44 100755 --- a/src/docker-outside-of-docker/install.sh +++ b/src/docker-outside-of-docker/install.sh @@ -13,7 +13,8 @@ MOBY_BUILDX_VERSION="${MOBYBUILDXVERSION:-"latest"}" DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"latest"}" # v1 or v2 or none or latest ENABLE_NONROOT_DOCKER="${ENABLE_NONROOT_DOCKER:-"true"}" -SOURCE_SOCKET="${SOURCE_SOCKET:-"/var/run/docker-host.sock"}" +SOCKET_PATH="${SOCKETPATH:-"/var/run/docker-host.sock"}" # From feature option +SOURCE_SOCKET="${SOURCE_SOCKET:-"${SOCKET_PATH}"}" TARGET_SOCKET="${TARGET_SOCKET:-"/var/run/docker.sock"}" USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" INSTALL_DOCKER_BUILDX="${INSTALLDOCKERBUILDX:-"true"}" @@ -318,22 +319,19 @@ else buildx=(moby-buildx${buildx_version_suffix}) fi apt-get -y install --no-install-recommends ${cli_package_name}${cli_version_suffix} "${buildx[@]}" || { err "It seems packages for moby not available in OS ${ID} ${VERSION_CODENAME} (${architecture}). To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS version (eg: 'ubuntu-24.04')." ; exit 1 ; } - apt-get -y install --no-install-recommends moby-compose || echo "(*) Package moby-compose (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." + if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "v1" ]; then + apt-get -y install --no-install-recommends moby-compose || echo "(*) Package moby-compose (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." + fi else buildx=() if [ "${INSTALL_DOCKER_BUILDX}" = "true" ]; then buildx=(docker-buildx-plugin) fi - apt-get -y install --no-install-recommends ${cli_package_name}${cli_version_suffix} "${buildx[@]}" docker-compose-plugin - buildx_path="/usr/libexec/docker/cli-plugins/docker-buildx" - # Older versions of Docker CE installs buildx as part of the CLI package - if [ "${INSTALL_DOCKER_BUILDX}" = "false" ] && [ -f "${buildx_path}" ]; then - echo "(*) Removing docker-buildx installed from docker-ce-cli since installDockerBuildx is disabled..." - rm -f "${buildx_path}" + #install cli + buildx first + apt-get -y install --no-install-recommends ${cli_package_name}${cli_version_suffix} "${buildx[@]}" + if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "v1" ]; then + apt-get -y install --no-install-recommends docker-compose-plugin fi - fi - unset buildx buildx_path -fi # If 'docker-compose' command is to be included if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then @@ -438,6 +436,8 @@ echo "docker-init doesn't exist, adding..." # By default, make the source and target sockets the same if [ "${SOURCE_SOCKET}" != "${TARGET_SOCKET}" ]; then + # Create parent directory if it doesn't exist + mkdir -p "$(dirname "${SOURCE_SOCKET}")" touch "${SOURCE_SOCKET}" ln -s "${SOURCE_SOCKET}" "${TARGET_SOCKET}" fi diff --git a/test/docker-outside-of-docker/docker_dash_compose_v1.sh b/test/docker-outside-of-docker/docker_dash_compose_v1.sh index 4ae7a9e02..33ae319f4 100755 --- a/test/docker-outside-of-docker/docker_dash_compose_v1.sh +++ b/test/docker-outside-of-docker/docker_dash_compose_v1.sh @@ -7,6 +7,7 @@ source dev-container-features-test-lib # Definition specific tests check "docker-compose" bash -c "docker-compose --version | grep -E '1.[0-9]+.[0-9]+'" +check "no docker compose plugin" bash -c "if command -v docker >/dev/null 2>&1; then ! docker compose version >/dev/null 2>&1; else true; fi" # Report result reportResults diff --git a/test/docker-outside-of-docker/root_docker_socket.sh b/test/docker-outside-of-docker/root_docker_socket.sh new file mode 100644 index 000000000..4c62147cb --- /dev/null +++ b/test/docker-outside-of-docker/root_docker_socket.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Test script to detect Docker type + +if [ -S "/var/run/docker.sock" ]; then + echo "Root Docker detected" + export DOCKER_HOST="unix:///var/run/docker-host.sock" +elif [ -S "/var/run/docker-rootless.sock" ]; then + echo "Rootless Docker detected" + export DOCKER_HOST="unix:///var/run/docker-rootless.sock" +else + echo "No Docker socket found" + exit 1 +fi + +docker --version +docker info --format '{{.SecurityOptions}}' \ No newline at end of file diff --git a/test/docker-outside-of-docker/rootless_docker_socket.sh b/test/docker-outside-of-docker/rootless_docker_socket.sh new file mode 100644 index 000000000..004554e9a --- /dev/null +++ b/test/docker-outside-of-docker/rootless_docker_socket.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +source dev-container-features-test-lib + +echo "=== Rootless Docker Socket Configuration Test ===" + +# Test the custom rootless socket path +EXPECTED_SOCKET="/var/run/docker-rootless.sock" + +# Check if the configured rootless socket exists and is accessible +check "rootless-socket-exists" test -S "$EXPECTED_SOCKET" +check "rootless-socket-readable" test -r "$EXPECTED_SOCKET" + +# Verify Docker functionality using the rootless socket +export DOCKER_HOST="unix://$EXPECTED_SOCKET" +check "docker-functional-rootless" docker ps >/dev/null + +# Test basic Docker operations with rootless configuration +check "docker-version-rootless" docker version --format '{{.Client.Version}}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' >/dev/null +check "docker-info-rootless" docker info >/dev/null + +# Demonstrate that customers can configure custom socket paths +echo "Configured rootless socket path: $EXPECTED_SOCKET" +echo "Docker host: $DOCKER_HOST" + +reportResults \ No newline at end of file diff --git a/test/docker-outside-of-docker/scenarios.json b/test/docker-outside-of-docker/scenarios.json index 3a49d594e..a8740629f 100644 --- a/test/docker-outside-of-docker/scenarios.json +++ b/test/docker-outside-of-docker/scenarios.json @@ -180,6 +180,32 @@ "moby": false } } + }, + "rootless_docker_socket": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "docker-outside-of-docker": { + "moby": false, + "socketPath": "/var/run/docker-rootless.sock" + } + }, + "mounts": [ + { + "source": "/var/run/docker.sock", + "target": "/var/run/docker-rootless.sock", + "type": "bind" + } + ], + "containerUser": "vscode" + }, + "root_docker_socket": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "docker-outside-of-docker": { + "moby": false + } + }, + "containerUser": "vscode" }, "docker_dash_compose_latest_moby": { "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", @@ -201,4 +227,5 @@ }, "containerUser": "vscode" } -} + + \ No newline at end of file