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"
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".
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"]
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 .
That is the entire R2K Level 1 entry point. 5–15 lines of Dockerfile changes, and a few extra --build-args in CI.
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;commitis required,branch/tagstrongly 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
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
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
A 20000× difference. This is why LABEL should be the first stop for any image inventory.
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
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 labels —
dev.releaseasknowledge.{version, level, commit, build-time}, withbranch/tagstrongly recommended - [ ] Vendor extensions use reverse-DNS namespace —
com.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).
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)