margo.yaml
margo.yaml is the project descriptor file for a Margo application.
It ancors the project root.
If margo.yaml is absent from the current directory, margot fails with:
margo.yaml not found in current directory. Create it manually.
Use --project-dir to point at a different directory.
Example
apiVersion: v1
id: com-example-myapp
name: myapp
version: "1.0.0"
appVersion: "2.3.1"
description: "Human-readable description of the application"
directory: margo
repository: public.ecr.aws/g2n4p2m7/margo
annotations:
opentelemetry.io/instrumented: "true"
author:
- name: Alice Example
email: alice@example.com
organization:
- name: Example Corp
site: https://example.com
compose:
directory: compose
version: 1.0.0
repository: public.ecr.aws/g2n4p2m7/margo
variants:
- name: default
- name: simple
quadlet:
directory: quadlet
version: 1.0.0
repository: public.ecr.aws/g2n4p2m7/margo
variants:
- name: default
- name: simple
Fields
| Field | Required | Description |
|---|---|---|
apiVersion |
Yes | Config schema version. Currently v1. |
id |
Yes | Margo application identifier. Stable machine identifier — must not change across releases. Used as the base for derived component names and deployment profile IDs. Exposed as manifest.id in templates. |
name |
Yes | Application name. Used in tarball filenames (<name>-<version>.tgz) and OCI title annotation. Exposed as manifest.name in templates. |
version |
Yes | Margo artifact OCI tag and manifest version. Must be a valid OCI tag; SemVer recommended. Exposed as manifest.version in templates. |
appVersion |
No | Version of the deployed application (like Helm's appVersion). Not validated as SemVer. Exposed as manifest.appVersion in templates (empty string if absent). Useful for passing as a parameter to deployment profiles (e.g. image.tag). |
description |
Yes | Short description. Used in OCI description annotation. Exposed as manifest.description in templates. |
directory |
No | Path to the margo artifact source directory. Default: margo. Exposed as manifest.directory in templates. |
repository |
No | Default OCI repository for all artifacts. Overridable per component. Falls back to tool config / CLI / env var. Exposed as manifest.repository in templates. |
annotations |
No | Arbitrary key/value pairs. Exposed as manifest.annotations dict in templates. |
author |
No | List of authors, each with name (optional) and email (optional). Maps to metadata.catalog.author in the Margo spec. |
organization |
No | List of organizations, each with name (required) and site (optional). Maps to metadata.catalog.organization in the Margo spec. |
Components
compose and quadlet declare deployment components — each producing a separate OCI artifact when built.
compose
A Docker Compose deployment artifact. Can be built as a single artifact (flat layout) or as multiple variant artifacts.
quadlet
A systemd Quadlet deployment artifact. Same structure and variant support as compose.
compose / quadlet fields
| Field | Required | Default | Description |
|---|---|---|---|
version |
Yes | — | Base version for the component. Used directly as OCI tag in flat mode; used as the derivation base for variant versions when variants is declared. |
repository |
No | Global repository from tool config / CLI / env |
OCI repository for this component. Overrides the global repository. |
component |
No | <id>-<type> (flat) |
Margo component name (developer-owned). margot computes the default but never overwrites an explicitly set value. |
directory |
No | <type> (i.e. compose or quadlet) |
Path (relative to project root) to the component source directory. |
image |
No | — | {search, replace} block — swaps a dev-local image reference for the target one at build time. See Image search-and-replace. Not available for margo. |
Component name defaults
- Flat (no
variants):{id}-{type}— e.g.com-example-nginx-quadlet - With variants:
{id}-{type}-{variant-name}— e.g.myapp-compose-default
Image search-and-replace
compose and quadlet sources are meant to be runnable as checked in — a developer runs
compose/compose.yaml or quadlet/*.container locally against a dev-local image (e.g. built with
podman build -t localhost/myapp:dev .) before margot ever touches it. At build time, margot swaps that
dev-local reference for the environment-appropriate one — no manual editing of source files, no
drift between what's run locally and what's shipped.
compose:
directory: compose
version: 1.0.0
image:
search: "localhost/myapp:dev"
replace: "public.ecr.aws/g2n4p2m7/myapp:{{ manifest.appVersion }}"
search— a literal string (not a regex), exactly as it appears in the component's source text file(s), e.g.localhost/myapp:dev.replace— a Jinja2 template string, rendered with the same template context used forapp.yaml.jinja.- May be declared at the component level (
compose:/quadlet:) and/or per variant. A variant'simageblock fully overrides the component-level one — it does not merge. - Optional. A component/variant with no
imageblock gets no substitution. - An unmatched
searchstring (declared but not found in any source file) produces a warning, not a hard failure — the same posture as other unresolved placeholders. - An undefined Jinja variable in
replaceis a hard error at build time (fail fast), not a silent empty string — same behavior as an invalidapp.yaml.jinjatemplate. - Not available for
margo—margo/app.yaml(orapp.yaml.jinja) is only ever rendered once at build time and is never run directly, so it usesapp.yaml.jinjadirectly instead.
Per-variant override:
compose:
directory: compose
version: 1.0.0
image:
search: "localhost/myapp:dev"
replace: "public.ecr.aws/g2n4p2m7/myapp:{{ manifest.appVersion }}"
variants:
- name: default # inherits the component-level image block above
- name: addon-mosquitto
image: # fully replaces the component-level block for this variant
search: "localhost/mosquitto:dev"
replace: "public.ecr.aws/g2n4p2m7/mosquitto:{{ manifest.appVersion }}"
See the Image search-and-replace example for a full walkthrough
with margot build output.
Variants
Variants let you ship multiple deployment flavours from a single component. When variants is declared, each entry
maps to a subdirectory under the component's directory:
compose:
directory: compose
version: 2.1.0
variants:
- name: default
- name: minimal
This produces two artifacts built from compose/default/ and compose/minimal/.
Variant fields
| Field | Required | Default | Description |
|---|---|---|---|
name |
Yes | — | Variant name. Maps to <directory>/<name>/ subdirectory. |
version |
No | <component-version>+<type>-<variant-name> |
OCI version for this variant. |
component |
No | <id>-<type>-<variant-name> |
Margo component name for this variant. |
With the example above (version: 2.1.0, compose variants default and minimal):
| Variant | Derived version | OCI tag |
|---|---|---|
| default | 2.1.0+compose-default |
2.1.0_compose-default |
| minimal | 2.1.0+compose-minimal |
2.1.0_compose-minimal |
Rules
name: defaultis a reserved name but not special — it maps to<directory>/default/, a real subdirectory like any other variant.- When
variantsis present, the component-levelversionis used as the derivation base, not as a direct OCI tag. --variant allbuilds every declared variant.--variant NAMEselects one.- Variant names that collide with reserved field names (
directory,repository,variants,version,tag,ref,component) are rejected at parse time with a clear error.
Version strings and OCI tags
Warning
The + character (SemVer build metadata separator) is not valid in OCI tags. Write versions with + in
margo.yaml — margot automatically converts + to _ when producing OCI tags.
For example:
margo.yaml version |
OCI tag pushed |
|---|---|
1.0.0+quadlet |
1.0.0_quadlet |
2.1.0+compose-default |
2.1.0_compose-default |
2.1.0+quadlet-minimal |
2.1.0_quadlet-minimal |
This is the standard way to prevent tag collisions when multiple components share the same repository — append
build metadata (+margo, +quadlet, +compose-<variant>, etc.) to distinguish them.
See also
- App description templating — full Jinja2 template context reference, variable
types, and worked examples (
app.yaml.jinja, variant loops, conditional blocks, and more). - Image search-and-replace example — walkthrough with
margot buildoutput showing theimage.search/image.replacesubstitution in action. - Concepts — Project layout — full directory tree for a Margo project.