> ## Documentation Index
> Fetch the complete documentation index at: https://sitectl.libops.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Compose reconcile contract

> The local sitectl compose up reconcile contract for plugin-owned Compose projects.

export const Compose = () => <Tooltip headline="Compose" tip={<>
        Docker Compose is Docker's tool for defining and running multi-container applications.{" "}
        <a href="https://docs.docker.com/compose/">https://docs.docker.com/compose/</a>.
      </>}>
    <>
      <Icon icon="docker" />
      {" "}
      Compose
    </>
  </Tooltip>;

Core `sitectl compose up` performs a command-scoped reconcile for local contexts owned by an installed plugin. The contract belongs to core `sitectl`, not to an individual app template.

The goal is a smooth first start for setup-only checkouts:

```bash theme={null}
sitectl create app/default --setup-only --path ./app --type local --checkout-source template
sitectl compose up
```

The first `compose up` can initialize missing files, build missing local images, and start the stack. A fresh `sitectl create --setup-only` may already run clone-time init; reconcile checks artifacts before deciding what is still needed. Later calls use a local cache unless the plugin create metadata or image override file changes. Cache entries expire after 7 days so long-lived local checkouts are rechecked periodically.

## Flow

For local plugin-owned contexts, core `sitectl compose up`:

1. Loads the active context and the plugin's default create definition, or the first create definition when none is marked default.
2. Checks the local reconcile cache. The cache key includes host, user, plugin, canonical project directory, `docker-compose.override.yml` fingerprint, and create spec fingerprint. Entries older than 7 days are ignored.
3. If there is no cache hit, inspects desired state:
   * missing or empty `InitArtifacts` make `Initialized=False`
   * missing `InitVolumes` make `Initialized=False`
   * missing local images or build-arg overrides make `ImagesAvailable=False`
   * when explicit metadata is absent, core falls back to `docker compose config --format json` and checks file-backed secrets, named service volumes, and buildable services
4. Runs `DockerComposeInit` when init is needed.
5. Runs `DockerComposeBuild` when images are needed.
6. Runs `DockerComposeUp`.
7. Marks the current host, user, project, override fingerprint, and create-spec fingerprint as checked after the commands succeed.

The cache is marked only after the selected commands complete successfully. If a command fails, the next `sitectl compose up` inspects and retries instead of assuming the project is reconciled.

## Operator Commands

Run reconcile directly when you want to inspect or repair the init/build/up workflow without relying on the automatic `compose up` hook:

```bash theme={null}
sitectl compose reconcile
sitectl compose reconcile --force
sitectl compose reconcile --reset-init
```

`--force` bypasses the cache and reruns build/up. It only reruns init when the declared init state is missing. `--reset-init` removes plugin-declared init artifacts and init volumes first, then runs reconcile.

Use `sitectl compose clean` when local runtime state should be destroyed:

```bash theme={null}
sitectl compose clean
```

`clean` runs `docker compose down -v`, removes plugin-declared init artifacts, and clears the reconcile cache entry. It requires typed confirmation because database volumes, uploaded files stored in named volumes, generated secrets, certificates, and declared env files can be lost.

## Conditions

Reconcile status uses Kubernetes-style condition vocabulary for local facts:

| Condition         | Meaning                                                                                                   |
| ----------------- | --------------------------------------------------------------------------------------------------------- |
| `Initialized`     | Required init files and volumes are present, or fallback Compose checks pass.                             |
| `ImagesAvailable` | Required local images exist, no build policy requires a build, and build-arg overrides have been applied. |
| `Reconciled`      | No init or build work is needed for this project/spec/override state.                                     |

Condition status values are `True` or `False`. `ObservedGeneration` records the create-spec fingerprint that was inspected.

## Kubernetes Analogy Boundary

The vocabulary is deliberately familiar to contributors who have used Kubernetes controllers:

* "reconcile" means compare desired metadata with observed local project state, then run the missing lifecycle steps
* `conditions` report current facts rather than command history
* `ObservedGeneration` records which desired create-spec fingerprint was checked

The analogy stops there. This is not a Kubernetes controller, background control loop, scheduler, or distributed reconciler. It is a synchronous CLI workflow for one local <Compose /> project. There is no API server, watch stream, lease, work queue, leader election, multi-worker retry contract, or Kubernetes-style eventual consistency guarantee.

That boundary is important for plugin authors. Lifecycle commands must be safe to retry because users can rerun `sitectl compose up` after a failure, but core does not provide a persistent controller that will eventually converge the project without another CLI invocation.

## Plugin Authoring Contract

Create definitions that participate in reconcile should follow these rules:

1. Keep `DockerComposeInit`, `DockerComposeBuild`, and `DockerComposeUp` idempotent enough to rerun after partial failure.
2. Make `InitArtifacts` explicit and deterministic. Prefer files such as `.env` and secret files over probing container state.
3. Use `ValueFrom: plugin.InitArtifactValueFromHostUID` when a generated marker file must match the local host user.
4. List named Compose volumes that prove first-start state in `InitVolumes`. Core resolves the actual Docker volume name through `docker compose config`.
5. List locally built images in `Images` with the Compose service name, image reference, and build policy.
6. Use `BuildPolicyIfNotPresent` for normal local images, `BuildPolicyAlways` only when every `compose up` should rebuild, and `BuildPolicyNever` for images that should not trigger the build phase.
7. Avoid global mutable state in lifecycle commands. The same project path, create spec, and override file should produce the same result.
8. Do not make init destructive. Existing application data must survive a repeated init command.
9. Declare app services that require MariaDB with `depends_on: {mariadb: {condition: service_healthy}}` so startup and healthcheck behavior match the template contract.
10. Keep plugin namespace commands app-specific. Shared lifecycle belongs to core `sitectl compose`.

## Image Overrides

Core `sitectl image set` writes `docker-compose.override.yml` for local contexts:

```bash theme={null}
sitectl image set --tag wp=nginx-1.30.3-php84
sitectl image set --image app=ghcr.io/example/app:pr-123
sitectl image set --build-arg app.BASE_IMAGE=libops/app:php84
```

Image overrides affect reconcile:

* an explicit image override for a service means core does not require the plugin's default image to exist locally
* a build-arg override triggers the build phase so the new arguments are applied
* the override file fingerprint is part of the reconcile cache key

Use `--image` or `--build-arg` for app-specific services that are not known to the shared `--tag` map.

Use `sitectl image clear [SERVICE...]` to remove image/build-arg override keys while preserving unrelated local override content such as dev-mode bind mounts or port remaps.
