Skip to content

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:

Text Only
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 namespacesfiles/, isos/, extracts/, and docs/ 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 as plugin/<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 as content/artifacts.yaml, which drpcli contents bundle folds 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 (under mounts/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:

Text Only
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.yaml carrying the archive's metadata, and
  • a .drpca.sha256 carrying 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 &amp; 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.