A complete implementation of Testcontainers for Zig. It provides Docker container management for integration testing, following the architecture and patterns of testcontainers-go.
A fully functional Zig library with:
- Docker Client (
src/docker_client.zig) — Unix-socket HTTP communication with the Docker Engine - Container Handle (
src/docker_container.zig) — Container lifecycle, port mapping, exec, logs - Container Configuration (
src/container.zig) —ContainerRequestand related types - Wait Strategies (
src/wait.zig) — 7 built-in readiness checks - Network Management (
src/network.zig) — Bridge network creation - Public API (
src/root.zig) —DockerProvider,run(),genericContainer(),modulesnamespace - 10 Pre-configured Modules (
src/modules/) — Postgres, MySQL, Redis, MongoDB, RabbitMQ, MariaDB, MinIO, Elasticsearch, Kafka, LocalStack
README.md— Features, installation, usage guideQUICKSTART.md— 5-minute getting-started guideARCHITECTURE.md— System design and patternsCONTRIBUTING.md— How to contributePROJECT_SUMMARY.md— Implementation overviewIMPLEMENTATION_GUIDE.md— This document
examples/basic.zig— Runnable end-to-end examplesrc/integration_test.zig— 24 integration tests.github/workflows/ci.yml— CI/CD pipeline
testcontainers-zig/
├── build.zig # Build system configuration
├── build.zig.zon # Package manifest & dependencies
│
├── src/
│ ├── root.zig # Public API (DockerProvider, run, modules)
│ ├── docker_client.zig # DockerClient — HTTP over unix socket
│ ├── docker_container.zig # DockerContainer — running container handle
│ ├── container.zig # ContainerRequest, KV, Mount, ContainerFile
│ ├── wait.zig # Strategy tagged union + forXxx helpers
│ ├── network.zig # Network creation / removal
│ ├── types.zig # Docker API JSON types
│ ├── integration_test.zig # 24 Docker integration tests
│ └── modules/
│ ├── postgres.zig
│ ├── mysql.zig
│ ├── redis.zig
│ ├── mongodb.zig
│ ├── rabbitmq.zig
│ ├── mariadb.zig
│ ├── minio.zig
│ ├── elasticsearch.zig
│ ├── kafka.zig
│ └── localstack.zig
│
├── examples/
│ └── basic.zig # Nginx example with HTTP wait + exec
│
├── .github/
│ ├── ISSUE_TEMPLATE/
│ ├── pull_request_template.md
│ ├── dependabot.yml
│ └── workflows/
│ └── ci.yml
│
├── README.md
├── QUICKSTART.md
├── ARCHITECTURE.md
├── PROJECT_SUMMARY.md
├── CONTRIBUTING.md
├── LICENSE
└── .gitignore
- Create containers from any Docker image
- Start, stop, terminate (force remove) containers
- Port mapping — Docker allocates a free host port per
exposed_portsentry - Container state inspection via
/containers/{id}/json execcommands inside a running container- Fetch container logs (stdout + stderr)
- Copy files or in-memory content into a container (
copyToContainer)
| Name | Constructor | Trigger |
|---|---|---|
| None | .none |
Immediate |
| Log | forLog("text") |
Substring in container stdout/stderr |
| HTTP | forHttp("/path") |
HTTP 2xx on the first exposed port |
| Port | forPort("5432/tcp") |
TCP connection succeeds |
| Health check | forHealthCheck() |
Docker HEALTHCHECK == healthy |
| Exec | forExec(&cmd) |
Command exit code 0 |
| All | forAll(&strats) |
All sub-strategies pass |
- Create named bridge networks
- Attach containers via
ContainerRequest.networks - DNS aliases via
ContainerRequest.network_aliases - Inter-container communication using aliased hostnames
All 10 modules follow the same pattern:
// Configuration
pub const default_image: []const u8;
pub const Options = struct { /* fields with defaults */ };
// Lifecycle
pub fn run(provider: *tc.DockerProvider, image: []const u8, opts: Options) !*XxxContainer
pub fn runDefault(provider: *tc.DockerProvider) !*XxxContainer
pub fn terminate(self: *XxxContainer) !void
pub fn deinit(self: *XxxContainer) void
// Connection helpers (caller owns returned slice)
pub fn connectionString(self, alloc) ![]u8 // or httpURL / brokers / endpointURL
pub fn port(self, alloc) !u16Public API entry point. Exports:
DockerProvider— owns aDockerClient; drivesrunContainer/createContainerrun(alloc, image, req)— global-provider shortcutgenericContainer(alloc, req)— named-container shortcut- All public types:
ContainerRequest,GenericContainerRequest,ContainerFile,Mount,KV,NetworkAlias,DockerContainer,DockerClient,docker_socket modulesnamespace re-exporting all 10 module fileswaitandnetworknamespaces
DockerClient communicates with the Docker Engine via a Unix socket using a built-in
HTTP/1.1 client (std.net.connectUnixSocket). Endpoints used:
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /containers/create |
Create container |
| POST | /containers/{id}/start |
Start |
| POST | /containers/{id}/stop |
Stop |
| DELETE | /containers/{id} |
Remove |
| GET | /containers/{id}/json |
Inspect |
| POST | /containers/{id}/exec |
Create exec instance |
| POST | /exec/{id}/start |
Run exec |
| GET | /containers/{id}/logs |
Fetch logs |
| POST | /images/create |
Pull image |
| POST | /networks/create |
Create network |
| POST | /networks/{id}/connect |
Connect container |
| DELETE | /networks/{id} |
Remove network |
DockerContainer wraps a container ID and a reference to DockerClient. All methods
take an alloc parameter for returned slices.
Key methods:
pub fn mappedPort(self, port_spec: []const u8, alloc) !u16
pub fn daemonHost(self, alloc) ![]const u8
pub fn containerIP(self, alloc) ![]const u8
pub fn inspect(self) !std.json.Parsed(types.ContainerInspect)
pub fn logs(self, alloc) ![]const u8
pub fn exec(self, cmd: []const []const u8, alloc) !ExecResult
pub fn copyToContainer(self, content: []const u8, dest_path: []const u8, mode: u32) !void
pub fn networks(self, alloc) ![][]const u8
pub fn networkAliases(self, network: []const u8, alloc) ![][]const u8
pub fn networkIP(self, network: []const u8, alloc) ![]const u8
pub fn start(self) !void
pub fn stop(self, timeout_s: u32) !void
pub fn terminate(self) !void
pub fn deinit(self) voidPlain data types — no methods, no allocations:
pub const ContainerRequest = struct {
image: []const u8 = "",
cmd: []const []const u8 = &.{},
entrypoint: []const []const u8 = &.{},
env: []const []const u8 = &.{}, // "KEY=VALUE"
exposed_ports: []const []const u8 = &.{},
labels: []const KV = &.{},
name: ?[]const u8 = null,
wait_strategy: wait.Strategy = .none,
networks: []const []const u8 = &.{},
network_aliases: []const NetworkAlias = &.{},
mounts: []const Mount = &.{},
files: []const ContainerFile = &.{},
always_pull_image: bool = false,
image_platform: []const u8 = "",
startup_timeout_ns: u64 = 0,
};pub const Strategy = union(enum) {
none,
log: LogStrategy,
http: HttpStrategy,
port: PortStrategy,
health_check: HealthCheckStrategy,
exec: ExecStrategy,
all: AllStrategy,
};
// Constructor helpers
pub fn forLog(message: []const u8) Strategy
pub fn forHttp(path: []const u8) Strategy
pub fn forPort(port_spec: []const u8) Strategy
pub fn forHealthCheck() Strategy
pub fn forExec(cmd: []const []const u8) Strategy
pub fn forAll(strategies: []const Strategy) StrategyDocker API JSON types parsed with std.json:
ContainerInspect, ContainerInspectNetworks, NetworkSettings, PortBinding,
HealthState, ExecCreate, ExecStart, ExecInspect, ImageCreate, NetworkCreate,
NetworkConnect, CreateContainer, CreateContainerResponse.
pub const Network = struct {
id: []const u8,
name: []const u8,
pub fn create(client: *DockerClient, name: []const u8, alloc) !Network
pub fn remove(self: *Network, client: *DockerClient, alloc) !void
};24 tests exercising the full stack end-to-end. Each test:
- Attempts to connect to Docker; returns
error.SkipZigTestif unavailable. - Starts a real container.
- Asserts postconditions (mapped port > 0, connection string non-empty, etc.).
- Cleans up via
defer.
Full nginx example:
tc.run(alloc, "nginx:latest", .{ .wait_strategy = tc.wait.forHttp("/") })ctr.mappedPort("80/tcp", alloc)- Fetch
/usingstd.http.Client ctr.exec(&.{"echo", "hello"})- Print output
- Create
src/modules/<name>.zigfollowing the conventions insrc/modules/postgres.zig. - Add
pub const <name> = @import("modules/<name>.zig");insidepub const modulesinsrc/root.zig. - Add a test block in the new file.
- Add an integration test in
src/integration_test.zig. - Update
PROJECT_SUMMARY.md,README.mdmodule table, and this guide.
- Add a new field and payload struct to the
Strategyunion insrc/wait.zig. - Add a
pub fn forXxx(...)constructor in thewaitnamespace. - Handle the new tag in the strategy dispatch
switchindocker_container.zig. - Add a test in
src/integration_test.zig.
zig build # compile all targets
zig build test --summary all # unit tests (no Docker required)
zig build integration-test --summary all # Docker integration tests
zig build example # run examples/basic.zig| Package | Version | Role |
|---|---|---|
| Zig stdlib | built-in | JSON, I/O, HTTP, networking, testing |
No external dependencies. The library uses a built-in HTTP/1.1 client over Unix domain socket.
| Metric | Value |
|---|---|
| Source files | 8 core + 10 modules + 1 example |
| Modules | 10 |
| Wait strategies | 7 |
| Integration tests | 24 |
| External dependencies | 0 |
| Zig version | 0.15.2 |
- Open an issue on GitHub
- Ask on Stack Overflow with the
testcontainerstag - Join the Testcontainers Slack