# testcontainers-go: container-backed integration tests in Go, and where the abstraction leaks

> testcontainers-go starts real Postgres, MySQL, Redis or Kafka containers from Go test code and tears them down when the test ends. It is the right tool when your tests need a real dependency, and the wrong one when they only need a fake.

**testcontainers/testcontainers-go** — Testcontainers for Go is a Go package that makes it simple to create and clean up container-based dependencies for automated integration/smoke tests. The clean, easy-to-use API enables developers to programmatically define containers that should be run as part of a test and clean up those resources when the test is done.

- Repository: https://github.com/testcontainers/testcontainers-go
- Website: https://golang.testcontainers.org
- Stars: 4,991 · Forks: 642
- Language: Go
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/testcontainers-testcontainers-go

## The problem testcontainers-go solves, and who it is written for

Integration tests usually need a database, a broker or a cache that is actually running. The two common workarounds both cost something. In-memory substitutes such as an embedded SQL engine or a fake Kafka client diverge from production behaviour in ways that only show up later. A long-lived shared test environment, on the other hand, is stateful: one test leaves a row behind and another fails for reasons unrelated to the change under review.

testcontainers-go is a Go package that takes a third route. The README states that it makes it simple to create and clean up container-based dependencies for automated integration and smoke tests, and that the API lets developers programmatically define containers that should run as part of a test and clean up those resources when the test is done. The audience is Go developers who already have a container runtime available where tests execute, typically a laptop or a CI runner.

The package is versioned and released on a regular cadence. The repository lists v0.44.0 on 2026-08-07, v0.43.0 on 2026-06-19 and v0.42.0 on 2026-04-09, and the last push was on 2026-09-21. The MIT licence is stated in the README badge and the LICENSE file. The module path is github.com/testcontainers/testcontainers-go, and the repository requires go 1.25.0 with toolchain go1.25.9.

## How a container becomes a test fixture: lifecycle, wait strategy and ports

The repository layout shows the shape of the library. Top-level files include container.go, docker.go, docker_client.go, network.go, mounts.go, image.go, lifecycle.go, cleanup.go, config.go, logconsumer.go and file.go. Two directories matter for scope: modules/ holds the per-technology packages, and examples/ holds runnable samples such as examples/nginx.

The split tells you how the abstraction is layered. The root package owns the generic container lifecycle: creating a container from an image, starting it, exposing its ports, reading logs, copying files in and out, and removing it. The modules directory holds the typed wrappers that know a specific product's connection string format and default port. The generic.go file sits alongside container.go, which suggests the generic container API is a first-class entry point rather than a fallback.

Lifecycle and cleanup being separate files is the interesting design choice. A container is started explicitly, and cleanup is a distinct concern, which is what lets the library attach teardown to the test rather than to a deferred call you have to remember. The README describes exactly that contract: define the containers a test needs, then clean up those resources when the test is done.

Network and mounts are also top-level concerns rather than options buried in the container struct. That matters when two containers have to reach each other, for example an application container that connects to a database container. The library exposes network creation as part of the same API surface, and the related searches include a network-specific phrase, which suggests this is a common point of confusion rather than an afterthought.

## Installing testcontainers-go and running a first container-backed test

The README does not inline installation steps. It says to visit the quickstart guide at golang.testcontainers.org/quickstart to understand how to add the dependency to your Go project. That page is rendered from the docs directory in the repository, so the documentation and the code ship together.

What the repository does give you is the module path and the toolchain constraint. Because the module declares go 1.25.0 and toolchain go1.25.9, a build image pinned to an older Go release will either refuse the module or try to download the newer toolchain. Check that before you debug anything else.

Once the dependency is in place, the generic container API is the entry point that does not depend on a specific module. The pattern the README describes is define, run, clean up. The repository's examples/nginx sample is the smallest place to see that pattern in the project's own code rather than in prose, and examples_test.go at the root runs the examples.

The repository also ships a Makefile target for the docs site, which builds an mkdocs container on port 8000:

```bash
make serve-docs
```

That runs the docs locally from requirements.txt, which pins mkdocs 1.5.3 and mkdocs-material 9.5.18. Useful if you want to read the quickstart offline against the exact version the project builds.

The Makefile also exposes the test entry points the project uses on itself:

```bash
make test-all
```

That target depends on tools, test-tools and test-unit, which tells you the project separates its own test tiers rather than running everything in one pass. If you are deciding whether to adopt the library, reading examples/nginx and examples/Makefile first is cheaper than reading the quickstart, because the sample shows the calls in the order they execute.

## Where testcontainers-go is the wrong tool

The package requires a container runtime. If the machine running go test cannot reach a Docker daemon, nothing in this library works, and no amount of configuration changes that. The search question about using Testcontainers without Docker is a fair one to ask, and the honest answer is that the library is built around container APIs; the docs directory includes a Podman page, which is a different runtime rather than an absence of one.

Startup cost is the second constraint. Every test that starts a container pays for image pull, container start and whatever readiness check the wait strategy performs. On a cold CI runner with no image cache, a Postgres image pull can take longer than the test itself. The library cleans up containers, but it does not make them free. Teams that run hundreds of small tests will feel this, and the usual response is to share one container across a package rather than starting one per test, which reintroduces some of the state leakage the library was meant to remove.

There is also a category of test that gains nothing. If your code under test takes an interface and you supply a stub, a real container adds latency and a runtime dependency without changing what you learn. testcontainers-go is for the layer where the interaction with the real dependency is the thing being tested: SQL dialect quirks, transaction isolation, message ordering, connection pool behaviour under load.

Finally, the library is pre-1.0. The releases listed are v0.42.0, v0.43.0 and v0.44.0. The README does not document a stability guarantee or a deprecation policy for the public API, so pinning a version and reading the release notes before upgrading is the practical approach.

## testcontainers-go compared with docker compose in CI

The closest alternative for many teams is a docker compose file that the CI job brings up before running go test. The difference is where the lifecycle lives. With compose, the services are declared in YAML, started by a shell step, and torn down by another shell step or by the CI runner discarding the job. The test binary has no idea the services exist; it just reads connection details from environment variables.

With testcontainers-go, the lifecycle lives inside the test binary. The container is created by the same process that runs the assertions, and the cleanup is attached to the test rather than to the pipeline. That means a developer running a single test locally gets the same dependency the CI job gets, without a separate compose invocation. It also means the dependency is versioned with the test code, so changing the Postgres image is a code review rather than an edit to a pipeline file.

The costs run the other way too. Compose can start several services once and let every test in the package reuse them, which is cheaper on a cold runner. Compose also lets you inspect the running stack by hand when a test fails, since the services outlive the test process. With testcontainers-go, a failed test may leave you with a container that has already been removed, and diagnosing it means reading the log consumer output rather than opening a shell.

The related searches include a compose-specific phrase, and the repository reflects that interest: the Makefile has a set of compose goals (compose-clean, compose-clone, compose-replace, compose-spec-replace, compose-tidy, compose-test-all-latest) for testing the compose module against the latest compose and compose-go repositories. So the two approaches are not mutually exclusive in the project's own view; the compose module exists to drive compose files from Go when that is what you need.

## Maintenance, upgrade cost and licence

The repository is not archived, and the last push was on 2026-09-21. Releases arrive roughly every two months based on the three listed dates. That cadence is a cost as well as a signal: a pre-1.0 module on a two-month release cycle means upgrades are a recurring task, not a one-time setup. The module has a substantial dependency tree, including the moby client and api packages, containerd libraries, OpenTelemetry-adjacent indirect dependencies and gopsutil. Each of those can move independently and show up as a transitive bump.

The repository is structured to make that manageable. There is a tidy-all target that runs go mod tidy at the root, in examples/ and in modules/, which mirrors the fact that the modules directory may be a separate module boundary. There is a lint-all target covering the root, modulegen, examples and modules. If you vendor or fork, those targets tell you where the project expects consistency to be maintained.

The licence is MIT, stated in the README badge and present as LICENSE at the repository root. MIT is permissive: it allows use, modification and redistribution with the licence and copyright notice retained. That is a summary of the licence text, not legal advice. If your organisation has rules about the licences of transitive dependencies, the go.mod file is the place to audit, since the direct requirements list includes several separate projects each under their own terms.

## Conclusion

Adopt testcontainers-go when your Go tests exercise a real dependency such as Postgres, MySQL, Redis or Kafka and you can run a container runtime in the environment that executes them. Do not adopt it for pure unit tests, for builds where the test runner has no access to a container daemon, or where a large image pull would dominate the build. Before committing, verify that the Go toolchain in go.mod (go 1.25.0, toolchain go1.25.9) matches your build image, and confirm that the container runtime your CI provides is one the project supports, since the README documents Docker and the docs cover Podman as a separate page.

## FAQ

### What are Testcontainers used for?

The README describes testcontainers-go as a Go package that creates and cleans up container-based dependencies for automated integration and smoke tests. Developers define the containers a test needs programmatically, and the resources are cleaned up when the test finishes.

### Can I use Testcontainers without Docker?

The library is built around container runtimes, and the README names Docker in the project description. The docs directory does include a Podman page, so Podman is documented as a runtime, but there is no documented mode that runs without a container runtime.

### How do I add testcontainers-go to a Go project?

The README does not inline the steps. It says to visit the quickstart guide at golang.testcontainers.org/quickstart to understand how to add the dependency. The module path is github.com/testcontainers/testcontainers-go.

### Which Go version does testcontainers-go require?

The go.mod file declares go 1.25.0 and toolchain go1.25.9. A build image on an older Go release will need to account for that before the module will build.

### Which databases and services have a testcontainers-go module?

The repository has a modules/ directory holding the per-technology packages, and the related searches name Postgres, MySQL, Redis, Kafka and LocalStack. The modules directory is the authoritative list of which wrappers exist.

### Is testcontainers-go stable enough to depend on?

The releases listed are v0.42.0, v0.43.0 and v0.44.0, so the module is pre-1.0. The README does not document a stability guarantee or a deprecation policy for the public API.

## Sources

- [License: MIT](https://github.com/testcontainers/testcontainers-go/blob/main/LICENSE)
- [Project website](https://golang.testcontainers.org)
- [README](https://github.com/testcontainers/testcontainers-go/blob/main/README.md)
- [Releases](https://github.com/testcontainers/testcontainers-go/releases)
- [testcontainers/testcontainers-go on GitHub](https://github.com/testcontainers/testcontainers-go)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/testcontainers-testcontainers-go
