Quarkus Dev Services for PostgreSQL: When the Ephemeral Database Is the Right Tool
Quarkus Dev Services auto-provisions PostgreSQL in dev and test via Testcontainers. Learn when to pin the image, make production explicit, and switch to a persistent database.
25 Oct 2026, 07:25 UTC

Quarkus projects often start with a pleasant surprise: you add the PostgreSQL extension, run ./mvnw quarkus:dev, and a database appears without anyone installing Postgres or writing a Docker Compose file. That convenience is Dev Services, and it is easy to treat as a free default. The engineering decision is not whether to use it, but knowing exactly where its boundaries lie—because the same mechanism that removes local setup friction can also hide version drift, break CI pipelines, and erase data you expected to keep.
The useful takeaway: Dev Services is a deliberate, profile-scoped convenience. Pin the container image when reproducibility matters, make production configuration explicit, and switch to a persistent database the moment you need data to survive a restart.
What Dev Services actually does in dev mode
When Quarkus starts in the dev profile and finds no datasource properties, it does not fall back to an embedded database or fail. Instead, it launches a throwaway PostgreSQL container through Testcontainers, a Java library that manages Docker containers programmatically. Before Hibernate ORM or Panache—Quarkus's active-record style ORM layer—initializes, Quarkus injects the generated connection details (quarkus.datasource.jdbc.url, quarkus.datasource.username, and quarkus.datasource.password) as system properties. Your entities and repositories see a normal Java Database Connectivity (JDBC) datasource; they have no idea the database is ephemeral.
Because the container is real PostgreSQL, schema-management tools behave normally. If Flyway or Liquibase is on the classpath, migrations run against the container on startup, so your dev schema matches what you expect in production.
The same mechanism inside @QuarkusTest
Dev Services is not limited to interactive development. In the test profile, a @QuarkusTest triggers the same container lifecycle: the database starts before the test suite and is destroyed afterward. Every run begins with a clean schema, which eliminates cross-test pollution and makes integration tests deterministic from a data perspective.
The catch is reproducibility. The default PostgreSQL image tag shipped with Quarkus can change between Quarkus releases. A test suite that passes locally may behave differently after a framework upgrade because the underlying database version shifted. For CI or long-lived branches, pin the image explicitly:
quarkus.datasource.devservices.image-name=postgres:16
You can also fix the host port with quarkus.datasource.devservices.port, though the random default is usually safer for parallel test execution.
Drawing the production boundary
Dev Services is intentionally unavailable in production builds. Even so, relying on its absence is fragile. Adding explicit %prod properties documents the production connection and makes the boundary unambiguous:
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://prod-db.internal:5432/orders
%prod.quarkus.datasource.username=app_user
%prod.quarkus.datasource.password=${DB_PASSWORD}
With those lines in application.properties, the default dev profile still gets the zero-config container, while prod connects to the real database. This keeps the developer experience intact without leaving production configuration implicit.
A worked example
Suppose you have a Quarkus project with the PostgreSQL and Hibernate ORM (or Panache) extensions, and application.properties contains only the pinned image above. Exact extension coordinates and configuration names can vary by Quarkus release, so check the versions in your project before copying the example. Create a simple Panache entity:
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
@Entity
public class Order extends PanacheEntity {
public String customer;
public String status;
}
Then write an integration test:
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@QuarkusTest
class OrderRepositoryTest {
@Test
void persistsAndCounts() {
Order order = new Order();
order.customer = "acme";
order.status = "new";
order.persist();
assertEquals(1, Order.count());
}
}
Run the test from the project root with:
./mvnw test
This requires a working container runtime (Docker or a compatible socket) and permission for your user to access it. If the Docker socket is unavailable, the test fails before any assertion runs. When it succeeds, inspect the test logs for a Dev Services startup message and a JDBC URL pointing to localhost on a random port—that confirms the container was used rather than an external database.
When the convenience stops paying for itself
Dev Services has three hard limitations that turn it from a shortcut into a liability:
- No container runtime, no database. CI agents without Docker socket access cannot start the ephemeral instance. In that environment, you must supply a real PostgreSQL service or a container-in-container setup.
- Data is ephemeral. Every restart wipes the schema and rows. If you are exploring data locally or debugging a stateful workflow, you need a persistent database, not a throwaway one.
- Unpinned images drift. Without
image-name, the database version is tied to the Quarkus release train, which can surprise you during upgrades.
The practical check is simple: ask whether your data needs to survive the process. If yes, configure a standard datasource. If no, and you have Docker available, Dev Services removes an entire class of setup problems.
An actionable decision
Before your next commit, run through this checklist:
- Is
quarkus.datasource.devservices.image-namepinned for any test suite that runs in CI? - Are
%proddatasource properties present and free of hardcoded secrets? - Does your CI pipeline have access to a container runtime, or does it need a managed PostgreSQL service instead?
- Do you need data to persist across local restarts? If so, switch to an explicit datasource.
Dev Services is one of Quarkus's most effective defaults, but it is a default with edges. Treat those edges as configuration decisions, and the ephemeral database stays a tool that accelerates development instead of a mystery that complicates it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.