docker-test-runner
Skill0 starsChanged 7 days agoMITView on GitHub
Use when running a repo's tests or stack in Docker containers.
- Installs packages
In this repository6 other files it gives its agents.
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 2 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 25
If the repo is pure-language with a lockfile (uv, poetry, npm ci), run it directly — a container adds minutes per iteration for no …
- Distinctive: line 63
Check cleanliness afterwards — this is the assertion that the recipe worked:
- Very specific: line 69
Run docker run with --user "$(id -u):$(id -g)" on Linux when the image runs as root and writes into the mount.
- Distinctive: line 164
Docker Desktop must be running: before any of this on Windows/macOS.
- Very specific: line 167
Keep repos inside the WSL filesystem (~/repos) and use Docker Desktop's from-WSL integration, or accept minutes instead of seconds.
- Very specific: line 177
Images pile up: After a few rebuilds: docker image prune -f, and docker system df to see where the space went.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Running a repository's tests and stack in Docker
When to Use
- A repository's runtime is defined by its
Dockerfile(system packages, services, a specific interpreter) rather than by a lockfile you can install locally. - You do not want host toolchains installed at all.
- Two stacks must run side by side without fighting over ports, networks or volumes.
- A container reports unhealthy and you need to know whether it is the app or the probe.
If the repo is pure-language with a lockfile (uv, poetry, npm ci), run it directly — a
container adds minutes per iteration for no isolation you need.
1. Run the suite in the repo's own image
Build the runtime image, then mount the working tree read-only and install test dependencies inside the container:
docker build -t <name>:dev ./<context>
docker run --rm \
-v "$PWD/<context>:/app:ro" -w /app \
<name>:dev \
sh -c "pip install -q -r requirements-dev.txt && python -m pytest -p no:cacheprovider -q"docker run --rm -v "${PWD}\<context>:/app:ro" -w /app <name>:dev `
sh -c "pip install -q -r requirements-dev.txt && python -m pytest -p no:cacheprovider -q"Node equivalent:
docker run --rm -v "$PWD:/app:ro" -w /app node:22-alpine \
sh -c "npm ci --no-audit --no-fund && npm test -- --run"Why each part:
| Part | Reason |
-v <code>:/app:ro | the image has no test files (.dockerignore usually excludes them); the mount supplies them, and :ro keeps the git tree clean |
-w /app | the image's WORKDIR is often a subdirectory of the mount |
| install test deps in the container | runtime images ship production requirements only |
-p no:cacheprovider | pytest otherwise writes .pytest_cache into the mounted tree |
--rm | a stale exited container per run is pure litter |
Check cleanliness afterwards — this is the assertion that the recipe worked:
git status --porcelain # must print nothingRun docker run with --user "$(id -u):$(id -g)" on Linux when the image runs as root and
writes into the mount; otherwise the working tree ends up owned by root and the next git
command fails with permission errors. On Docker Desktop (macOS/Windows) file ownership is
translated by the VM, so this is usually unnecessary there.
Read-only mounts break more than caches
Any tool that writes a cache at import time fails against :ro, and the error reads like a
tool failure:
ruff check→Failed to initialize cache at /app/.ruff_cache: Read-only file system, exit 1. Useruff check --no-cache, orRUFF_CACHE_DIR=/tmp.mypywithcache_dirconfigured,eslint --cache,prettier --cache,go buildwithGOCACHEinside the tree — all the same shape. Point the cache at/tmpor disable it.black --checkand plainmypyneed no writes and work read-only.
Exporting PYTHONDONTWRITEBYTECODE=1 (or NODE_OPTIONS equivalents) prevents the subtler
variant: __pycache__ directories appearing in the tree and showing up as untracked files.
Rebuilding often? Add a test image
The in-container pip install re-runs on every invocation. For a suite you run frequently:
FROM <name>:dev
COPY requirements-dev.txt /tmp/
RUN pip install -q -r /tmp/requirements-dev.txtdocker build -t <name>:test -f Dockerfile.test . once, then docker run --rm -v "$PWD:/app:ro" -w /app <name>:test pytest -q.
Exit codes
docker run returns the container's exit code, so &&/set -e chains work — but do not pipe
the output through tee/tail without set -o pipefail, or a failing suite looks like a
success. And read the first failure, not the last line: pytest prints the short summary last,
which is rarely the cause.
2. Stack hygiene before up
docker ps --format '{{.Names}}\t{{.Image}}\t{{.Ports}}'
docker compose lsdocker ps --format "{{.Names}}`t{{.Image}}`t{{.Ports}}"
docker compose ls- Give every stack an explicit project name (
COMPOSE_PROJECT_NAME, or the top-levelname:in the compose file). Compose otherwise derives it from the directory name, so every/opt/*/devdirectory on a host shares the projectdev— anddocker compose down --remove-orphansin one stack removes the other's containers. - Volumes are named
<project>_<volume>. Changing the project name gives the new stack a fresh empty database. Either accept the reseed, or copy the old volume deliberately; do not pre-create a volume and expect compose to adopt it (label checks differ by compose version). - Pick ports outside the used set, or publish none and reach the service by container name. New stacks generally should not publish their database port at all.
- Migrating an existing stack: remove legacy containers by exact name after checking their
com.docker.compose.projectandcom.docker.compose.project.working_dirlabels — never by project name alone, for the reason above. - Ports belong in one place. The listener (
PORT), theports:mapping and theHEALTHCHECKmust all read the same variable:
services:
app:
environment:
PORT: ${PORT:-8090}
ports:
- "${PORT:-8090}:${PORT:-8090}"
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:${PORT:-8090}/health || exit 1"]A healthcheck with a hardcoded port keeps probing the old number after the port moves, so a
healthy container is reported unhealthy and restart: unless-stopped recycles it forever.
3. Verify
docker compose config # resolved values: ports, env, volumes, project name
docker compose up -d
docker ps --format '{{.Names}}\t{{.Status}}\t{{.Ports}}'
docker inspect -f '{{.State.Health.Status}}' <container> # healthy, not "starting"
docker compose logs --tail=50 <service>config is the cheap check that catches variable expansion mistakes before anything starts;
inspect is the only trustworthy source for health because docker ps shows (healthy) only
after the first successful probe.
Pitfalls
- Docker Desktop must be running before any of this on Windows/macOS; the CLI failing with "cannot connect to the Docker daemon" is usually that, not a broken install.
- WSL2 path performance. Code under
/mnt/c/...is slow enough to dominate a test suite. Keep repos inside the WSL filesystem (~/repos) and use Docker Desktop's from-WSL integration, or accept minutes instead of seconds. - Bind-mount paths differ by shell:
-v "$PWD/x:/app"in bash/zsh,-v "${PWD}\x:/app"in PowerShell,-v "%CD%\x:/app"in cmd. A path with spaces needs quoting around the wholesrc:dstargument. :rois not enforced the same everywhere. On some Docker Desktop setups (and with remote contexts) a read-only bind can still be written through; verify with a deliberatetouchinside the container if cleanliness matters.docker compose down -vdeletes named volumes — including the database you just spent an hour seeding. Preferdownwithout-vunless you mean it.- Images pile up. After a few rebuilds:
docker image prune -f, anddocker system dfto see where the space went. Neverdocker system prune -aon a host running other stacks. depends_ondoes not wait for readiness, only for start. A service that connects in its entrypoint needs a retry loop or acondition: service_healthydependency, or it fails on a cold start and works on the second try — the classic "works when I re-run it".- Container CPU/memory limits hide in
docker stats; a suite that times out in CI but passes locally can be a memory ceiling, not a slow test.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

