Content Archives¶
A content archive is a single, self-contained, versioned file (.drpca) that packages a content pack — or a plugin provider — together with all of its payload: files, ISOs, extracted directories, and documentation. It is a read-only filesystem image. DRP mounts the archive and serves its payload straight from the image, decompressing files on demand rather than unpacking them up front.
This page explains what content archives are, what is inside one, how DRP serves them, and how they differ from a plain content bundle. It does not cover building or installing one — see the Developer and Operator guides for those.
Lifecycle at a glance¶
A content archive follows one path from authoring to a running endpoint. The payload is packaged once, at build time, and is never expanded again.
flowchart LR
subgraph author["Author"]
direction TB
cm["content model<br/>tasks, templates, profiles"]
pl["payload<br/>files / isos / extracts / docs"]
af["artifacts.yaml"]
end
author -->|"drpcli content_archives build"| drpca[".drpca<br/>read-only filesystem"]
drpca -->|publish| cat["catalog / mirror<br/>+ .meta.yaml + .drpca.sha256"]
cat -->|import| ep["DRP endpoint"]
ep -->|"mount read-only"| serve["served in place over HTTP<br/>(never exploded)"]
The problem: payload stored twice¶
DRP content has two parts that behave very differently:
- the content model — the stages, tasks, templates, and profiles that describe automation, and
- the payload — the large binary artifacts automation consumes: OS ISOs, firmware repositories, and vendor tool bundles.
The payload is the awkward part. It has historically been shipped as loose .tgz files that DRP downloads and then explodes into its file-serving space so the contents can be served over HTTP. On a connected endpoint that is merely redundant. In an air-gapped bundle it is expensive: the bundle keeps both the original .tgz and the exploded directory tree, so a multi-gigabyte payload is stored twice — once compressed, once expanded — doubling the size of the one artifact that has to be physically carried across the air-gap boundary.
Loose payload files bring a second class of problem: they are fragile to handle. Checksums must be tracked out of band (or smuggled into the filename), the explode step is not idempotent and overwrites existing files, and files can be published with the wrong permissions.
Note
The airgap bundle was designed to be both the transport image and the running filesystem. That conflation is why the payload is exploded up front: the installed endpoint expects a fully expanded directory to serve from, and pushing an untar-on-install step onto the operator is not acceptable.
The model: a mounted, read-only filesystem¶
A content archive collapses those two parts into one file. The .drpca is a read-only filesystem image containing both the content model and the payload. DRP mounts it and serves the payload directly out of the mounted image — the large artifacts (ISOs, files, docs) are read in place and never copied. Only artifacts that must exist as real on-disk files, such as extracted trees and plugin binaries, are written out, and then only once; see How DRP mounts and serves it.
The heavy payload therefore exists once — inside the archive — whether the endpoint is connected or air-gapped. Because the same file is both the transport unit and the runtime source, the air-gap duplication disappears and the runtime disk cost drops with it. And because a single file is versioned and checksummed as a unit, the out-of-band checksum tracking and permission drift that loose files invite go away too.
flowchart LR
subgraph old["Loose airgap bundle — payload stored twice"]
direction TB
tgz["payload .tgz<br/>(compressed source)"]
exp["exploded into tftpboot/<br/>(expanded copy, served)"]
tgz -. "kept as well" .-> exp
end
subgraph new["Content archive — payload stored once"]
direction TB
drpca[".drpca<br/>(mounted, served in place)"]
end
old --> new
Anatomy of a content archive¶
Inside, an archive is organized under its own name and version, and holds the content model alongside a set of well-known payload namespaces plus the manifest that describes them:
my-content / 1.2.0 ← identity: name / version
├── content.yaml content model (meta, tasks, templates, …) + artifacts manifest
├── files/ arbitrary files (--files)
├── isos/ OS images (--isos)
├── extracts/ unpacked archive trees (--extracts)
└── docs/ documentation, served at /docs (--docs)
- Content model — the same objects a content bundle carries. An archive is a content bundle plus its payload.
- Payload namespaces —
files/,isos/,extracts/, anddocs/each map to a conventional location in the endpoint's file-serving tree. plugin/— present instead of a content pack when the archive packages a plugin provider (built with--providers): the provider's per-architecture binaries, laid out asplugin/<arch>/<os>/<name>. On install the endpoint extracts the binary for its own platform and registers it as a plugin.- Artifacts manifest — records which artifacts the archive carries, where they belong, and a specific install action per file (see How DRP mounts and serves it). It lives inside
content.yaml; at authoring time you write it ascontent/artifacts.yaml, whichdrpcli contents bundlefolds into the content file. At build time the named artifacts are fetched and embedded, so nothing has to be downloaded when the archive is installed.
Under the hood the .drpca is a single tar image that DRP reads in place — optionally compressed as a seekable zstd stream (readers detect that automatically), with every file carrying its own SHA256 and the archive optionally signed. One .drpca file can also bundle several archives, each under its own name/version.
What an archive can carry¶
"Content archive" is a slight misnomer: the container is not limited to content packs. A single .drpca can package:
- a content pack — the model and its payload (the common case), or
- a plugin provider — the provider's per-architecture binaries, packaged the same way, so a plugin and everything it needs travel as one file.
In both cases the payload namespaces (files/, isos/, extracts/, docs/) are available. This is why the same format that distributes a content pack can also distribute a plugin provider — architects can treat the archive as the universal unit of packaged, self-contained functionality.
How DRP mounts and serves it¶
This is the mechanism that makes "served in place" true, and it is worth understanding precisely — it is what removes the explode step.
Opened, not unpacked¶
When an archive is added to an endpoint, DRP opens the .drpca, memory-maps it, and reads it through a read-only filesystem layer. The image is a tar that DRP reads in place; when it is compressed (a seekable zstd stream), individual files are located through a compression index and decompressed on demand at request time. Either way, a booting machine's HTTP request streams bytes straight out of the archive. Adding an archive registers it; it does not unpack it.
Placed by a manifest, with conventions as the default¶
DRP walks the archive once and, for each file, applies either an explicit action from the archive's artifacts manifest or a default based on which conventional directory the file lives in. There are four actions:
| Action | Meaning | Written to disk? |
|---|---|---|
| Expose | Surface the file at a path, served directly from the archive | No |
| Mount | Treat a nested image (such as an ISO) as a filesystem and make its contents browsable | No |
| Extract | Copy the file out onto the endpoint's file store | Yes — idempotent |
| Link | Alias one path to another | No |
The conventional directories map to these defaults:
files/→ Expose — served in place.isos/→ Mount and Expose — the ISO is served as a raw file and its inner contents are made browsable (undermounts/isos/…), so automation can pull individual files out of an image without unpacking it.docs/→ mounted as one subtree, served at/docs.extracts/→ Extract — copied to the file store, for artifacts that must exist as real files.- plugin binaries → extracted to the provider directory so they can be executed.
What actually touches disk¶
ISOs, files, and docs are served straight from the memory-mapped archive and never copied. Only extracts/ and plugin binaries are materialized to disk — and even then the copy is idempotent: DRP compares a stored checksum and skips the write when the file is already current. That idempotency is the direct fix for the old loose-.tgz behavior, which blindly overwrote files on every install.
The file server is a mount namespace¶
Each mounted archive contributes its files to the endpoint's single file-serving tree, layered over the on-disk file store. It behaves as a mount namespace, not a stack of full layers:
- A request resolves to the most specific (deepest) mount that owns the path, falling back to the on-disk store if none does.
- Directory listings are unioned across every mount that overlaps the path plus the on-disk store; where the same name appears in more than one place, the deeper mount wins.
- If two archives claim the exact same path, the last one mounted wins — the displaced mount is released.
- Two install actions from the same archive cannot target the same path — that collision is rejected at install. Across layers, a mount shadows an on-disk file at the same path: the on-disk copy stays, hidden beneath the mount.
Atomic, reversible install¶
Each archive's mounts are installed as one owner-scoped operation and removed by unmounting that owner, so an archive attaches and detaches cleanly — no explode-then-clean-up. The only state that outlives an unmount is the deliberately extracted files (extracts/, plugin binaries), which are tracked and cleaned up separately. The Operator guide covers the import procedure.
Content model vs. payload — two subsystems
Everything above concerns the payload. An archive's content model — tasks, templates, parameters, boot environments — is loaded by a different subsystem: DRP's in-memory, priority-layered content store, served by the REST API. That store is transactional and rejects conflicting overrides, whereas the file-serving overlay simply lets the last-mounted archive win for an identical path. Both are applied together, in one operation, when an archive is installed.
Versioning and catalog identity¶
An archive's identity is its name plus version. In a catalog it is stored as <name>/<version>.drpca, which lets many versions of the same content coexist side by side:
rebar-catalog/my-content/1.0.0.drpca
rebar-catalog/my-content/1.1.0.drpca
rebar-catalog/my-content/1.2.0.drpca
The catalog groups these by name and resolves the moving targets — tip, stable, and hotfix versions — to concrete releases, so an endpoint can ask for "stable my-content" and get the right file. Each version carries its own metadata and checksum sidecars (see Distribution), so the catalog can list and verify every version without opening a single archive.
On an endpoint, several versions of an archive may be present as sources on disk, but exactly one version of a given archive is active (mounted) at a time. Which version that is comes from the endpoint's version sets — the manager-role mechanism that decides what each endpoint should be running — and switching versions is applied as a single atomic update.
A version set expresses this declaratively. It lists components, and a content-archive component is an entry of type ContentArchive naming the archive and a version — tip, stable, or a pinned vX.Y.Z. When the manager applies a version set to an endpoint, it resolves tip/stable to a concrete catalog version, compares the endpoint's desired components against what is currently installed, and installs or removes archives to converge — each change applied as an atomic upload. That is how a fleet is held to a consistent, declared set of content archives without anyone installing them by hand.
Content archive vs. content bundle¶
A content archive is not a replacement for a content bundle — it is the right choice when the content carries heavy payload.
Content bundle (.json/.yaml) |
Content archive (.drpca) |
|
|---|---|---|
| Carries | The content model, plus artifacts.yaml references to payload |
The content model and the payload itself |
| Payload delivery | Fetched from source URLs when the content is installed | Embedded; nothing is fetched at install |
| Best for | Small content; environments with network access to artifact sources | Large payloads; air-gapped or bandwidth-constrained sites; reproducible, self-contained distribution |
| Distribution unit | A small text file | A single versioned, checksummed file |
Reach for an archive when the payload is large, when the endpoint cannot reach the artifact sources, or when you need one reproducible file that carries everything.
Distribution¶
Archives are published to a catalog — the RackN catalog or a private mirror — alongside two small sidecar files:
- a
.meta.yamlcarrying the archive's metadata, and - a
.drpca.sha256carrying its checksum.
The catalog indexer reads those sidecars to list and verify an archive without downloading the archive itself, which matters when a single .drpca can run to gigabytes. For an air-gapped site, the archive and its two sidecars are mirrored together as a set.
flowchart LR
build["drpcli content_archives build"] --> cat["catalog / mirror"]
cat --> a[".drpca"]
cat --> m[".meta.yaml"]
cat --> s[".drpca.sha256"]
a --> ep["endpoint<br/>mounts & serves"]
m -. "indexed without<br/>downloading .drpca" .-> idx["catalog index"]
s -. verifies .-> idx
Next steps¶
- Build one — the Developer guide walks through creating a content archive from scratch.
- Use one — the Operator guide covers installing an archive and running its content on an endpoint.