1. OverviewIn recent years, Testcontainers has become a go-to library for Java integration testing, enabling us to spin up real containers during our test lifecycle. Such containers represent external dependencies, such as databases, message brokers, and other components. The official library page describes it as “an open source library for providing throwaway, lightweight instances…that run in a Docker container.”At the same time, many teams are exploring or migrating to alternative container solutions. Podman is one of those alternatives, if not the most common. The reason for that is licensing changes or a preference for rootless engines. So the natural question arises: Can we use Podman instead of Docker for Testcontainers in a Java project?In this tutorial, we’ll walk through how to get Testcontainers working with Podman, outline what’s officially supported, show the required configuration, and highlight pitfalls.2. Why Consider Using Podman Instead of Docker?As mentioned, Docker has made a few changes to its licensing in the past, but here are a few more reasons:On Linux, Podman offers a daemonless, rootless container engine that can be simpler and more secure in some environmentsIn CI/CD environments (especially on Linux), running Podman can align with existing infrastructure and reduce reliance on the Docker Engine Teams using Apple Silicon Macs may prefer Podman’s podman machine mode over Docker DesktopThat said, for the Testcontainers-based tests, we need a Docker API-compatible container runtime, as the maintainer states that container runtimes are not actively tested in the main development workflow.3. Configuring Podman for TestcontainersLet’s walk through the steps required to configure Podman so that Testcontainers can use it as a container runtime. The setup presented in this article was tested only on Linux and Mac machines. It may be possible to use the WSL2 Linux distribution for Windows, but it has not been tested.3.1. Installing PodmanFirst, let’s install Podman on our local environments following the instructions on the official page. This will enable us to run a container with the Podman container engine.3.2. Enabling the Podman SocketTestcontainers needs to communicate with the container engine through a socket that implements the Docker API.On Linux, we can enable the socket for our users like this:systemctl --user enable --now podman.socketThen, we can confirm that the socket is active:ls -la /run/user/$UID/podman/podman.sockOn Mac, the socket is available inside the Podman VM. We can check its path using:podman machine startpodman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}'The last command prints the full path to the Podman Docker API socket on your Mac host. We will use that path in the next section to configure DOCKER_HOST.3.3. Setting the Environment VariablesNext, we need to instruct Testcontainers on how to connect to the Podman socket. This can be done by setting the DOCKER_HOST environment variable.On Linux:export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/podman/podman.sockexport TESTCONTAINERS_RYUK_DISABLED=trueOn macOS:export DOCKER_HOST=unix://$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')export TESTCONTAINERS_RYUK_DISABLED=trueBy default, Docker setups use Testcontainers’ helper container, “Ryuk,” to automatically clean up resources (containers and networks). With Podman support, this may not always work (especially in rootless mode or macOS VM scenarios). The workaround is to turn it off using TESTCONTAINERS_RYUK_DISABLED=true.4. Known Limitations & CaveatsIt’s important to highlight the things that may still cause friction when running Testcontainers with Podman:Feature gapsThe official statement is that alternative container runtimes are not actively tested by the Testcontainers team, so “not all features might be available.”Ryuk channel issuesAs mentioned, container cleanup via Ryuk may fail or require privileged mode or disabling. That means potential leftover containers, networks, and volumes, if cleanup is not handled.Socket/permissions issuesIn rootless mode, Podman listens on unix:///run/user/$UID/podman/podman.sock. Our test environment must have permissions to that socket; on MacOS, the VM and host mapping might cause path mismatches.Host networking & mapping quirksBecause Podman uses a different default network mode (for instance, sometimes podman network instead of Docker’s bridge), specific advanced networking configurations (custom networks, aliasing, host port binding) may not work identically to DockerImage pull/short‐name lookupOn some distributions, Podman may prompt for a registry when using short image names (e.g., busybox) unless unqualified-search-registries is configured. If Testcontainers pulls and encounters a prompt, tests may hang.CI/agent environment variabilityIf we build agents that vary (some use Docker, some Podman), we may introduce subtle differences. We must test both paths.Compatibility with third-party extensionsIf you rely on frameworks (for instance, Quarkus Dev Services) that assume Docker, their Podman support may be less mature.In short, yes, we can use Testcontainers with Podman for Java tests, as long as we configure the socket and environment variables and consider the cleanup strategy. So it’s not plug-and-play. We need to test the configuration path early (especially on Mac or ARM) and monitor for leftover containers, socket permissions issues, or feature mismatches.5. Testing SetupWith the environment configured, we can write integration tests just as we do with Docker. For example:@Testvoid whenSettingValue_thenCanGetItBack() { try (RedisContainer redis = new RedisContainer("redis:7-alpine").withExposedPorts(6379)) { redis.start(); String host = redis.getHost(); int port = redis.getFirstMappedPort(); try (Jedis jedis = new Jedis(host, port)) { jedis.set("greeting", "hello"); String value = jedis.get("greeting"); Assertions.assertEquals("hello", value); } }}This test is a simple integration test using a Redis container, where the test only sets a value and retrieves it using the Jedis APIs. This is a straightforward test, but it is enough to confirm our Podman setup.Next, let’s also use another very common database, which is frequently used on Java projects.@Testvoid whenQueryingDatabase_thenReturnsOne() throws Exception { try (MySQLContainer mysql = new MySQLContainer("mysql:8.4")) { mysql.start(); try (Connection conn = DriverManager .getConnection(mysql.getJdbcUrl(), mysql.getUsername(), mysql.getPassword()); Statement st = conn.createStatement()) { st.execute("CREATE TABLE t(id INT PRIMARY KEY)"); st.execute("INSERT INTO t VALUES (1)"); ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM t"); rs.next(); Assertions.assertEquals(1, rs.getInt(1)); } }}Finally, let’s use a message broker with a setup a bit more complicated but still using a single container.@Testvoid whenProducingMessage_thenConsumerReceivesIt() { DockerImageName image = DockerImageName.parse("confluentinc/cp-kafka:7.6.1"); try (KafkaContainer kafka = new KafkaContainer(image)) { kafka.start(); String bootstrap = kafka.getBootstrapServers(); String topic = "hello"; Properties prodProps = new Properties(); prodProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrap); prodProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); prodProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); try (KafkaProducer producer = new KafkaProducer(prodProps)) { producer.send(new ProducerRecord(topic, "key", "hello")).get(); } catch (Exception e) { throw new RuntimeException(e); } Properties consProps = new Properties(); consProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrap); consProps.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group"); consProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); consProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); consProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); try (KafkaConsumer consumer = new KafkaConsumer(consProps)) { consumer.subscribe(Collections.singletonList(topic)); ConsumerRecords records = consumer.poll(Duration.ofSeconds(10)); ConsumerRecord first = records.iterator().next(); Assertions.assertEquals("hello", first.value()); } }}Using these integration tests, we can confirm the container’s compatibility, verify that our Podman setup is functional, and enable us to utilize a new runtime engine.6. ConclusionIn this article, we provide a clear path to adopting Podman with Testcontainers in our Java projects and help us avoid the common pitfalls many developers encounter.Switching the container runtime from Docker to Podman for Testcontainers in Java isn’t just about replacing an alias. It requires awareness of socket configuration, cleanup mechanisms, permissions, and underlying differences. But with the correct setup, we can be confident our integration tests run reliably, regardless of the container engine.The post Configuring Testcontainers to Work with Podman first appeared on Baeldung.