Skip to content

Operations

The CLI is the operational interface for local use and CI. This page shows common workflows. Use gitopsctr COMMAND --help for all flags.

gitopsctr finds the Git repository that contains the current directory. Use --repository PATH or GITOPSCTR_REPOSITORY for another checkout. Terminal output uses color. Redirected output is plain. NO_COLOR=1 disables color and FORCE_COLOR=1 enables it. Machine-readable output is always plain.

Inspect and validate

get is the resource-oriented, read-only inspection command. An Environment is analogous to a Kubernetes namespace: environment-scoped resources require --environment NAME, while -A or --all-environments queries every authored environment.

Representative queries are:

gitopsctr get environments
gitopsctr get all --environment dev
gitopsctr get unit application/deploy --environment dev
gitopsctr get artifacts --environment dev --producer application/image
gitopsctr status --environment dev
gitopsctr validate

The registry supplies the remaining selectors shown by gitopsctr get --help, including Stacks, StackTemplates, Promotions, and Receipts.

Singular and plural selectors are equivalent apart from whether a name is supplied. A named lookup with -A returns every matching resource and includes its Environment, which is useful when names are reused across namespaces:

gitopsctr get unit application/deploy -A

get all is the namespace overview, analogous to kubectl get all. It queries every registry-defined, environment-scoped inspection family and prints one table section per family that has results. This includes Units, Stacks, StackTemplates, Promotions, Receipts, and Artifacts; project-scoped Environments keep their dedicated selector. With -o yaml or -o json, the aggregate is always one provenance-bearing ResourceList, even when it contains zero or one resource.

--environment and -A/--all-environments are mutually exclusive. Project-scoped Environment queries need neither. A collection with no matches succeeds with an empty table; a named lookup with no matches fails and identifies the resource and Environment.

Tables and raw resources

-o table is the default. Tables are operational views rather than stored API documents. -o wide adds identity fences, complete digests, acquisition lineage, and projection details where a resource family defines an expanded view. The Units table joins desired Units with their separately stored Receipts to show OBSERVATION (CURRENT, STALE, MISSING, or N/A) and RECONCILIATION (CLEAN, READY, WAIT, or MATERIALIZED). The Receipts table reports CURRENT, STALE, or ORPHAN relative to the selected desired snapshot. These relationships are derived at read time; a Receipt is not embedded into Unit status.

The built-in views use these columns, with ENVIRONMENT added for -A:

Resource Default columns
Environments NAME, DESIRED, OBSERVED, reconciliation counts
Units NAME, KIND, PARTITION, DESIRED, OBSERVATION, RECONCILIATION, REASON
Stacks NAME, TEMPLATE, short TEMPLATE-DIGEST, PARTITION, active/structural UNITS, OBSERVATION, STATE
StackTemplates NAME, short CONTENT-DIGEST, short SOURCE, PARAMETERS, UNITS, PARTITION, REFERENCES, STATE
Promotions NAME, SOURCE, PARTITION, pinned desired, observed, and specification revisions
Receipts NAME, subject KIND, subject PARTITION, OBSERVATION, ARTIFACTS
Artifacts qualified NAME (unit/artifact), KIND, producer PARTITION, AUTHENTICATION

The default Stack view keeps TEMPLATE and a short TEMPLATE-DIGEST for quick identification; UNITS is the active/structural projection count. In -o wide, Stack TEMPLATE, TEMPLATE-UID, and TEMPLATE-DIGEST are the name/UID/content fences for its selected desired StackTemplate. STRUCTURAL shows the intended projection identity, context, generated Unit kinds, and topology; ACTIVE shows the concrete activated projection and its source projection. TOPOLOGY shows each logical Unit and its dependencies. OBSERVATION is derived from UID-fenced child Units and their separate receipts, so it reports child observation states rather than embedded Stack status. STATE reports the Stack's deletion state.

The default StackTemplate view shortens its content digest and source revision. In -o wide, ACQUISITION reports input, git, or promotion distinctly. It includes the document digest and, for Git and promotion, the requested selector plus the resolved exact revision/lineage. Repository values are shown without credentials. SOURCE reports retained repository and exact revision context, and REFERENCES lists Stacks whose name/UID/content-digest binding selects that template. PARTITION follows the resource's authoritative apply membership; PARAMETERS and UNITS are counts. These relationship and child observation facts are evaluated against the selected desired and observed snapshots at read time.

For Units, DESIRED is the short Git blob identity of that exact persisted Unit document—the same identity used by a Receipt's freshness binding. It is intentionally per-resource rather than repeating the desired snapshot commit on every row.

Every environment-scoped table includes PARTITION. Desired resources resolve it through their UID-fenced ownership chain; Receipts and Artifacts inherit it from the exact desired Unit authenticated by their registered relationships. Unpartitioned and orphaned resources render -.

Use -o yaml or -o json for machine-readable output:

gitopsctr get unit application/deploy --environment dev -o yaml
gitopsctr get unit application/deploy --environment dev -o yaml --as-list
gitopsctr get receipts -A -o json

A named lookup with one match returns the exact persisted resource document. A collection query or get all always returns a schema-versioned inspection envelope, even when it contains zero or one item; a named all-Environment query uses the envelope when it matches multiple resources. Every item contains its generic family/scope/qualified-name address plus Environment, plane, ref, revision, and path provenance alongside the exact document. The envelope is the registered, typed inspection.gitopsctr.io/v1 ResourceList output API; it is not a persisted resource family. Use --as-list with -o yaml or -o json to force this envelope for a named lookup and retain its address and provenance metadata. The option is rejected for table output.

An item's optional inspection object contains state derived while traversing registered relationships; it is never part of the persisted document. For Artifacts, inspection.authentication is CURRENT when the Receipt descriptor, Artifact identity/content bindings, and exact current desired producer all match; STALE when the descriptor is valid for an older desired producer; and ORPHAN when no matching Receipt/current producer relationship authenticates the Artifact. These values do not assert that an external image, bundle, or deployment is reachable or healthy.

Queries accept ref/revision overrides for every plane used by their registry-defined inspection relationships. Artifact and Receipt authentication can therefore select both historical desired and observed snapshots. Explicit ref or revision overrides cannot be combined with -A, because each Environment may resolve different deployment refs.

Each family defines its operator-facing qualifiedName. Roots use one segment, Stack-owned Units and their Receipts use stack/unit, and Artifacts append their local name as unit/artifact. Direct Units remain one segment, so a direct producer's Artifact is unit/artifact. Environment and family stay in the command context; partition is management metadata and never part of the address. Controller-generated storage names are not accepted as aliases for Stack-owned Units.

Artifacts are first-class inspectable resources. The table renders their exact address in NAME, so it can be copied into a named lookup. --producer accepts the producer Unit's qualified name:

$ gitopsctr get artifacts --environment dev --producer application/image
NAME                           KIND             PARTITION    AUTHENTICATION
application/image/containers  ContainerImages  application  CURRENT

Artifact identity and authentication are separate. Inspection follows the registered Receipt-to-Artifact description and Receipt-to-desired-Unit observation before reporting CURRENT; the authenticated Unit supplies PARTITION. The registry composes these addresses from root, child, and mirror rules rather than hardcoding a three-level Artifact identity. Persisted documents retain local names for execution and are stored under the corresponding hierarchical collection path; ResourceList addresses and human output use the qualified form.

The Receipt relationship shortcuts remain available. --artifact NAME prints one typed Artifact resource and --artifacts prints every artifact described by the Receipt:

gitopsctr get receipt application/image --environment dev --artifact containers
gitopsctr get receipt application/image --environment dev --artifacts

status remains the higher-level diagnostic view and includes authored resources that have not yet resolved into persisted desired Units; get units lists persisted desired Units only.

Apply and reconcile

apply is the sole desired-state constructor. It takes explicit authored or canonical desired resources, resolves authored inputs, and atomically publishes the resulting desired snapshot:

gitopsctr apply --environment dev \
  --partition application \
  --file deployment/stack-templates/application.yaml \
  --file deployment/environments/dev/stacks/application.yaml \
  --source-revision HEAD
gitopsctr reconcile --environment dev --unit application/deploy --plan
gitopsctr reconcile --environment dev --unit application/deploy

The optional partition identifies an authoritative apply set. Reapplying partition application means the supplied roots are its complete membership: members omitted from that application begin deletion. Different partitions and unpartitioned roots are untouched. Without --partition, apply updates only the named inputs; an existing root keeps its partition, while a new root is unpartitioned. Owned resources inherit selection through ownerReferences.

Deletion is a two-phase lifecycle. delete and partition omission publish UID-/digest-fenced deletion intent only; they do not run external teardown. Reconcile a deleting Unit or run converge to let the controller process deleting resources child/dependent-first, perform idempotent teardown, record observed evidence, and publish the cleanup commit automatically. A resource whose driver cannot prove teardown remains in the desired tree and is shown as RECONCILIATION: WAIT rather than being removed.

Persisted desired state supplies teardown inputs and Stack projection context. The controller's live Project and Environment configuration remains the trust anchor that identifies the accepted desired ref; desired or candidate documents never authorize their own cleanup effects.

--dry previews the controller-owned Git changes. A no-op application creates no commit. An Environment change gate decides whether a changed candidate is published to the desired ref or offered for review. A reconciliation plan lets the driver inspect its work without applying changes or publishing a receipt; normal reconciliation publishes a receipt only after the driver succeeds.

Without --source-revision, apply reads its explicit documents and source-less configuration from the current worktree. It does not silently substitute HEAD or create a hidden commit. If an inline StackTemplate or authored Unit uses repository-backed spec.source, apply fails and asks for --source-revision <commit>; the selected revision is persisted as the desired template's sourceContext.

--source-revision selects the exact committed snapshot used for repository-backed paths and pins. In that mode, working-tree changes are excluded and apply reports that exclusion. Every -f/--file spelling is first resolved relative to the caller's current working directory; revision-backed apply then maps that path into the selected repository snapshot and rejects stdin or paths outside the repository. Without a source revision, live filesystem input is read as spelled and no hidden HEAD commit is created. Commit the intended content and select that commit explicitly when reproducibility is required.

For local orchestration, converge a unit and its dependencies until clean:

gitopsctr converge --environment dev --unit application --yes

Without --unit or --partition, converge targets every persisted desired Unit. --partition application is selection shorthand for the Units rooted in that partition, including owned descendants. Supplying --file makes converge retain those exact inputs for the invocation and alternate apply with dependency-ordered reconciliation:

gitopsctr converge --environment dev \
  --partition application \
  --file deployment/stack-templates/application.yaml \
  --file deployment/environments/dev/stacks/application.yaml \
  --source-revision HEAD \
  --yes

Without --file, converge reconciles current desired state and can re-project persisted StackTemplate/Stack inputs when new observation evidence unlocks a dynamic reference. No re-application of the original source files is required; provide --file only when intentionally changing authored input. It also progresses deleting resources until the environment is clean, waiting, or failed. A change-gated candidate is not live desired state: it cannot start reconciliation, teardown, or cleanup until it is approved and reaches the environment's desired ref.

Promote and verify

Promotion requires a clean permitted source environment. It combines the target specification with explicitly selected values or artifacts from the reviewed source state:

gitopsctr promote \
  --from-environment dev \
  --to-environment staging \
  --file deployment/stack-templates/application.yaml \
  --file deployment/environments/staging/stacks/application.yaml \
  --partition application \
  --specification-revision SOURCE_SHA
gitopsctr verify --environment staging

Promotion applies the explicit target resources passed with --file; --partition gives that target apply set the same omission-based pruning semantics as ordinary apply. A Stack-only promotion may reuse a retained target StackTemplate only when no authoritative partition selects it. When an authoritative partition selects that template, the target StackTemplate must be supplied explicitly with its inline, fromGit, or fromPromotion mode. The Promotion resource pins three independently selected revisions: the source desired revision, its matching source observed revision, and --specification-revision, which authenticates the target Project/Environment configuration and the exact bytes of the explicit target input files. --source-desired-revision defaults to the source desired-ref head; --specification-revision defaults to HEAD. Pass the latter explicitly when HEAD might have advanced beyond the source revision reviewed in the source environment.

Promotion is a resolution context, not a source-tree copy. A target StackTemplate is reused only from target desired state when no authoritative partition selects the retained template, or supplied explicitly by --file input in its inline, fromGit, or fromPromotion mode. A target StackTemplate owned by an authoritative partition must be supplied explicitly so omission-based pruning remains deterministic. apply rejects fromPromotion; it requires the explicit promote transaction and its pinned source desired revision. fromGit is resolved from its requested ref and can be used where a StackTemplate input is accepted. Field-level fromPromotion values and artifactImports[].fromPromotion are resolved against the pinned source desired and observed revisions, with receipt, producer, artifact, and digest validation before publication.

Repository-backed Unit paths inherit the exact source context retained by the desired StackTemplate unless source.revision selects an exact 40-hex commit. A StackTemplate parameter may provide that revision, allowing independent Stacks to use different commits from the same repository and acquired-ref history. Direct authored Units do not accept this field; they continue to use the operation's --source-revision. The effective revision is resolved before projection, contributes to the structural and desired identity, and is retained under the Stack/template incarnation. The driver inputHash reflects the selected bytes and deliberately excludes the revision value itself, so byte-identical commits have the same input hash while remaining distinct projections. Updating the template context reprojects inheritors; changing one Stack's override does not change another Stack.

The target Environment decides whether promotion is published directly or through a pull-request candidate. After a gated candidate is merged, reconcile or converge the target without a source revision because its specification and inputs are pinned by the merged promotion.

Roll back forward

Choose an ancestral desired revision and publish it as a new forward commit:

gitopsctr rollback \
  --environment prod \
  --to-desired-revision DESIRED_SHA \
  --reason "Incident mitigation"

Repeat --unit for a targeted rollback; omit it for the full desired tree. --dry previews the controller write, and the Environment's change gate controls direct publication versus a reviewed candidate.

For CI orchestration, see the GitHub Action.

CI-driven preview cleanup

Forge identity is provenance only. gitopsctr does not decide whether a pull request or merge request is eligible, and normal desired-state operations do not call GitHub or GitLab.

Trusted PR CI may create, update, and delete a preview Stack with the normal CLI primitives, then run converge against the live desired ref. Convergence performs UID-/revision-fenced teardown and automatic child/dependent-first cleanup. A scheduled CI job may enumerate preview refs or resources by lineage, consult the forge for missed events, and request deletion intent; the deployment-owned job remains responsible for lineage enumeration and forge API calls. This repository does not provide a forge-aware recovery command or watcher.

Verify GitHub merge policy

After configuring branch protection or a merge queue, run the read-only verifier:

python tools/verify_github_policy.py \
  --repository OWNER/REPOSITORY \
  --branch main

It prints versioned JSON and returns non-zero if the branch is unprotected, the GitHub API fails, or the policy is malformed. Deployment repositories that use gated candidates may add --required-check to verify a specific required status context. This repository does not require the candidate-freshness check because its CI does not publish deployed desired state. The verifier does not inspect or change GitHub rulesets or merge-queue settings.