I switched from Docker Desktop to Lima a while back. Lighter, faster, no licensing headaches. For day-to-day Docker use it is seamless. Then I added Testcontainers to a Spring Boot project and the integration test started failing with the least helpful error message in the ecosystem:
ERROR tc.testcontainers/ryuk:0.11.0 -- Could not start containerERROR tc.testcontainers/ryuk:0.11.0 -- There are no stdout/stderr logs available
No stack trace. No connection refused. Just silence and a 60-second timeout. This post is the guide I wish I had found instead of spending an afternoon on it.
Why Lima Breaks the Default Testcontainers Setup
Lima exposes Docker through a Unix socket at a non-standard path — typically ~/docker.sock or inside ~/.lima/ — and sets DOCKER_HOST in your shell to point to it. That is enough for the Docker CLI and most tools.
Testcontainers has two problems with this setup.
- Maven Surefire forks a new JVM process for tests. That forked process does not automatically inherit your shell’s
DOCKER_HOST. Testcontainers falls back to the default socket path (/var/run/docker.sock), finds nothing, and times out. - Ryuk cannot start inside Lima’s Docker. Ryuk is the resource-reaper sidecar container that Testcontainers starts to clean up after itself. It requires a privileged bind-mount of the Docker socket from inside a container, which Lima’s Docker daemon restricts by default. The result is that silent 60-second hang.
Fix both problems and everything works. Here are the five steps.
Step 1: Find Your Lima Docker Socket Path
Before configuring anything, confirm where your socket actually lives:
echo $DOCKER_HOST# unix:///Users/you/docker.sock# Or check directlylimactl list# NAME STATUS SSH CPUS MEMORY DISK# docker Running 127.0.0.1:60006 3 4GiB 70GiB
The value from $DOCKER_HOST is what you will use in the next step. If it is not set, check ~/.lima/docker/sock/docker.sock or run limactl shell docker -- docker context inspect.
Step 2: Create ~/.testcontainers.properties
Testcontainers always reads this file, regardless of how the JVM was started. It is the most reliable place to set the socket path and disable Ryuk.
# ~/.testcontainers.properties# Point to the Lima socketdocker.host=unix:///Users/you/docker.sock# Disable Ryuk — it cannot start inside Lima's Dockerryuk.disabled=true
Disabling Ryuk does not mean your containers leak. It means Testcontainers will not start a separate reaper container. Containers started with @Container static are tied to the JVM process and stopped when it exits. For the vast majority of test suites this is perfectly safe.
Step 3: Pass DOCKER_HOST Through Maven Surefire
~/.testcontainers.properties handles Testcontainers’ internal config, but Maven Surefire forks a JVM that may not see your shell environment. Pass the variables explicitly in your pom.xml:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <environmentVariables> <TESTCONTAINERS_RYUK_DISABLED>true</TESTCONTAINERS_RYUK_DISABLED> <DOCKER_HOST>${env.DOCKER_HOST}</DOCKER_HOST> </environmentVariables> </configuration></plugin>
${env.DOCKER_HOST} reads the value from the shell that invoked Maven and forwards it to the forked process. This is belt-and-suspenders alongside ~/.testcontainers.properties, and it is what made the test pass consistently in CI as well.
Step 4: Disable Spring Boot Docker Compose in Tests
If you are using the spring-boot-docker-compose module — which starts your compose.yaml automatically on application startup — there is a conflict: both Spring Boot and Testcontainers will try to manage a Postgres instance at the same time.
The fix is a single property in your test resources:
# src/test/resources/application.propertiesspring.docker.compose.enabled=false
Tests use the Testcontainers-managed Postgres. The application uses Docker Compose at runtime. Each environment gets its own database lifecycle, cleanly separated.
Step 5: Use @ServiceConnection for Zero-Config Datasource Wiring
Spring Boot 3.1 introduced @ServiceConnection, and it is the cleanest part of this whole setup. Instead of manually injecting container host/port values into application.properties, Spring Boot reads them directly from the running container and configures the datasource automatically.
@SpringBootTest@Testcontainersclass BackendApplicationTests { @Container @ServiceConnection static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:latest"); @Test void contextLoads() { }}
There is no @DynamicPropertySource, no datasource.url override, no test profile. The @ServiceConnection annotation handles all of it. This is the approach I now use as the default for any new Spring Boot project with Testcontainers.
The pom.xml Dependencies You Need
For completeness, here are the test-scoped dependencies. Spring Boot manages the Testcontainers version through its BOM — note that Spring Boot 4.x targets Testcontainers 2.x, which may not yet be widely available on Maven Central. Pin the version explicitly if you hit resolution errors:
<properties> <testcontainers.version>1.20.4</testcontainers.version></properties><dependencyManagement> <dependencies> <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers-bom</artifactId> <version>${testcontainers.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-testcontainers</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency></dependencies>
Configuration Checklist
If you are debugging a broken setup, work through this list in order:
- Confirm
$DOCKER_HOSTis set in your shell and the socket file exists. - Confirm
docker infoworks without errors from the same terminal you run Maven in. - Create
~/.testcontainers.propertieswithdocker.hostandryuk.disabled=true. - Add the
<environmentVariables>block to the Surefire plugin inpom.xml. - Add
spring.docker.compose.enabled=falsetosrc/test/resources/application.properties. - Run
./mvnw test. The first run pulls the Postgres image — subsequent runs are fast.
Does This Configuration Work in CI?
Yes, with one caveat: the CI environment needs Docker available and DOCKER_HOST set appropriately. On GitHub Actions, the standard ubuntu-latest runner has Docker pre-installed at the default socket path (/var/run/docker.sock), so Testcontainers works out of the box — no Lima-specific configuration needed. The Surefire ${env.DOCKER_HOST} expression will be empty on Linux runners and Testcontainers falls back to the default socket, which is correct.
This means the Surefire configuration is safe to commit. It works on Lima locally and on standard Docker in CI without any branching or profiles.
Key Takeaways
- Two separate problems, two separate fixes. Ryuk failing is solved by
ryuk.disabled=true.DOCKER_HOSTnot being inherited is solved by the Surefire<environmentVariables>block. ~/.testcontainers.propertiesis global — it applies to every project on your machine. Good for the socket path; decide consciously whetherryuk.disabled=trueshould be global for you.@ServiceConnectioneliminates boilerplate — no more@DynamicPropertySourcein every integration test class.- Pinning the Testcontainers version is safer than relying on the Spring Boot BOM when using early releases of Spring Boot major versions.
This post is part of the Fullstack 2026 series, covering a production-ready Spring Boot + React stack from scratch. The next post covers modern state management — how to stop using Redux for things it was never meant for.


Leave a comment