skip to content

What does an OPA bundle contain, and what is the revision in its .manifest for?

level: juniorimportance: must knowfreq 66%

answer

  1. gzipped tarball, pulled not pushed
  2. rego plus data plus one metadata file
  3. manifest holds revision and roots
  4. opaque label, never compared by OPA
  5. echoed in status and decision logs

basics

~20 s

An OPA bundle is a gzipped tar archive of Rego policy, data files and an optional .manifest. The manifest's revision is an opaque label for that build, reported in OPA's status and decision output so you know which policy decided.

solid answer

~50 s

A bundle is how a running OPA gets its policy: the bundle plugin polls an HTTP bundle service on an interval, downloads a gzipped tarball and activates it into the process. Inside the tarball are `.rego` policy files, `data.json`/`data.yaml` files whose position in the archive decides where they land under `data`, and an optional `.manifest` at the archive root. The manifest carries `revision` (an opaque string the publisher chooses), `roots` (the slice of the policy and data namespace this bundle owns) and optional `metadata`. OPA never parses or orders revisions — it activates whatever it just downloaded — so the revision exists for correlation, not for version comparison. It shows up in the status plugin's report and in decision log entries, which is what lets you answer "which policy admitted this request?". Most teams set it to the git commit that built the bundle.

go deeper

for a junior

Be ready to say what an OPA bundle physically is (a gzipped tarball of Rego and data plus a .manifest) and that the revision is an opaque build label, not a version OPA compares.

for a middle

Explain the mechanics: the plugin polls a service, activation replaces content under the bundle's roots, and the revision is echoed in the status report and in decision log entries.

for a senior

Show you use the revision operationally — pin it to a git SHA so a decision record resolves to a commit, and treat the status report's active revision as the ground truth for what is enforcing.

for a principal

Own the convention across teams: what a revision string must contain so an audit question becomes a lookup, and who is accountable when the label and the deployed rules diverge.

## Where policy actually comes from A long-running OPA does not read policy from a git repository, a mounted volume or a ConfigMap at request time. In the bundle model, OPA's bundle plugin is configured with a service URL and a resource path, polls that service on an interval, downloads a **bundle** and **activates** it into the running process. From then on every query is answered out of the in-memory policy and data that activation installed. That single fact drives most of what interviewers ask here: the thing serving decisions is a snapshot the agent pulled at some point in the past, and the interesting questions are about how you know *which* snapshot. ## What is in the tarball A bundle is a gzipped tar archive. It contains: - **`.rego` files** — the policy. Their package declarations, not their file paths, decide where the rules live under `data`. - **data files** — `data.json` or `data.yaml`. Here the *path in the archive* matters: a file at `k8s/registry/data.json` lands at `data.k8s.registry`. - **`.manifest`** — optional, at the root of the archive, and JSON: ```json { "revision": "7f3a1c9-2026-08-14", "roots": ["k8s/deprecated", "k8s/registry"], "metadata": {"built_by": "policy-ci"} } ``` - **`roots`** declares the slice of the document tree this bundle owns. Every policy package and every data path the bundle ships must fall inside one of its roots, and on activation OPA erases what is currently under those roots before writing the new content. If `roots` is omitted it defaults to the empty root `""`, meaning the bundle owns the entire tree. - **`metadata`** is a free-form object OPA carries along without interpreting. ## The revision string `revision` is **opaque**. OPA does not parse it, does not compare it to the one it already has, and has no notion of a newer or older revision. It never refuses a bundle because the revision looks like it went backwards, and it never activates a bundle because the revision looks newer. Activation is driven purely by "the service handed me a new body" — the revision is a label riding along with it. That label is valuable precisely because it is carried through to the outputs: - The **status plugin** reports, per configured bundle, the currently active revision plus the timestamps of the last successful download and activation and the last error. - **Decision log** entries carry the bundle name and the revision that was active when that decision was made. So when someone asks "was the deprecated-API ban actually in force when this Ingress was admitted at 14:02?", the answer is a revision string in a decision record, and that string maps back to a build. This is why the common convention is to set the revision to the git SHA of the policy repository, often with a build timestamp appended: it turns an operational question into a `git show`. What the revision is **not**: it is not a version constraint, not an ordering, and not a statement about who produced the bundle or whether it was tampered with. It is a name. ## Sibling bundle shapes Two variants use the same plumbing: - A **delta bundle** ships only changes to `data` rather than a whole snapshot, so a big data document can be kept fresh cheaply. It carries a patch, not a full tree, and it cannot change policy. - A **discovery bundle** is a bundle whose payload is OPA's *own configuration* — which bundles to fetch, where to send decision logs and status. OPA fetches it first and configures itself from it, which is how a fleet of agents gets reconfigured centrally without a redeploy. It has a revision of its own, exactly like a policy bundle. ## Reading a bundle in an interview If you are handed a manifest, the three things to say out loud are: the revision tells me which build this is and I expect to see it echoed in decision logs; the roots tell me what this bundle is allowed to own and what it will wipe on activation; and neither field makes OPA reject a bundle for being stale, because staleness is something you have to detect yourself from the status report.

  • If OPA never compares revisions, what stops it activating an older bundle the service accidentally republishes?
    Nothing in OPA. The bundle plugin activates whatever body the service returns, whatever its revision says. Preventing a rollback to last month's policy is entirely the publishing side's job — the service decides what to serve. On the agent side all you get is visibility: the status report and decision logs will show the older revision suddenly active, which is why alerting on an unexpected revision is worth the effort.
  • What would you put in the revision string, and why?
    The git commit SHA of the policy repository that produced the bundle, usually with a build timestamp or pipeline run id. It makes the revision in a decision log directly resolvable: given a decision that allowed something it should not have, you can check out that exact commit and read the rule that ran. A hand-incremented number or a date alone loses that link.
  • What is a discovery bundle, and how does it differ from the bundle carrying your rules?
    Same tarball format, different payload: a discovery bundle carries OPA's own configuration — which bundles to download, decision log and status sinks — rather than admission rules. OPA fetches it first and configures its other plugins from it, so a fleet can be repointed or have logging turned on centrally without redeploying the agents. It has its own revision, reported separately.

The revision is a shipping label on a crate, not a version number in a package manager: it tells you which crate arrived, but nothing about it makes the warehouse refuse an older one.

saying these in an interview costs you the question

  • Says OPA compares revisions and rejects older ones
  • Thinks the revision is a checksum OPA verifies
  • Believes OPA reads policy from git at request time
  • Cannot say where the revision is visible at runtime
  • Confuses roots with filesystem paths in the archive

context