Concepts
Package types
margot builds and publishes three package types, each producing a separate OCI artifact.
margo
The Margo application description artifact. Contains the app.yaml descriptor and
optional supporting resources.
- Source:
margo/directory (path set by top-leveldirectoryinmargo.yaml, default:margo) - Output: OCI artifact tagged with the top-level
version artifactType:application/vnd.margo.app.v1+json
Layers and their media types:
| File | Media type |
|---|---|
app.yaml |
application/vnd.margo.app.description.v1+yaml |
resources/icon.png |
application/vnd.margo.app.icon.v1+png |
resources/license.txt |
application/vnd.margo.app.license.v1+plain |
resources/release-notes.md |
application/vnd.margo.app.releaseNotes.v1+markdown |
resources/description.md |
application/vnd.margo.app.descriptionFile.v1+markdown |
Build step: copy source → output dir, render app.yaml from app.yaml.jinja if present,
or copy app.yaml verbatim. Both files present is a hard error.
compose
A Docker Compose deployment component.
- Source:
compose/directory (path set bycompose.directoryinmargo.yaml) - Output:
.tgztarball, OCI artifact tagged with the variant'sversion artifactType:application/vnd.org.margo.component.compose+json- Layer media type:
application/vnd.org.margo.component.compose.tar+gzip
Build step: copy source dir → temp dir (respecting .rsyncignore), apply image
search/replace if declared, pack as <name>-<version>.tgz using pure Python
tarfile — no tar binary required.
Tarball structure: a single top-level <name>/ directory wraps the contents, so
extracting produces <name>/<file>.
quadlet
A systemd Quadlet deployment component.
- Source:
quadlet/directory (path set byquadlet.directoryinmargo.yaml) - Output:
.tgztarball, OCI artifact tagged with the variant'sversion artifactType:application/vnd.org.margo.component.quadlet+json- Layer media type:
application/vnd.org.margo.component.quadlet.tar+gzip
Build step and tarball structure: identical to compose.
Tag naming
All tags are valid OCI tags. The artifact type (margo, compose, quadlet) is encoded
in the OCI artifactType field, not in the tag. Multiple artifacts at different tags can
coexist in the same repository.
Warning
The + character (SemVer build metadata separator) is not valid in OCI tags.
Write versions with + in margo.yaml — margot converts + to _ automatically
when producing OCI tags.
| Type | Tag format | Example |
|---|---|---|
| margo | <version> |
1.3.0 |
| compose (no variants) | <version> |
1.3.0 |
| compose (variant) | <version>_<variant-derived> |
1.3.0_compose-simple |
| quadlet (no variants) | <version> |
1.3.0 |
| quadlet (variant) | <version>_<variant-derived> |
1.3.0_quadlet-simple |
Variant tag derivation: when a variant has no explicit version, it is derived as
<component-version>+<type>-<variant-name> → OCI tag <component-version>_<type>-<variant-name>.
margo.yaml version |
OCI tag |
|---|---|
1.0.0+compose-default |
1.0.0_compose-default |
2.1.0+quadlet-minimal |
2.1.0_quadlet-minimal |
The suffix exists because multiple components and variants commonly share one OCI repository — the
top-level repository field in margo.yaml is inherited by compose and quadlet components and by
every variant unless explicitly overridden at the component or variant level. Within a single
repository, the tag is the only thing that distinguishes artifacts at the registry level: the OCI
artifactType field separates a margo artifact from a compose or quadlet artifact, but it does
not distinguish between different variants of the same type. Without the suffix, pushing
compose variant default at version 1.0.0 and variant simple also at version 1.0.0 into the
same repository would both resolve to tag 1.0.0 — the second push would silently overwrite the
first one's tag. The _compose-default / _compose-simple suffix is what keeps each variant
independently addressable.
Project layout
A Margo application project that margot operates on:
<project-root>/
├── margo.yaml # project descriptor (required)
├── margo/ # margo artifact source (default: margo/)
│ ├── app.yaml # Margo app descriptor — static
│ ├── app.yaml.jinja # Margo app descriptor — Jinja2 template (use one, not both)
│ └── resources/ # optional supporting files
│ ├── icon.png
│ ├── license.txt
│ ├── release-notes.md
│ └── description.md
├── compose/ # compose source (default: compose/)
│ ├── compose.yaml # flat layout — used when no variants declared
│ ├── .rsyncignore # optional ignore patterns (flat layout)
│ ├── default/ # 'default' variant subdir
│ │ ├── compose.yaml
│ │ └── .rsyncignore # optional ignore patterns (per-variant)
│ ├── simple/
│ │ └── compose.yaml
│ └── addon-mosquitto/
│ └── compose.yaml
└── quadlet/ # quadlet source (default: quadlet/)
├── myapp.container # flat layout — used when no variants declared
├── default/
│ └── myapp.container
└── simple/
└── myapp.container
Flat layout vs variants
Flat layout (no variants in margo.yaml): the component directory root is used as
a single artifact, tagged with the component's version.
Variants declared: the variants list in margo.yaml is authoritative — only declared
variants are built. All variant names (including default) map to
<component.directory>/<name>/. There is no implicit root mapping.
.rsyncignore
If .rsyncignore is present in a source dir (or variant subdir), its patterns are applied
during the tree copy step (via Python shutil.copytree). One file per source dir; applies
to that dir only.
SemVer gate
SemVer validation applies to build and push only. pull and fetch retrieve arbitrary
existing artifacts and do not validate the tag format.