Guidelines for AI agents working in the testcontainers-zig repository.
Zig library for managing throwaway Docker containers in tests, inspired by
testcontainers-go.
Single module: testcontainers (Docker HTTP client over unix socket, container
lifecycle, wait strategies, and 10 pre-configured modules).
See ARCHITECTURE.md for design decisions and component diagrams. See PROJECT_SUMMARY.md for a full feature inventory.
zig build # Build all targets
zig build test --summary all # Unit tests (no Docker required)
zig build integration-test --summary all # Integration tests (requires Docker)
zig build example # Run examples/basic.zigThere is no separate formatter or linter. Follow the style rules below and let
zig fmt (built into the compiler) handle formatting.
- Zig 0.15.2, Zig Build System (
build.zig+build.zig.zon). - 4-space indentation, ~120-character line limit.
snake_casefor functions, variables, and fields;PascalCasefor types (structs, unions, enums).SCREAMING_SNAKE_CASEfor comptime constants.- All public declarations must have
///doc comments. - Use
errdeferto release resources in error paths. - Propagate errors explicitly — never use
try!orcatch unreachablein production paths. - Every function that allocates memory must accept
std.mem.Allocator; document ownership of all returned slices. - Follow the rules in CONTRIBUTING.md for commit messages and PR conventions.
- Single library: all code lives under
src/; no external dependencies (uses built-in HTTP/1.1 client over Unix domain socket). - Tagged unions over protocols:
wait.Strategyis a tagged union, not an interface. - Struct-literal configuration:
ContainerRequestuses default field values; no builder methods or method chaining. - Module pattern: each
src/modules/<name>.zigexposesdefault_image,Options,run(),runDefault(), and appropriate connection helpers (connectionString,httpURL,brokers,endpointURL). - Deterministic cleanup: always use
defer ctr.terminate() catch {}anddefer ctr.deinit().
See ARCHITECTURE.md for diagrams and rationale.
| Area | Path |
|---|---|
| Build manifest | build.zig + build.zig.zon |
| Public API | src/root.zig |
| Docker HTTP client | src/docker_client.zig |
| Container handle | src/docker_container.zig |
| Container config types | src/container.zig |
| Wait strategies | src/wait.zig |
| Network management | src/network.zig |
| Docker API JSON types | src/types.zig |
| Pre-configured modules | src/modules/ |
| Integration tests | src/integration_test.zig |
| Example | examples/basic.zig |
| CI workflow | .github/workflows/ci.yml |
- Create
src/modules/<name>.zigfollowing the existingsrc/modules/postgres.zigpattern. - Export it inside
pub const modules = struct { ... }insrc/root.zig:pub const <name> = @import("modules/<name>.zig");
- Pre-configure:
default_image,Optionsstruct with defaults, wait strategy. - Expose a connection helper (
connectionString,httpURL,brokers, orendpointURL) that returns a caller-owned[]u8. - Add a
test { ... }block in the module file. - Add an end-to-end integration test in
src/integration_test.zig. - Update the module table in
README.md,PROJECT_SUMMARY.md, andIMPLEMENTATION_GUIDE.md.
- Add a new payload struct and a new field to the
Strategyunion insrc/wait.zig. - Add a
pub fn forXxx(...)constructor in thewaitnamespace. - Handle the new tag in the strategy dispatch
switchinsrc/docker_container.zig. - Add a test exercising the new strategy in
src/integration_test.zig.
Workflow files live in .github/workflows/. The pipeline runs zig build test and
zig build integration-test on both Linux and macOS.
- Every new public API must have at least one test.
- Tests are plain
test { ... }blocks usingstd.testing. - Integration tests that require Docker must check reachability and return
error.SkipZigTestwhen Docker is unavailable. - Always clean up containers with
defer ctr.terminate() catch {}anddefer ctr.deinit(). - Run
zig build test --summary allbefore every commit.
- Never unwrap optional or error types without handling; prefer
orelse/catchwith explicit fallback. - Use
errdeferto undo partial initialisation in error paths. - Pass
std.mem.Allocatorexplicitly; document which return values are caller-owned. - Do not hardcode the Docker socket path — use
tc.docker_socketor honourDOCKER_HOST. - In test code, create a fresh
DockerProviderper test function, not a shared global. - When building
ContainerRequest, prefer struct-literal syntax with named fields over any builder pattern. - Keep module wrappers thin — they should build a
ContainerRequestand delegate toDockerProvider.runContainer, not reimplement lifecycle logic.