Zero-Config PostgreSQL for Quarkus Tests: How Dev Services Remove the Docker Boilerplate
Quarkus Dev Services start a PostgreSQL container automatically when your datasource has no URL — no Testcontainers boilerplate in dev mode or tests. Here's how it works, how to customize it, and where it breaks.
15 Mar 2026, 10:30 UTC

Every integration test suite eventually accumulates the same plumbing: a Testcontainers @Container field, a static initializer that maps the random port back into the datasource URL, and a comment warning future maintainers not to reorder anything. Quarkus Dev Services delete that plumbing entirely. If your application declares a PostgreSQL datasource but no JDBC URL, Quarkus notices and starts a PostgreSQL container for you — in dev mode and in tests — then wires the connection details in automatically.
What Dev Services actually do
When Quarkus boots and finds the quarkus-jdbc-postgresql extension on the classpath but no quarkus.datasource.jdbc.url configured, it treats that as a signal rather than an error. It starts a PostgreSQL container via Testcontainers (default image postgres:15, overridable with quarkus.datasource.devservices.image-name) and injects the generated URL, username, and password into the datasource configuration. The container stops when the application shuts down, so CI runs don't leak containers.
The same mechanism applies in both JVM and native test modes, and it is deliberately disabled for production builds — a packaged application with no datasource URL fails fast instead of silently starting a database.
A worked example: a test with no setup at all
Assume a standard Maven project with quarkus-hibernate-orm-panache and quarkus-jdbc-postgresql dependencies, and an application.properties that contains only:
quarkus.hibernate-orm.database.generation=drop-and-createNo URL, no credentials, no @Testcontainers. The test:
@QuarkusTest
class FruitResourceTest {
@Inject
EntityManager em;
@Test
@TestTransaction
void persistsAFruit() {
Fruit f = new Fruit();
f.name = "cherry";
f.persist();
assertEquals(1, Fruit.count());
}
}Run it from the project root with ./mvnw test (or start dev mode with ./mvnw quarkus:dev). You need a working Docker daemon and permission to use it — on Linux that typically means your user is in the docker group. In the log you should see a line like Dev Services for the datasource jdbc:postgresql started along with the container image name. The test passes with zero datasource configuration because Quarkus supplied it.
To verify cleanup afterward, run docker ps -a and confirm no leftover PostgreSQL container from the run remains.
When you need to customize the container
The defaults cover the common case, but real projects often need a specific PostgreSQL version, an extension such as PostGIS, or an init script. Dev Services expose properties for this, for example:
quarkus.datasource.devservices.image-name=postgres:16
quarkus.datasource.devservices.port=55432
quarkus.datasource.devservices.init-script-path=import.sqlFixing the port is occasionally useful for sharing a database between dev mode sessions, but it reintroduces a failure mode Dev Services were designed to avoid: port collisions on shared CI agents. Prefer the default random port unless you have a concrete reason. If your customization needs go beyond properties — a custom Dockerfile, sidecar containers, replication topologies — you are usually better off declaring a shared container explicitly and accepting the boilerplate Dev Services were saving you from.
The trade-off to keep in mind
The hard dependency is Docker. Dev Services fail at startup if the daemon is unreachable or the current user lacks permission, which can surprise contributors on locked-down machines or rootless CI environments. The escape hatch is simple: set a real quarkus.datasource.jdbc.url (or set quarkus.devservices.enabled=false) and Quarkus stops trying to manage a container. It is also worth remembering that the convenience applies to dev and test only; production configuration remains entirely your responsibility, which is the correct behavior but occasionally surprises people during a first deployment.
Try it on one test
The lowest-risk way to evaluate this is to pick one existing integration test, delete its Testcontainers setup and any test-scoped datasource URL, and run ./mvnw test. If the suite passes and docker ps -a is clean afterward, you have your answer — and a smaller diff to maintain.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.