Stacks
A Stack is how your standards travel. It packages golden paths (compositions) and, optionally, platform rules (intent presets) as one versioned OCI artifact. Every repository pulls it by reference, like any other dependency, so upgrading a golden path across an organisation is a version bump that each repository reviews as a plan diff, not a migration project.
What is a Stack?
A Stack replaces the legacy orun.yaml / CompositionPackage format.
A Stack is a directory rooted at a stack.yaml manifest (apiVersion: orun.io/v1, kind: Stack). It bundles one or more composition types — each using split-kind authoring — together with metadata and an optional OCI registry target.
my-platform/
├── stack.yaml ← Stack manifest
└── compositions/
├── terraform/
│ ├── composition.yaml ← Composition facade
│ ├── schema.yaml ← ComponentSchema
│ ├── jobs/
│ │ └── terraform-validate.yaml ← JobTemplate
│ └── profiles/
│ ├── terraform-pull-request.yaml ← ExecutionProfile
│ ├── terraform-verify.yaml
│ └── terraform-release.yaml
└── helm-chart/
├── composition.yaml
├── schema.yaml
├── jobs/
│ └── helm-chart-render.yaml
└── profiles/
├── helm-chart-lint-only.yaml
└── helm-chart-verify.yaml
Each composition type uses split-kind authoring: a Composition facade references a ComponentSchema, one or more JobTemplate documents, and ExecutionProfile documents by name.
Auto-discovery
When spec.compositions is omitted from stack.yaml, the packager walks the directory tree and automatically discovers composition documents (composition.yaml, schema.yaml, job templates, and profiles). The composition name is taken from the parent directory:
compositions/terraform/composition.yaml → type "terraform"
compositions/helm-chart/composition.yaml → type "helm-chart"
No path listing is required. Drop new composition subdirectories in and they are included automatically on the next pack or publish.
Stack manifest
apiVersion: orun.io/v1
kind: Stack
metadata:
name: my-platform-stack # unique package name
title: My Platform Stack # human-readable title
version: 1.0.0 # semver
description: Platform compositions for my-platform
owner: my-org
tags:
- terraform
- helm
registry:
host: ghcr.io
namespace: my-org
repository: my-platform-stack
visibility: public # public | private
The registry block is used to infer the OCI publish target when running orun publish without an explicit --ref.
To pin specific files instead of relying on auto-discovery, add spec.compositions:
spec:
compositions:
- path: compositions/terraform/composition.yaml
- path: compositions/helm-chart/composition.yaml
Packaging a Stack
Build a local .tgz archive (no registry required):
orun pack --root ./my-platform
orun pack --root ./my-platform --output dist/my-platform-stack-1.0.0.tgz
Publishing a Stack to an OCI registry
Log in first, then stream-publish directly from the directory. No temp file is written:
orun login ghcr.io
orun publish --root ./my-platform
orun publish reads the registry block from stack.yaml to determine the target. Override with --ref:
orun publish --root ./my-platform --ref ghcr.io/my-org/my-platform-stack:v1.0.0
Dry-run to validate the target without uploading:
orun publish --root ./my-platform --dry-run
OCI artifact layout
Stacks are published as multi-layer OCI artifacts:
| Layer | Media type | Content |
|---|---|---|
| compositions | application/vnd.orun.stack.compositions.layer.v1+tar+gzip | stack.yaml + compositions/ tree |
| examples | application/vnd.orun.stack.examples.layer.v1+tar+gzip | examples/ tree (optional) |
Consumers pull only the compositions layer, avoiding the (potentially large) examples layer.
Using a remote Stack
Reference a published Stack by OCI ref in your intent.yaml:
compositions:
sources:
- name: platform
kind: oci
ref: oci://ghcr.io/my-org/my-platform-stack:v1.0.0
Pin the resolved digest for reproducible, air-gapped builds:
compositions:
sources:
- name: platform
kind: oci
ref: oci://ghcr.io/my-org/my-platform-stack:v1.0.0
digest: sha256:abc123...
orun plan verifies a declared digest: and fails if the source resolves to anything else. Every plan also records the digest each source resolved to, so a moved tag shows up in the plan diff. See versioning and locking.
Using a local Stack
Point at a local directory or archive:
# Local directory (hashed and cached)
compositions:
sources:
- name: platform
kind: dir
path: ./my-platform
# Local archive
compositions:
sources:
- name: platform
kind: archive
path: ./dist/my-platform-stack-1.0.0.tgz
Multiple stacks with precedence
Declare multiple sources and control resolution with precedence or bindings:
compositions:
sources:
- name: core
kind: oci
ref: oci://ghcr.io/my-org/core-stack:v2.0.0
- name: team-overrides
kind: dir
path: ./overrides
resolution:
precedence:
- team-overrides # wins when both export the same type
- core
bindings:
terraform: core # always use terraform from core
Example: orun platform stack
The repository ships a complete example stack at examples/compositions/. It exports eleven composition types covering Terraform, Helm, Cloudflare Workers, Cloudflare Pages, and Turbo monorepo patterns.
The stack.yaml uses auto-discovery (no spec.compositions list) and includes the OCI registry target:
apiVersion: orun.io/v1
kind: Stack
metadata:
name: sumo-ops-orun-platform-stack
version: 0.10.0
description: Packaged compositions for the Sumo Ops Platform Orun repository.
owner: sourceplane
registry:
host: ghcr.io
namespace: sourceplane
repository: sumo-ops-platform-orun-stack
visibility: public
Running orun pack --root examples/compositions discovers and archives all eleven composition types automatically.
Intent presets
A Stack can also publish platform rules (environments, trigger bindings, defaults,
discovery roots) as intent presets, listed under spec.intentPresets in stack.yaml.
Repositories inherit them with extends:. See intent presets for
the preset format and merge rules.