docker build, docker run, or docker compose, choose Docker access by the task’s trust boundary first:
All three modes require an image with the Docker CLI preinstalled. The two DinD modes accept only compatible images derived from official
docker:<version>-dind.
The image needs a Docker daemon, Node, and eval tools, but no custom NiceEval ENTRYPOINT. NiceEval starts and supervises the inner daemon,
Sandbox keepalive, in-container TTL, and docker info readiness. It does not dynamically install Docker into arbitrary images.
Do not put an underived docker:<version>-dind directly in source.image. That image lacks the Node runtime required by the NiceEval supervisor
and lacks tools for the Agent under test. Creation fails with dind-image-incompatible: missing node.
Use the Dockerfile below, or publish an equivalent derived image and point source to it.
Option 1: Mount a Docker socket directly
First build an image containing only the CLI, Node, and eval tools:sandbox/Dockerfile
experiments/docker-socket.ts
/var/run/docker.sock path inside the Sandbox,
and adds node to a supplementary group with the socket’s numeric GID. Before starting the Agent, NiceEval verifies that the image has no preset
Docker endpoint or context and that default docker info and explicit access to this Unix socket reach the same daemon. Sandbox creation fails when
the CLI is missing, the user cannot access the socket, or the image changes the default endpoint.
This check fixes the Agent’s default usage at startup; it is not a security boundary. The Agent can later pass docker --host explicitly to reach another
endpoint. Untrusted tasks must rely on the managed mode’s network policy, not on the Docker CLI default.
Option 2: Docker-in-Docker
DinD gives every outer Sandbox its own inner daemon. Create this image first:sandbox/Dockerfile
FROM to a reviewed digest and bake the fixed CLI tools the Agent under test needs into the image.
How NiceEval starts DinD
SelectingdockerAccess: { mode: "dind", ... } also selects NiceEval’s DinD image protocol.
The Provider overrides the derived image’s original ENTRYPOINT and CMD, checks docker-init,
node, docker, dockerd-entrypoint.sh, timeout, and tail, then starts its own
supervisor. The supervisor watches both the official dockerd-entrypoint.sh dockerd and the Sandbox keepalive process.
If either process exits early, it stops the outer container instead of leaving a Sandbox that looks alive but cannot use Docker.
The inner daemon listens only on /var/run/docker.sock and exposes no 2375/2376 TCP endpoint.
The Agent still runs as user: "node". The Dockerfile above adds node to the docker group at build time,
so the socket does not need chown root:node or chmod 666. If the image lacks a tool, the user is not in the docker group,
the daemon exits early, or readiness times out, NiceEval collects a bounded log tail and reports an actionable reason before deleting the failed container.
Do not set DOCKER_HOST or DOCKER_CONTEXT in the derived image. NiceEval first confirms the default context,
then verifies that docker info without endpoint options and explicit /var/run/docker.sock access reach the same daemon.
Only after this compatibility check passes does it run author-declared readiness.
This protocol does not promise to preserve the startup semantics of an arbitrary service image. If you need to evaluate services that depend on their own
ENTRYPOINT / CMD, use a Compose Sandbox and declare service processes with Compose.
Prepare an Attempt runtime with an action
The Dockerfile should bake fixed tools and project starting files. Put operations that need the inner daemon in Sandbox-level.before() actions, not in the image ENTRYPOINT. This declarative action creates a writable workspace and verifies the inner daemon. Add docker load, file-copy, or project smoke-check commands here only when the corresponding archives and files are baked into your image:
lib/dind-sandbox.ts
errored during sandbox.create; a half-prepared Sandbox is never handed to the Agent. Keep fixed content in image layers.
For each Attempt, copy or import only state that must be written to tmpfs or the inner data root to reduce repeated installation and network drift.
Raw privileged DinD
On a disposable VM or dedicated runner, you can select raw privileged explicitly:experiments/dind-raw.ts
raw-privileged literal authorizes the risk. NiceEval does not describe raw mode as rootless and does not automatically fall back to a managed profile on failure.
Managed rootless DinD
Use managed mode on a shared host or for an untrusted Agent. Beyond the inner daemon, it adds profile attestation, per-container resource limits, cross-process capacity admission, an exclusive outer network, and watchdog recovery: On a NixOS host, first deploy and verify a profile according to Configure Managed DinD on NixOS, then configure the Experiment below.experiments/dind-managed.ts
doctor starts a DinD container on that profile and actually runs inner
docker run --rm alpine:3.20 true. It proves that the profile supports nested Docker, but does not inspect your project’s
Dockerfile. The project image is still verified by its default docker info readiness.
Set run concurrency for DinD
resources.memoryBytes limits one Sandbox; it does not set run concurrency automatically. DinD also consumes inner
images, BuildKit, tmpfs, and page cache, so the default concurrency can be too high for the host. Start with a small-concurrency smoke run:
available memory ÷ memoryBytes, while leaving capacity for host Docker, NiceEval, and other processes.
When an Experiment serves only these heavy Sandboxes, it can also set maxConcurrency directly.
Diagnose DinD creation failures
After a creation failure, use the Attempt locator printed by the terminal rather than inspecting run artifacts directly:Verify a Docker task in an eval
All three modes expose the same Docker CLI usage to an eval:evals/docker-compose.eval.ts
sandbox directly in defineEval({ sandbox: ... }). Put it on the Experiment when several evals share the configuration.