Skip to main content
Core performs a command-scoped reconcile before a plain full-stack sitectl compose up for local contexts owned by an installed plugin. The contract belongs to core sitectl, not to an individual app template. An up command with a profile, selected services, or behavior-changing flags such as --wait or --build passes through unchanged; run sitectl compose reconcile explicitly first when that specialized start also needs lifecycle repair. Normalized defaults such as -d, --remove-orphans, and sitectl’s local-daemon --no-build still qualify for automatic reconcile. The goal is a smooth first start for setup-only checkouts:
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. A spec containing BuildPolicyAlways bypasses that cache so downstream Dockerfile, plugin, theme, or module edits are rebuilt immediately. Other cache entries expire after 7 days.

Flow

For local plugin-owned contexts, a plain full-stack 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 unless any declared image uses BuildPolicyAlways. 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. Application plugins use bounded Compose --wait --wait-timeout 600 metadata so reconcile reports success only after services are running or healthy.
  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. Commands such as sitectl compose up --wait, sitectl compose up --profile assistant, and sitectl compose up drupal retain their exact Compose semantics and do not get replaced by plugin lifecycle metadata. Use sitectl compose reconcile before them when init or rebuilding is required. Runtime health has one narrow completed-service exception. Core accepts a container that is exactly exited (0) only when docker compose config --format json shows it is a defined, required dependency of another service with condition: service_completed_successfully. This supports tracked one-shot services such as database-init without treating arbitrary or optional exited jobs as healthy. A missing/nonzero/dead container or an unreadable effective Compose model fails the check.

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:
--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:
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 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 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. DockerComposeUp must include a bounded health wait before the plugin’s ready message can be truthful.
  2. Make InitArtifacts explicit and deterministic. Prefer generated secret or marker 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 when Compose declares a distinct explicit output image: that core can inspect. Use BuildPolicyAlways for a buildable app service with no distinct output image name; it bypasses the reconcile cache and relies on BuildKit’s incremental cache so downstream source edits are not missed. Use 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. Put database root credentials only on a required one-shot initializer. Give the long-running application a scoped account and declare the initializer with condition: service_completed_successfully.
  11. Pin directly consumed Compose images with a readable tag and immutable digest; keep buildable application base selection in BASE_IMAGE.
  12. 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:
Image overrides affect reconcile:
  • an explicit image override for a non-buildable 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
Known buildable application services reject --image; use --tag or --build-arg SERVICE.BASE_IMAGE=.... Reserve --image for non-buildable dependency services. 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.