Operate

Compose Attachments

How attachments.compose works, how it differs from compose:up, and how to keep workload containers and Compose-managed services on the same truthful network boundary.

learnmaintainersintermediatestable2026-04-29

Why this feature exists

attachments.compose lets an Ota-managed workload container join one or more Compose project networks without forcing Docker to run inside the app image.

That is the right model when Compose stays on the host control plane, but app tasks should still reach Compose-managed services by service name from inside their own container context.

  • Compose is still started and stopped from the host
  • the workload task still runs in its own Ota-managed container context
  • the workload container joins the Compose network namespace declared by the attachment
  • service reachability becomes truthful from the workload plane instead of being faked through host ports or Docker-in-Docker

What it is and what it is not

  • attachments.compose is a network attachment declaration for container contexts
  • it is not a replacement for compose:up or a service manager declaration
  • it does not start Compose services by itself
  • it does not mean the task runs inside the Compose service container

Use attachments.compose on the workload context when that workload container should join the Compose project network.

Compose attachment excerptyaml
execution:  default_context: app  contexts:    host:      backend: native      requirements:        tools:          docker: "*"    app:      backend: container      lifecycle: persistent      container:        image: maven:3.9.14-eclipse-temurin-21-noble      attachments:        compose:          - qredex-core

The most important alignment rule

Keep attachments.compose aligned with the Compose project name used by the related service managers.

If those names drift, Ota attaches the workload container to the wrong network family and service-name reachability breaks.

The workload context and the typed Compose service manager both use the same project name: qredex-core.

Aligned namesyaml
execution:  contexts:    app:      backend: container      attachments:        compose:          - qredex-core services:  postgres:    manager:      kind: compose      name: qredex-core      file: docker-compose.yml      service: postgres

How it differs from compose:up

compose:up is a host task that controls the Compose project lifecycle.

attachments.compose is a workload-context declaration that tells Ota which Compose network family the workload container should join.

  • use compose:up when the host should start the service stack
  • use attachments.compose when the app container should join that stack's network
  • use both together when the repo truthfully splits host orchestration from container workload execution

This is the common split: host tasks own Compose commands, app tasks run in a container context that joins the Compose network.

Host control plus attached workloadyaml
tasks:  compose:up:    context: host    run: docker compose up -d postgres   compose:down:    context: host    run: docker compose down -v   db:integration:    context: app    requires_services:      - postgres    run: mvn -B -Dgroups=db-integration test

Canonical use case

This is the qredex-core shape: Compose owns Postgres on the host, while the Java app and tests run in an Ota-managed container that should reach postgres:5432 directly.

The goal is honest network topology, not a flattened host-port workaround.

The workload reaches Postgres by service name from inside the attached container context.

Compose-backed app contextyaml
version: 1project:  name: qredex-core execution:  default_context: app  contexts:    host:      backend: native      requirements:        tools:          docker: "*"    app:      backend: container      lifecycle: persistent      container:        image: maven:3.9.14-eclipse-temurin-21-noble      attachments:        compose:          - qredex-core services:  postgres:    manager:      kind: compose      name: qredex-core      file: docker-compose.yml      service: postgres    endpoints:      app:        address: postgres        port: 5432    readiness:      from: app      run: pg_isready -h postgres -p 5432

When to use it

  • use it when Compose services should be controlled from the host but reached by service name from a container workload context
  • use it when the repo should stay honest about host orchestration versus workload execution
  • use it when the right answer is a shared network boundary, not a guessed host alias or published port workaround

What not to do

  • do not use Docker inside the app image as the main model
  • do not treat published host ports as the primary in-container reachability path when service-name networking is the real topology
  • do not let attachments.compose and services.<name>.manager.name drift apart
  • do not confuse Compose attachments with shared backends; one is network attachment, the other is backend-boundary reuse