DEV Community

Ted Enjtorian
Ted Enjtorian

Posted on

[Release-as-Knowledge-02] Starting from LABEL · R2K Level 1 Identify

The entry mechanism for R2K Level 1 (Identify) ── five lines of Dockerfile that change the support team's world

Release-as-Knowledge (R2K) series · Part 2 / 6


In the last part we watched Mary stitch together an 80%-correct answer in 30 minutes.

From this part onward, we move into the solution.

The first brick is unglamorous: a Dockerfile instruction you've probably written hundreds of times but never taken seriously — LABEL.

This instruction has been around for ten years. Most teams use it to stash a maintainer email and a build date. Used properly, it becomes the foundation of the entire R2K mechanism.


First, naming: this approach is called R2K

Before we get into the technicals, let me close the loop on a concept Part 1 left dangling.

I'm naming the thesis "software release is also knowledge transfer" as Release-as-Knowledge, abbreviated R2K.

It follows the same naming pattern as Infrastructure-as-Code: X-as-Y means "manage X as if it were Y".
IaC manages infrastructure as versionable code.
R2K manages software release as structured knowledge transfer.

R2K is not a tool. Not a product. It's a gradual-adoption ladder with 4 levels:

Level Name Primary mechanism Cost to reach
L1 Identify OCI standard LABEL + dev.releaseasknowledge.* 1–2 weeks
L2 Trust /r2k/ snapshots + index.yaml 2–3 weeks
L3 Understand Change Manifest (Mode A/B) 6–8 weeks
L4 Share OCI Referrers + badge 4 weeks

Like SLSA, each level can be claimed independently. An organization can say "we are R2K Level 2" the same way it says "we are SLSA Level 3".

This part is about R2K Level 1: Identify.


What LABEL is, and why it's underrated

LABEL is a Dockerfile instruction that embeds key-value pairs into the image's config blob.

The simplest form:

LABEL maintainer="ops@yourco.com"
LABEL com.yourco.version="1.0.0"
Enter fullscreen mode Exit fullscreen mode

Once written, the built image carries this metadata. Any tool that can read an image (Docker, Harbor, Trivy, Cosign) can pull it out.

Why is it underrated? Because most teams only use it to store metadata for humans to read, not for machines to query.

Its real power: letting any image scanner pull structured data without pulling the image.

But static LABEL has a problem: version="1.0.0" is hard-coded — every build comes out the same.
What we want is a different git commit, build ID, and test result on every build.

That's where build-arg comes in.


R2K Level 1's three LABEL namespaces

R2K Level 1 attaches two required + one optional namespace of labels onto the image:

Group Namespace Defined by Role
OCI standard org.opencontainers.image.* OCI Image Spec Native support across the container ecosystem
R2K spec dev.releaseasknowledge.* R2K v1 For the R2K toolchain
Vendor extension com.yourco.* (reverse-DNS) Each team Your business fields (case_type, license_modules, …)

Both required namespaces should be present, so that without introducing new tools the image is simultaneously OCI-conventional and R2K-compliant. OCI labels feed the "existing container ecosystem"; R2K labels feed the "R2K toolchain".

Figure 1 · Three LABEL namespaces, three roles


build-arg: making LABEL dynamic

# syntax=docker/dockerfile:1.6
FROM eclipse-temurin:17-jre-alpine

# CI injects these dynamically at build time
ARG APP_VERSION=unknown
ARG GIT_COMMIT=unknown
ARG GIT_BRANCH=unknown
ARG GIT_TAG=
ARG BUILD_TIME=unknown
ARG BUILD_ID=unknown
ARG TEST_PASSED=0
ARG TEST_FAILED=0
ARG CASE_TYPE=standard

# === OCI standard labels (industry convention, scanners all recognize) ===
LABEL org.opencontainers.image.title="payment-service" \
      org.opencontainers.image.version="${APP_VERSION}" \
      org.opencontainers.image.revision="${GIT_COMMIT}" \
      org.opencontainers.image.created="${BUILD_TIME}" \
      org.opencontainers.image.source="https://github.com/yourco/payment-service" \
      org.opencontainers.image.vendor="Your Org" \
      org.opencontainers.image.licenses="Apache-2.0"

# === R2K Level 1 · Identify ===
LABEL dev.releaseasknowledge.version="1.0" \
      dev.releaseasknowledge.level="1" \
      dev.releaseasknowledge.commit="${GIT_COMMIT}" \
      dev.releaseasknowledge.branch="${GIT_BRANCH}" \
      dev.releaseasknowledge.tag="${GIT_TAG}" \
      dev.releaseasknowledge.build-time="${BUILD_TIME}" \
      dev.releaseasknowledge.repo="https://github.com/yourco/payment-service" \
      dev.releaseasknowledge.spec.url="https://enjtorian.github.io/release-as-knowledge/"

# === Vendor extension (reverse-DNS namespace, no collisions with anything else) ===
LABEL com.yourco.psp.case_type="${CASE_TYPE}" \
      com.yourco.psp.build_id="${BUILD_ID}" \
      com.yourco.psp.test_summary="passed=${TEST_PASSED},failed=${TEST_FAILED}"

WORKDIR /app
COPY target/app.jar /app/app.jar
ENTRYPOINT ["java","-jar","/app/app.jar"]
Enter fullscreen mode Exit fullscreen mode

The CI-side build command looks like this:

docker build \
  --build-arg APP_VERSION="2.4.1" \
  --build-arg GIT_COMMIT="$(git rev-parse HEAD)" \
  --build-arg GIT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" \
  --build-arg GIT_TAG="$(git tag --points-at HEAD | paste -sd, -)" \
  --build-arg BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --build-arg BUILD_ID="${CI_BUILD_ID}" \
  --build-arg TEST_PASSED="$(jq '.passed' test-report.json)" \
  --build-arg TEST_FAILED="$(jq '.failed' test-report.json)" \
  --build-arg CASE_TYPE="enterprise" \
  -t harbor.yourco.com/products/payment-service:2.4.1 .
Enter fullscreen mode Exit fullscreen mode

That is the entire R2K Level 1 entry point. 5–15 lines of Dockerfile changes, and a few extra --build-args in CI.

Figure 4 · build-arg → LABEL → scanner end-to-end flow


Naming convention: how the three groups divide labour

Which group does each LABEL key belong to? Simple rule of thumb:

org.opencontainers.image.* — metadata the industry has already defined (version, revision, source, created, title, description, vendor, licenses, authors …).
If OCI standard covers it, use OCI standard — don't invent synonyms. Every scanner recognizes it (Trivy, Grype, Cosign, Harbor's built-in scan).

dev.releaseasknowledge.* — R2K spec's own concerns. For example:

  • version — the R2K spec version this image conforms to
  • level — the self-claimed R2K level (1 / 2 / 3 / 4)
  • commit / branch / tag — the source git triple; commit is required, branch / tag strongly recommended. Multiple tags are comma-separated; omit if there's no tag.
  • build-time — the build time of this R2K manifest (RFC 3339)
  • snapshot.path / snapshot.index — only filled at L2 (see Part 3)
  • diff.mode / diff.from — only filled at L3 (see Part 4)
  • spec.url — the location of the R2K spec document in use

com.yourco.* — vendor business fields the standard doesn't cover: case_type, license_modules, test_summary, build_id.
Use reverse-DNS (like Java package naming) to avoid collision with other organizations' LABEL keys.

R2K Manifesto Principle 4 — "Standards over invention" — manifests here. Three layered namespaces with clean division of labor: don't redo what OCI already has, R2K spec only minds its own business, vendor extensions stay in their own prefix.


Reads are extremely fast: two Harbor v2 API calls

Why is LABEL better than other mechanisms? Because reading is dirt cheap.

Any scanner can pull every LABEL through two Harbor v2 API HTTP calls:

GET /v2/<repo>/manifests/<tag>
└─→ retrieves the config descriptor digest, ~ 2 KB

GET /v2/<repo>/blobs/<config-digest>
└─→ retrieves the image config JSON, which contains the LABEL Map, ~ 5 KB
Enter fullscreen mode Exit fullscreen mode

Total under 10 KB, millisecond response. No image pull, no layer extraction, no heavy operations.

Real numbers:

Single image index scan: ~50 ms
Scanning 1000 images:    ~50 sec
Total bandwidth:         ~7 MB
Enter fullscreen mode Exit fullscreen mode

This cost is so low that running it hourly won't even bruise Harbor.

Compare with the alternative — "pull the whole image just to read metadata":

Single image (150 MB):   ~8 sec
1000 images:             130 minutes
Total bandwidth:         150 GB
Enter fullscreen mode Exit fullscreen mode

A 20000× difference. This is why LABEL should be the first stop for any image inventory.

Figure 2 · Harbor v2 API index scan vs full image pull


90% of queries answered in seconds

Once you have an inventory table, Mary's scenario changes immediately.

She opens the PSP UI, searches "Customer A's payment-service", and sees:

✓ Customer's deployed version: 2.3.4
✓ R2K Level: 2 (image carries snapshots)
✓ Most recent build: 2026-04-28 (commit abc1234)
✓ Test summary: 1247 passed, 0 failed
✓ Case type: enterprise (with retail module + audit module)
✓ Build pipeline: GitLab CI #4231
Enter fullscreen mode Exit fullscreen mode

In under a second, Mary has the data she used to spend 5 minutes stitching together from four systems.

This is not some new query interface — it's a PostgreSQL view plus a React table. The data source is the image itself.
Engineers don't have to maintain extra docs, PMs don't have to update Confluence, support doesn't have to ask anyone — the data is right there, in the image.


What it takes to claim R2K Level 1

To claim R2K Level 1, your production image must:

  • [ ] OCI standard labels complete — at minimum version, revision, created, title, source
  • [ ] R2K spec required labelsdev.releaseasknowledge.{version, level, commit, build-time}, with branch / tag strongly recommended
  • [ ] Vendor extensions use reverse-DNS namespacecom.yourco.* form (if any)
  • [ ] CI injects values via build-arg — not a hard-coded 1.0.0
  • [ ] External scanner reads in seconds — 1000 images should scan in < 1 minute
  • [ ] Inventory persisted — at minimum into a DB / search engine, not just ad-hoc API calls

Tick those six and you are R2K Level 1. You can claim it publicly, write it on the engineering blog, put it in an RFP.

Cost: 1–2 engineering weeks (Dockerfile once, CI once, scanner once).

Figure 3 · R2K Level 1 achievement checklist


Why this is not enough

LABEL has one obvious limit: capacity.

In practice, stuffing in a version, commit, and test summary is fine. Stuffing in a full SBOM or a complete test report is not — a single LABEL should stay under a few hundred bytes, and the whole image config blob shouldn't exceed 100 KB.

So LABEL solves the "identify layer":

  • ✅ "What version is this image?"
  • ✅ "Did the tests fail on this image?"
  • ✅ "What case_type does this image belong to?"
  • ✅ "Which R2K level does this image meet?"

But it cannot solve:

  • ❌ "Give me the full SBOM"
  • ❌ "Give me the OpenAPI spec"
  • ❌ "Give me a snapshot of the DB schema"
  • ❌ "Give me the Change Manifest for this release"

That's what R2K Level 2 (Trust) is for: next part we'll see how /r2k/ snapshots + index.yaml add the deep-query layer.


Closing

LABEL looks unglamorous. But it is the entry point to the entire R2K mechanism.

The vast majority of teams already use LABEL — just not in the right way. OCI standard + R2K spec namespace + reverse-DNS vendor extensions + dynamic build-arg injection — these four moves turn LABEL from "metadata nobody reads" into "the identity layer that answers 90% of queries instantly".

This shift requires no new tooling, no new platform, no boss to convince — just 5–15 lines of Dockerfile changes plus a few extra --build-args in CI.

This is R2K Level 1's promise: smallest change, largest payoff, fastest to land.

LABEL is the table of contents for an image: every image should have one, and every query should look here first.

In the next part we move to R2K Level 2 (Trust): when the data grows, when you need to ship a full SBOM, an OpenAPI spec, a DB schema — LABEL is no longer enough, but OCI has another mechanism: metadata layer, with /r2k/index.yaml as the entry-point manifest.


R2K series navigation

  • Part 1: The Support Engineer's 30 Minutes
  • Part 2: Starting from LABEL — R2K Level 1 Identify ← you are here
  • Part 3: Two-Tier Metadata Pattern + index.yaml — R2K Level 2 Trust
  • Part 4: Change Manifest — R2K Level 3 Understand (Mode A/B)
  • Part 5: Mary's next version — R2K-aware tooling
  • Part 6: The R2K Manifesto — 8 principles

Release-as-Knowledge · R2K · v1 · CC BY 4.0

Full Document : https://enjtorian.github.io/release-as-knowledge/
Official Website : https://www.releaseasknowledge.com/

Top comments (0)