Arconia Dev Services

Arconia Dev Services streamline your development workflow by automatically provisioning external services your application depends on during development and testing. No manual setup is required.

Need a database, message broker, or other infrastructure components for your application? Add the corresponding Dev Service dependency to your project. When you run your application or tests, the required services start automatically as OCI containers and are seamlessly wired into your application. Zero additional code or configuration needed.

Built on Testcontainers and Spring Boot’s Testcontainers support, Arconia Dev Services provide a great developer experience by eliminating the friction of manually setting up and managing services in your development environment.

Dev Services are designed exclusively for development and testing workflows. Declared as development/test-only dependencies, they are excluded from production builds and have zero impact on your production deployments. As an additional safety net against dependency scoping mistakes, Dev Services never activate when the application runs in prod mode.

How It Works

When Arconia Dev Services are detected while the application is running in dev or test mode, the framework:

  1. Starts the container: Launches an OCI container for the required service using Testcontainers.

  2. Configures the connection: Sets up connection properties (URLs, credentials, ports).

  3. Wires the service: Integrates the service into your application context using Spring Boot’s Service Connection mechanism.

  4. Manages the lifecycle: Handles container startup, shutdown, and cleanup.

This happens transparently without requiring any code or configuration. Dev Services take precedence over manually configured properties, ensuring your application always connects to the containerized service during development and testing.

Quick Start

Get started with Arconia Dev Services in three simple steps:

  1. Add the dependency for your required service (e.g., PostgreSQL):

    • Gradle

    • Maven

    dependencies {
        testAndDevelopmentOnly 'io.arconia:arconia-dev-services-postgresql'
    }
    <dependency>
        <groupId>io.arconia</groupId>
        <artifactId>arconia-dev-services-postgresql</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
  2. Run your application as usual:

    • Gradle

    • Maven

    • CLI

    ./gradlew bootRun
    ./mvnw spring-boot:run
    arconia dev
  3. That’s it! The PostgreSQL container starts automatically and your application connects to it. No configuration files, no code changes needed.

Want to verify which Dev Services are running? When running the application in dev mode with Spring Boot Actuator in your classpath, check the /actuator/devservices endpoint (see Actuator).

Using Dev Services

Dev Services are available as separate modules for each supported service. You can find the complete list in the Supported Services section below.

Dev Services providing the same capability (e.g. JDBC databases) are mutually exclusive: if more than one is active at the same time, the application fails at startup with a clear error message pointing at the conflict.

Dev Services are provisioned as OCI containers. Ensure you have a container runtime like Podman or Docker installed and running in your environment.

Service Connections

Your application is automatically configured to connect to Dev Services, which take precedence over manually configured properties. For example, when using the arconia-dev-services-postgresql module, the Dev Service transparently overrides properties like spring.datasource.url, spring.datasource.username, and spring.datasource.password.

The auto-configuration mechanism uses Spring Boot’s Service Connections internally to establish connections between your application and containerized services, eliminating the need for manual property configuration or test-specific setup code.

Lifecycle Management

Arconia instructs Spring Boot to manage the lifecycle of Dev Services as beans in the application context:

  • Startup: Services start when the application context initializes.

  • Shutdown: Services stop when the context closes.

  • Persistence: Services remain available throughout the lifecycle of your application or tests.

When using Spring Boot DevTools, Dev Services are not recreated on restart. They remain running in the background, ensuring a smooth development experience without wait times or state loss.

Configuration Options

Each Dev Service can be customized through Spring Boot configuration properties. All Dev Services support a common set of properties under the arconia.dev.services.<service-name> prefix.

Common Properties

The following properties are available for all Dev Services.

All Dev Services are enabled by default. Other properties have sensible defaults that may vary by service.

Property Description

arconia.dev.services.<service-name>.enabled

Whether the dev service is enabled.

arconia.dev.services.<service-name>.image-name

Full name of the container image used in the dev service.

arconia.dev.services.<service-name>.environment

Environment variables to set in the service.

arconia.dev.services.<service-name>.network-aliases

Network aliases to assign to the dev service container.

arconia.dev.services.<service-name>.port

Fixed port for exposing the primary service port to the host. When it’s 0 (default), a random available port is assigned dynamically.

arconia.dev.services.<service-name>.resources

Resources from the classpath or host filesystem to copy into the container. They can be files or directories that will be copied to the specified destination path inside the container at startup and are immutable (read-only).

arconia.dev.services.<service-name>.reuse

Whether the container used in the dev service is reused across multiple applications and application restarts, relying on the Testcontainers reusable containers feature. Only applicable in dev mode.

arconia.dev.services.<service-name>.shared

Whether the dev service is shared among applications running simultaneously. A shared dev service is discoverable by other applications, and the application connects to an existing shared dev service if available instead of starting a new one. Only applicable in dev mode, and only available for Dev Services that support sharing (see Sharing Dev Services).

arconia.dev.services.<service-name>.startup-timeout

Maximum waiting time for the service to start.

arconia.dev.services.<service-name>.volumes

Files or directories to mount from the host filesystem into the container. They are mounted at the specified destination path inside the container at startup and are mutable (read-write). Changes in either the host or the container will be immediately reflected in the other.

Service-Specific Properties

Individual services provide additional configuration properties. Refer to each service’s documentation for all available configuration options.

Programmatic Customization

Configuration properties cover the most common scenarios. When you need full control over a container (for example, a custom wait strategy, a startup check strategy, extra labels, or additional commands), you can define a DevServiceContainerCustomizer<T> bean. Every customizer is applied to any Dev Service container that is an instance of its generic type T, right after the container is created and before it is started.

For example, to customize the wait strategy of the PostgreSQL Dev Service (using org.testcontainers.postgresql.PostgreSQLContainer):

@Configuration(proxyBeanMethods = false)
public class DevServicesConfiguration {

    @Bean
    DevServiceContainerCustomizer<PostgreSQLContainer> postgresCustomizer() {
        return container -> container.waitingFor(Wait.forListeningPort());
    }

}

A customizer typed on a common supertype (e.g. GenericContainer<?>) applies to all Dev Service containers. When multiple customizers match the same container, they are applied following the semantics of the @Order annotation and Ordered interface.

Customizers reference classes from the Dev Services and Testcontainers dependencies, which are not present in production builds. Define them in a configuration class that is only included at development and test time, consistently with how you scoped the Dev Service dependency itself.

Sharing Dev Services

Dev Services can be shared across multiple applications running simultaneously during development, allowing them to use the same containerized service instead of starting one container each. This is particularly useful for:

  • Distributed systems: Share a message broker (e.g., RabbitMQ, Kafka) or observability platform (e.g. Grafana LGTM) across multiple services.

  • Resource efficiency: Avoid running duplicate containers for the same service, especially for resource-intensive services (e.g., Docling, Ollama).

Dev Services that support sharing (typically services where it makes sense, such as message brokers, observability platforms, and resource-intensive services) provide the arconia.dev.services.<service-name>.shared property to enable/disable it, enabled by default. It works out of the box, with no additional setup required. The property is not available on Dev Services that don’t support sharing.

Every Dev Service container is labeled, making it identifiable across applications (e.g. docker ps --filter label=io.arconia.dev-service.name). When sharing is enabled, the application first looks for a running container that already provides the same shared Dev Service. If one is found, the application connects to it instead of starting a new container. Otherwise, the application starts its own container, which other applications can discover in turn. A shared container is discoverable as soon as it’s running, possibly a few moments before the service inside it is ready to accept connections; client libraries with connection retries (the norm in Spring Boot integrations) absorb that window transparently.

The application that started a shared Dev Service owns its lifecycle: the container is stopped when the owning application stops. Applications that were sharing the container lose access to the service at that point. On the next restart, they start (and own) a fresh container.

A discovered shared Dev Service is adopted as it runs, with the configuration of the application that started it. Application-specific customization (environment variables, resources, image name, DevServiceContainerCustomizer beans) doesn’t apply to containers started by other applications, and any credentials must be the same across the applications sharing the service. If an application needs its own differently-configured service instance, disable sharing for that service with arconia.dev.services.<service-name>.shared=false.

Sharing Dev Services is only supported when running the application in dev mode. When running automated tests, Dev Services are never shared to ensure test isolation, consistency, and reliability.

Sharing and reuse are independent features that can be combined. A container configured for reuse can also be shared: it stays discoverable by other applications, and applications with identical configuration converge on the same reusable container through the Testcontainers reusable containers feature.

Reusing Dev Services Across Restarts

Independently of sharing, a Dev Service container can be kept running across application restarts and executions by setting the arconia.dev.services.<service-name>.reuse property. It relies on the Reusable Containers feature of Testcontainers, which is disabled by default. To enable it:

  1. Create or edit the file ~/.testcontainers.properties in your home directory

  2. Add the following line:

testcontainers.reuse.enable=true

Once enabled, reusable Dev Services will persist across application restarts. Reuse is only applicable in dev mode. It composes with sharing: a reusable Dev Service can also be shared and discovered by other applications.

Reusable containers will not be automatically removed when no longer needed. You must stop and remove them manually using your container runtime (Docker/Podman) when they are no longer required.

Resource Mappings

Dev Services support copying files and directories from your classpath or host filesystem into the container at startup. This can be useful for various scenarios, including for loading configuration files, seeding initialization data, or making static resources available to containerized services.

Resources are copied to the specified destination path inside the container at startup and are never changed on the host after being copied (read-only). This means that any modifications made by the container to these files will not persist or affect the source files on the host.

This strategy is preferred over mounting host directories as volumes, as it avoids potential issues with file permission mismatches, path resolution, and cross-platform compatibility.

Configuration

Resource mappings are configured through the arconia.dev.services.<service-name>.resources property. Each mapping requires two values:

  • Source path: Location of the resource on the classpath or filesystem;

  • Container path: Destination path inside the container.

For example, to copy a configuration file from the classpath into an OpenTelemetry Collector container:

arconia:
  dev:
    services:
      otel-collector:
        resources:
          - source-path: otel-collector-config.yml
            container-path: /etc/otelcol-contrib/config.yaml

Source Path Resolution

The source path supports multiple resolution strategies:

  • Automatic resolution: Without a prefix, Arconia first attempts to locate the resource on the classpath. If not found, it searches the filesystem. For example, config/database.conf will be resolved from the classpath if available, otherwise from the filesystem.

  • Explicit classpath: Use the classpath: prefix to load resources from the classpath. For example, classpath:config/database.conf.

  • Explicit filesystem: Use the file: prefix to load resources from the host filesystem. For example, file:./config/database.conf.

Using explicit prefixes (classpath: or file:) improves clarity and readability, making it clear where the resource is being loaded from. It also avoids ambiguity and potential resolution issues, especially in complex projects with multiple resource locations.

Volume Mappings

Dev Services support binding files and directories from the host filesystem into the container at startup. This can be useful for various scenarios, including for persisting data generated by the containerized service or for sharing files between your host and the container.

Files or directories specified in the volume mappings are mounted from the host filesystem into the container at the specified destination path inside the container at startup and are mutable (read-write). Changes in either the host or the container will be immediately reflected in the other.

Since this provides direct read-write access between the host and container, use this feature with caution. Ensure that the mounted paths are secure and do not expose sensitive data or system files. Furthermore, be aware of potential file permission issues that may arise due to differences between the host and container environments.

It’s recommended to use volume mappings only when necessary. Instead, consider using resource mappings for most use cases where read-only access is sufficient.

Configuration

Volume mappings are configured through the arconia.dev.services.<service-name>.volumes property. Each mapping requires two values:

  • Host path: Location of the file or directory on the host filesystem;

  • Container path: Destination path inside the container.

For example, to mount a configuration directory from the host filesystem into a PostgreSQL container:

arconia:
  dev:
    services:
      postgresql:
        volumes:
          - host-path: ./postgresql/data
            container-path: /var/lib/postgresql/data/pgdata

Networks and Ports

Dev Service containers are connected to the default Docker/Podman network and are accessible from your application via the local host and dynamically assigned ports.

Fixed Ports

By default, Dev Services use random available ports to avoid conflicts. However, you can configure fixed exposed ports when needed. Each service supports the arconia.dev.services.<service-name>.port property to specify a fixed port for exposing the primary service provided by the container.

Some Dev Services may expose multiple ports (e.g., management console ports). In such cases, the Dev Service will offer additional properties to configure fixed ports for those secondary services. For example, the RabbitMQ Dev Service provides the common arconia.dev.services.rabbitmq.port property for the main AMQP port and an additional arconia.dev.services.rabbitmq.management-console-port property for the management UI port.

A common use case for fixed ports is when other tools or services in your development environment need to connect to the Dev Service container using a known port, or for exposing UI consoles through well-known ports. For example, to expose Grafana on port 3000.

Using fixed ports may cause conflicts if another service is already using the port. Consider using fixed ports only when necessary.

Shared Network

This is an advanced, opt-in feature. Most applications never need it: your application always reaches Dev Services over the local host and mapped ports, with no configuration.

Dev Service containers cannot, by default, reach each other by name. When you need container-to-container communication, for example, letting a Docling Dev Service send telemetry to a Grafana LGTM Dev Service, enable the shared network globally with the arconia.dev.services.network.enabled property (disabled by default). When enabled, all Dev Service containers join a common OCI network and are reachable by their service name, so you only need to point the consumer at the target’s service name and internal port:

arconia:
  dev:
    services:
      network:
        enabled: true (1)
      docling:
        environment:
          OTEL_EXPORTER_OTLP_ENDPOINT: http://lgtm:4317 (2)
          DOCLING_SERVE_OTEL_ENABLE_TRACES: "true"
          DOCLING_SERVE_OTEL_ENABLE_OTLP_METRICS: "true"
1 A single global switch attaches every Dev Service container to the shared network.
2 The lgtm hostname is the target’s service name (its default network alias) and 4317 is the container’s internal OTLP/gRPC port, not the host-mapped port. To reach a container by a different hostname, set its network-aliases property; those aliases replace the service-name default.

The shared network is distinct from shared: shared shares the same container across applications (see Sharing Dev Services), while the shared network puts containers on a common OCI network so they can reach each other. They are independent and can be combined. Your application’s service connections (host and mapped ports) are unaffected.

Advanced Scenarios

  • Across applications. By default the network is isolated per application, so containers from different applications don’t reach each other. To connect them (for example, several services reporting to one Grafana LGTM instance), set a shared network name with the arconia.dev.services.network.name property: all applications using the same name join the same OCI network, created on demand and removed on a best-effort basis by the application that created it. Also enable shared on the participating Dev Services so a single instance is reused; otherwise each application starts its own container and their identical aliases collide. This applies in dev mode only.

  • Container reuse. Reuse (reuse) is only effective on a named network: the Testcontainers reuse hash includes the network id, which is stable for a named network but per-JVM for the default isolated one, so a reused container on the isolated shared network never matches (a warning is logged). Set arconia.dev.services.network.name when combining the shared network with reuse.

  • Your own containers. The network is exposed as an injectable Testcontainers Network bean. To let a container you manage yourself reach Dev Services, enable the shared network, inject the bean, and attach your container:

@Bean
GenericContainer<?> daprContainer(Network devServicesNetwork) {
    return new GenericContainer<>("daprio/daprd:latest")
            .withNetwork(devServicesNetwork)
            .withNetworkAliases("dapr");
}

A user-defined Network bean takes precedence over the one Arconia provides (and over arconia.dev.services.network.name).

Startup Sequence

Control how Dev Services start using the spring.testcontainers.beans.startup property. It can be set to parallel or sequential. By default, Dev Services are started sequentially.

Startup Feedback

When a Dev Service starts, Arconia logs a consistent startup message with its resolved links, such as management consoles and telemetry endpoints:

...i.a.d.s.startup  : Dev Service 'lgtm' is ready - Grafana: http://localhost:32895, OTLP/HTTP: http://localhost:32896

The same information is available programmatically through the devservices actuator endpoint (see Actuator), for each service under a links list of id, label, and url.

When a container fails to start, Arconia writes the container’s logs to the application log, so an otherwise opaque startup failure (for example, a startup timeout) can be diagnosed. The level is controlled by the global arconia.dev.services.startup.log-level property (default info). Set it to off to disable the log dump.

Global Configuration

In case you want to disable all Dev Services at once, you can set the arconia.dev.services.enabled property to false.

The arconia.dev.services.network.enabled property (default false) turns on the shared network: when enabled, all Dev Service containers join a common OCI network so they can reach each other by name (see Shared Network). The arconia.dev.services.network.name property sets a stable, cross-application OCI network name for it; when unset (default), each application uses an isolated network.

The arconia.dev.services.startup.log-level property sets the level at which a failing Dev Service container’s logs are written to the application log (see Startup Feedback). It defaults to info; off disables the log dump.

Disabling Dev Services globally overrides individual service settings. If global disabling is set, no Dev Services will start, regardless of their individual enabled properties. If you want to disable only specific services, leave the global setting enabled and configure individual services instead via the arconia.dev.services.<service-name>.enabled property.

Supported Services

Actuator

When running the application in dev mode, a devservices actuator endpoint is auto-configured if Spring Boot Actuator is present in the classpath. This endpoint exposes information about the running Dev Services containers, helping you inspect and debug your development environment.

By default, Spring Boot exposes only the health endpoint over HTTP. To access the devservices endpoint, include it in the web exposure configuration:

management:
  endpoints:
    web:
      exposure:
        include: devservices

Usage

List All Dev Services

Retrieve information about all running Dev Services:

http :8080/actuator/devservices

Response example:

{
  "docling": {
    "name": "docling",
    "description": "Docling Dev Service",
    "origin": "OWNED",
    "containerInfo": {
      "id": "dbbda8272b5dc8d2ccfa48d3723756d767eef727322d9c35e0fd4c44ba60e967",
      "imageName": "ghcr.io/docling-project/docling-serve:latest",
      "exposedPorts": [
        {
          "ip": "0.0.0.0",
          "privatePort": 5001,
          "publicPort": 45525,
          "type": "tcp"
        }
      ]
    }
  },
  "postgresql": {
    "name": "postgresql",
    "description": "PostgreSQL Dev Service",
    "origin": "OWNED",
    "containerInfo": {
      "id": "cbf5a15cf5420eddd2daf0e912256b140c0ad067f9329f425a2ecdaa01008bc5",
      "imageName": "docker.io/pgvector/pgvector:latest",
      "exposedPorts": [
        {
          "ip": "0.0.0.0",
          "privatePort": 5432,
          "publicPort": 36803,
          "type": "tcp"
        }
      ]
    }
  }
}

Get Details for a Specific Dev Service

Retrieve detailed information about a specific Dev Service:

http :8080/actuator/devservices/postgresql

Response example:

{
  "name": "postgresql",
  "description": "PostgreSQL Dev Service",
  "origin": "OWNED",
  "containerInfo": {
    "id": "cbf5a15cf5420eddd2daf0e912256b140c0ad067f9329f425a2ecdaa01008bc5",
    "imageName": "docker.io/pgvector/pgvector:latest",
    "names": [
      "/unwritten_mithrandir"
    ],
    "exposedPorts": [
      {
        "ip": "0.0.0.0",
        "privatePort": 5432,
        "publicPort": 36803,
        "type": "tcp"
      }
    ],
    "labels": {
      "org.testcontainers.lang": "java",
      "org.testcontainers": "true"
    },
    "status": "Up About 67 minutes"
  }
}

The endpoint provides visibility into container IDs, image names, port mappings, and other runtime information useful for troubleshooting and debugging.

The origin field indicates whether the Dev Service runs in a container started by this application (OWNED) or in a shared container discovered from another application (DISCOVERED).

Arconia Dev Services vs. Spring Boot Development-time Services

Arconia Dev Services is a higher-level abstraction built on top of Spring Boot’s Testcontainers support. While both approaches leverage Testcontainers for provisioning services, Arconia Dev Services provides a more streamlined, zero-configuration experience.

Key Differences

The primary distinction is simplicity. Arconia Dev Services eliminates boilerplate code and configuration, allowing you to focus on development rather than infrastructure setup.

  • Arconia Dev Services

  • Spring Boot Development-time Services

dependencies {
    testAndDevelopmentOnly 'io.arconia:arconia-dev-services-postgresql'
}

Run your application normally:

./gradlew bootRun
dependencies {
    testAndDevelopmentOnly 'org.springframework.boot:spring-boot-testcontainers'
    testAndDevelopmentOnly 'org.testcontainers:postgresql'
}

Create a test configuration class:

@TestConfiguration(proxyBeanMethods = false)
public class TestContainersConfiguration {

    @Bean
    @ServiceConnection
    @RestartScope
    PostgreSQLContainer postgresContainer() {
        return new PostgreSQLContainer("postgres:latest");
    }
}

Create a test main class:

public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.from(Application::main)
            .with(TestContainersConfiguration.class)
            .run(args);
    }
}

Run from the test classpath:

./gradlew bootTestRun

Import the Testcontainers configuration in each test class needing the service:

@SpringBootTest
@Import(TestContainersConfiguration.class)
class ApplicationTests {
}

Migration Path

If your project already uses Spring Boot’s Testcontainers support, you can adopt Arconia Dev Services incrementally:

  1. Add Arconia dependencies for services you want to manage automatically.

  2. Keep existing Testcontainers configuration for custom or unsupported services.

  3. Gradually migrate more services to Arconia as needed.

Both approaches work alongside each other, allowing smooth transitions without disrupting your workflow.

Coexistence is per service: once you adopt the Arconia Dev Service for a given service, remove your own @ServiceConnection container definition for that same service. Otherwise, two containers will be started and compete for providing the connection details.

Feature Comparison

Aspect Arconia Dev Services Spring Boot Development-time Services

Dependencies

Single dependency per service.
Example: arconia-dev-services-postgresql.

Multiple dependencies per service.
Example: spring-boot-testcontainers + testcontainers-postgresql.

Code Requirements

Zero additional code required

Requires:
@TestConfiguration class with @Bean methods;
• Test main class with SpringApplication.from();
@ServiceConnection and @RestartScope annotations.

Configuration

Declarative via application.yml properties.
Example: arconia.dev.services.postgresql.startup-timeout=30s.

Programmatic configuration in Java code when defining container beans.

Application Startup

Standard commands:
./gradlew bootRun
./mvnw spring-boot:run
arconia dev

Special test classpath commands:
./gradlew bootTestRun
./mvnw spring-boot:test-run
arconia dev --test

Integration Testing

Automatic service discovery and wiring: no imports or annotations needed.

Requires explicit @Import(TestContainersConfiguration.class) or similar mechanism.

DevTools Live Restart

Automatic persistence across restarts: containers survive application reloads.

Requires @RestartScope annotation on each container bean definition.

Service Sharing

Declarative via properties:
arconia.dev.services.<name>.shared=true.
Running shared containers are discovered automatically across applications.

Manual configuration when defining container beans with withReuse(true).

Customization

Configuration properties for common scenarios.
Example: image-name, port, init-script-paths.
Programmatic control via DevServiceContainerCustomizer beans for advanced scenarios.

Full programmatic control. Direct access to Testcontainers API in bean definitions.

Flexibility

Opinionated with sensible defaults. Configuration properties are available for common use cases.

Maximum flexibility. Full control over container lifecycle and configuration.

Learning Curve

Minimal
Add dependency → Run application

Moderate
Requires understanding of Testcontainers API, Spring Boot integration patterns, and test classpath execution.

Best For

Rapid development, standard use cases, teams seeking simplicity.

Advanced scenarios requiring custom container configuration or specialized setups