---
title: "Why Compose is generated"
summary: "Compose is an artifact Onebox generates, not an input it reads. What that buys, and how you get out."
description: "Why Onebox derives the runtime from your declaration."
status: shipped
read_when:
  - "Wondering why you cannot hand Onebox a docker-compose.yml as the contract"
---
Onebox does not read a Compose file you wrote. The contract is a declaration of
what the application *is*; the Compose file, the container names and the routing
labels are all derived from it.

## Why the runtime is not yours to hand over

Under the [ownership boundary](/docs/concepts/ownership-boundary), Onebox owns the
runtime. Reading a runtime you wrote inverts that: you own it, and Onebox merely
operates something it did not design and cannot fully reason about.

The Compose file and Onebox annotations could disagree, with nothing to
arbitrate between them.

Onebox could not choose the container name, the volume name,
the router rule or the network, because you already would have. So it could not
guarantee they are consistent, collision-free, or stable across releases.

`strategy: rolling` needs a health check to gate it. Reading a health check from
an authored file would leave Onebox unable to guarantee that the rollout is gated.

## What generation buys

- **Derived, stable names.** Workload containers use the uniform
  `<app>-<component>-<replica>` grammar, such as `shop-web-1`; managed services
  are `onebox-<service>`, such as `onebox-postgres`; persistent and
  provider resources include `shop_default`, `onebox_postgres_data`, `onebox_services`,
  and `onebox-ingress`. The application and service networks are declared external
  and created under Onebox's ownership fence, so Compose cannot remove a live
  shared network during release teardown. A full `ob destroy --volumes` removes
  them; if an unmanaged endpoint remains attached, destruction stops without
  deleting Onebox's recovery state or releasing host ownership. Once a resource
  exists its name cannot change without migration, and a foreign resource already
  holding a derived name is refused, not adopted.
- **Digest binding.** The rendered Compose is bound into the plan, so what was
  reviewed is what executes.
- **Refusals that mean something.** `strategy_ungated`, `route_collision`,
  `stateful_replicas` are all statements about the generated runtime, which is
  the only thing that will actually run.
- **Typed routing.** Two workloads claiming the same entrypoint, protocol, hostname
  and path produce `route_collision`. This prevents the proxy from accepting both
  and routing to one without saying which.

## A workload name is its identity

Changing a workload's key is a rename in the declaration but a replacement at
runtime. Onebox starts and verifies the new workload, activates the release, and
only then drains and removes the old workload. This ordering keeps a route rename
from stopping the old endpoint before the new one can serve.

Persistent volumes follow the same identity rule: a renamed workload receives
new derived volume names. Onebox does not guess that data should move between
them. Move or reattach persistent data explicitly before treating a workload
rename as complete.

## You can still see it, and still leave

Generation is only acceptable because the output is inspectable and the exit is a
command:

```sh
ob preview    # print the generated runtime, change nothing
ob eject      # write it out and hand it over, permanently
```

See [Eject](/docs/guides/eject) for the output and what remains on the host.

## And the escape hatch remains

A container Onebox cannot describe is adopted verbatim:

```yaml
workloads:
  legacy:
    role: Daemon
    compose: docker-compose.yml#legacy
```

Refusals apply only where the referenced service contradicts something Onebox
owns: `extends`, a fixed `container_name`, `network_mode`, labels in the `onebox.`
or `traefik.` namespaces. See [Adopt an existing Compose file](/docs/guides/compose).

The contract generates by default and defers on request.