mwaeckerlin/dockindock runs a complete docker daemon inside a container — as an unprivileged user. Use it for CI jobs, sandboxes and nested tooling that need their own isolated docker without handing them root.
Unlike classic docker-in-docker images, the inner daemon runs rootless:
even a full compromise of the inner docker never yields a root process
inside the container. The docker CLI is preconfigured (DOCKER_HOST,
PATH), so docker … works out of the box.
All features are listed in FEATURES.md, all tests in TESTS.md.
The image is self-contained: it needs no host configuration (no AppArmor
profile, no sysctl change) — current Ubuntu hosts restrict user-namespace
creation for capability-less processes, and the image answers that with a
CAP_SYS_ADMIN file capability on the small rootlesskit supervisor (see
the trade-off below).
The container must be started with --privileged (rootless docker needs
the namespace and mount privileges):
docker run -d --privileged --name dockindock mwaeckerlin/dockindock
Or with the bundled compose file:
npm run buildnpm start (foreground) or npm run start:daemon (background)docker compose exec dockindock docker run --rm alpine echo helloCtrl+C (or npm stop for daemon mode)DOCKER_DATA_ROOT (default /docker-data) — where the inner daemon
stores images and containers. Always mount a named volume on
/docker-data: besides persistence, the inner overlay mounts cannot
sit on the container's own overlayfs (the kernel rejects
overlay-on-overlay). Never mount over /docker (that would mask the
toolchain).DOCKER_TCP_PORT (default: unset) — when set (e.g. 2375), the
docker API is additionally published on tcp://0.0.0.0:<port>, so the
container can serve as a shared docker-in-docker service for sibling
containers that point DOCKER_HOST=tcp://<service>:<port> at it.
Malformed values abort startup with a clear error.--privileged is required: the flag grants the container broad
access to the host — run the image only on hosts where that is
acceptable. Inside the container, everything still runs as an
unprivileged user (rootless daemon), which is the whole point of this
image.CAP_SYS_ADMIN file capability:
current Ubuntu hosts block user-namespace creation for capability-less
processes; the capability is the documented exemption and keeps the
image independent of any host configuration. Only the small supervisor
gains it — inside a container that must be --privileged anyway — while
the daemon and every workload keep running as the unprivileged user./docker/bin precedes the system PATH:
the rootless binaries exist nowhere else, and the directory belongs to
the same unprivileged user the daemon runs as — a process able to write
there already runs as that user, so nothing is gained by shadowing.DOCKER_TCP_PORT on an isolated container network dedicated to
the consumers (as the hermes and openclaw stacks do), never publish it
to a host port or a shared network.npm test runs the docs contract (every feature in
FEATURES.md has a test in TESTS.md, no skipped
tests), the config contract (client, rootless launcher, subordinate ids,
unprivileged user, preconfigured environment) and the end to end suite,
which starts the daemon privileged and runs a real inner container:
npm test # docs + config contract + e2e
The image is built from mwaeckerlin/very-base (build stage) and ships on
mwaeckerlin/scratch; the rootless toolchain is installed by the official
get.docker.com/rootless script into /docker.
Content type
Image
Digest
sha256:2c8275e7d…
Size
115.3 MB
Last updated
about 4 hours ago
docker pull mwaeckerlin/dockindock