From dca7f41c4d6ce0d643a691010af964229bbc3751 Mon Sep 17 00:00:00 2001 From: Erik Arvidsson Date: Thu, 11 Jun 2026 12:49:51 +0000 Subject: [PATCH] feat(docker): add Docker-in-Docker feature for testcontainers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `docker` feature that gives dev containers a self-contained Docker daemon via the official docker-in-docker feature (pulled in with dependsOn). This unblocks tooling that shells out to Docker — notably the zero-cache Postgres testcontainers tests, which fail in the dev container today with "Could not find a working container runtime strategy". Uses docker-in-docker rather than docker-outside-of-docker so testcontainers' bind mounts and container networking work across Codespaces, CI, and local hosts without depending on a host socket. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 24 ++++++++++++++++++++++++ src/docker/devcontainer-feature.json | 11 +++++++++++ src/docker/install.sh | 10 ++++++++++ test/docker/test.sh | 12 ++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 src/docker/devcontainer-feature.json create mode 100755 src/docker/install.sh create mode 100755 test/docker/test.sh diff --git a/README.md b/README.md index ee008a1..bd787a8 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,30 @@ This replaces the corepack/pnpm/npm-removal block that otherwise lives in each r `post-create.sh`. Combined with `agents`, a consumer repo's `devcontainer.json` needs no lifecycle scripts at all. +## `docker` + +Gives the container a working Docker daemon so tooling that shells out to Docker — most +notably [testcontainers](https://testcontainers.com) (used by the `zero-cache` Postgres +integration tests) — runs inside the dev container. + +- Pulls in the official + [`ghcr.io/devcontainers/features/docker-in-docker`](https://github.com/devcontainers/features/tree/main/src/docker-in-docker) + feature via `dependsOn`, which installs the Docker engine, runs a daemon **inside** the + container, and adds the remote user to the `docker` group (no `sudo` needed). +- Uses Docker-**in**-Docker rather than docker-outside-of-docker on purpose: testcontainers + relies on bind mounts and container-to-container networking, both of which break under the + host-socket approach (path translation) and aren't available in every environment + (Codespaces, CI). A self-contained daemon "just works" everywhere. + +```jsonc +"features": { + "ghcr.io/rocicorp/devcontainer-features/docker:1": {} +} +``` + +This replaces a per-repo `docker-in-docker` feature line and centralizes the pinned version +alongside the other rocicorp features. + ## Updating the feature versions everywhere 1. Bump `codexVersion` default (and/or the `dependsOn` claude-code pin) in diff --git a/src/docker/devcontainer-feature.json b/src/docker/devcontainer-feature.json new file mode 100644 index 0000000..1fea66d --- /dev/null +++ b/src/docker/devcontainer-feature.json @@ -0,0 +1,11 @@ +{ + "id": "docker", + "version": "1.0.0", + "name": "Docker (Docker-in-Docker, testcontainers-ready)", + "description": "Provides a self-contained Docker daemon inside the container via the official Docker-in-Docker feature. Picked over docker-outside-of-docker so testcontainers (bind mounts + container networking) works without depending on a host socket, which keeps it portable across Codespaces, CI, and local Docker/Podman/OrbStack hosts.", + "documentationURL": "https://github.com/rocicorp/devcontainer-features/tree/main/src/docker", + "dependsOn": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {} + }, + "installsAfter": ["ghcr.io/devcontainers/features/node"] +} diff --git a/src/docker/install.sh b/src/docker/install.sh new file mode 100755 index 0000000..46162b8 --- /dev/null +++ b/src/docker/install.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +# The Docker engine itself is installed by the official Docker-in-Docker feature pulled in +# via `dependsOn` (it installs first and also adds the remote user to the `docker` group, so +# the daemon is reachable without sudo). This wrapper exists to give every rocicorp repo a +# single, centrally-pinned entry point for Docker — mirroring how `agents` wraps the official +# claude-code / github-cli features — and a place to hang any future testcontainers-specific +# defaults. There is nothing extra to install here. +echo "rocicorp/docker: Docker engine provided by the docker-in-docker dependency; no extra install steps." diff --git a/test/docker/test.sh b/test/docker/test.sh new file mode 100755 index 0000000..dc85c6f --- /dev/null +++ b/test/docker/test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Pulls in the dev container test library (check, reportResults). +source dev-container-features-test-lib + +check "docker client on PATH" bash -c "command -v docker" +check "docker daemon reachable" bash -c "docker ps" +# testcontainers shells out to `docker run`; make sure the daemon can actually start a container. +check "can run a container" bash -c "docker run --rm hello-world" + +reportResults