R2K/ Levels/ Level 1 · Identify

L1 · Identify · 5-line Dockerfile

Starting from LABEL

Stamp the image with an identity — OCI standard + R2K spec + vendor extension. Three namespaces, three roles. Any scanner reads them in seconds.

Cost1–2 eng-weeks
Win90% of queries · instant
StackDockerfile · CI · scanner
  1. Dockerfile · three LABEL namespaces

    Receive with ARG, bake in with LABEL

    Three namespaces, each with its own prefix in the same Dockerfile. Don't hard-code values — every field that changes per build (commit / branch / tag / build-time / version) goes through an ARG that accepts a build-arg.

    Dockerfiledockerfile
    # syntax=docker/dockerfile:1.7
    
    # ─── Build args (injected by CI, decided at build time) ───
    ARG VERSION
    ARG COMMIT_SHA
    ARG GIT_BRANCH
    ARG GIT_TAG=""
    ARG BUILD_TIME
    
    FROM nginx:1.27-alpine
    
    # ─── Group 1 · OCI standard (every container tool understands it) ───
    LABEL org.opencontainers.image.title="acme-api" \
          org.opencontainers.image.version="${VERSION}" \
          org.opencontainers.image.revision="${COMMIT_SHA}" \
          org.opencontainers.image.created="${BUILD_TIME}" \
          org.opencontainers.image.source="https://github.com/acme/acme-api" \
          org.opencontainers.image.vendor="Acme Inc." \
          org.opencontainers.image.licenses="Apache-2.0"
    
    # ─── Group 2 · R2K spec (consumed by the R2K toolchain) ───
    LABEL dev.releaseasknowledge.version="1.0" \
          dev.releaseasknowledge.level="1" \
          dev.releaseasknowledge.commit="${COMMIT_SHA}" \
          dev.releaseasknowledge.branch="${GIT_BRANCH}" \
          dev.releaseasknowledge.tag="${GIT_TAG}" \
          dev.releaseasknowledge.build-time="${BUILD_TIME}" \
          dev.releaseasknowledge.repo="https://github.com/acme/acme-api"
    
    # ─── Group 3 · Vendor extension (reverse-DNS, your own business fields) ───
    LABEL com.acme.case_type="api" \
          com.acme.build_id="${COMMIT_SHA}"
    
    # ─── Whatever you already had ───
    COPY ./dist /usr/share/nginx/html
    EXPOSE 80
    

    Three rules:

    • Use org.opencontainers.image.* for OCI — written for every container tool (Trivy / Cosign / docker inspect read it natively)
    • Use dev.releaseasknowledge.* for R2K — written for the R2K toolchain (CLI / scanner / diff plugin)
    • Use reverse-DNS (com.<yourco>.*) for your own fields — keeps the standard namespace clean
  2. CI · build-arg injection

    Let git thread the build-time facts

    Every "truth" comes from git: commit / branch / tag, plus a build time from date -u. Do not maintain a separate VERSION.txt for humans to bump — it becomes a second source of truth that will eventually disagree with git.

    scripts/build.shbash
    #!/usr/bin/env bash
    set -euo pipefail
    
    # Everything from git — never from a hand-maintained file
    VERSION="${VERSION:-$(git describe --tags --always --dirty)}"
    COMMIT_SHA="$(git rev-parse HEAD)"
    GIT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
    GIT_TAG="$(git tag --points-at HEAD || true)"
    BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
    
    docker build \
      --build-arg VERSION="${VERSION}" \
      --build-arg COMMIT_SHA="${COMMIT_SHA}" \
      --build-arg GIT_BRANCH="${GIT_BRANCH}" \
      --build-arg GIT_TAG="${GIT_TAG}" \
      --build-arg BUILD_TIME="${BUILD_TIME}" \
      -t acme-api:"${VERSION}" \
      -t acme-api:"${COMMIT_SHA:0:12}" \
      .
    

    GitHub Actions equivalent (paste into .github/workflows/build.yml):

    .github/workflows/build.ymlyaml
    name: build
    on: [push]
    
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
            with: { fetch-depth: 0 }   # full git history needed for tags
    
          - name: Compute build args
            id: meta
            run: |
              echo "version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
              echo "commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
              echo "branch=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
              echo "tag=$(git tag --points-at HEAD)" >> $GITHUB_OUTPUT
              echo "build_time=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
    
          - uses: docker/build-push-action@v5
            with:
              context: .
              push: true
              tags: ghcr.io/acme/acme-api:${{ steps.meta.outputs.version }}
              build-args: |
                VERSION=${{ steps.meta.outputs.version }}
                COMMIT_SHA=${{ steps.meta.outputs.commit }}
                GIT_BRANCH=${{ steps.meta.outputs.branch }}
                GIT_TAG=${{ steps.meta.outputs.tag }}
                BUILD_TIME=${{ steps.meta.outputs.build_time }}
    
  3. Verify · scanner reads in milliseconds

    Get every LABEL in 50 ms, no image pull required

    Once the image hits the registry, any tool can pull every LABEL with two HTTP calls, ~7 KB total — no need to pull the full image. Locally, docker inspect is fastest; for production verification use oras / crane straight against the registry.

    Local checkbash
    # See whether every LABEL landed
    $ docker inspect acme-api:1.2.3 \
        --format '{{json .Config.Labels}}' | jq .
    
    # Filter only the R2K labels
    $ docker inspect acme-api:1.2.3 \
        --format '{{json .Config.Labels}}' \
      | jq 'with_entries(select(.key | startswith("dev.releaseasknowledge.")))'
    

    Output (excerpt):

    { "dev.releaseasknowledge.build-time": "2026-05-10T14:32:11Z", "dev.releaseasknowledge.commit": "a1b2c3d4e5f67890abcdef1234567890abcdef12", "dev.releaseasknowledge.branch": "main", "dev.releaseasknowledge.tag": "v1.2.3", "dev.releaseasknowledge.level": "1", "dev.releaseasknowledge.version": "1.0", "dev.releaseasknowledge.repo": "https://github.com/acme/acme-api", "org.opencontainers.image.revision": "a1b2c3d4e5f67890abcdef1234567890abcdef12", "org.opencontainers.image.created": "2026-05-10T14:32:11Z", "org.opencontainers.image.title": "acme-api", "org.opencontainers.image.version": "v1.2.3" }
    Remote read (no image pull required)bash
    # oras pulls the image config straight from the registry (~7 KB · ~50 ms)
    $ oras manifest fetch-config --pretty \
        registry.acme.com/acme-api:1.2.3
    
    # Or with crane
    $ crane config registry.acme.com/acme-api:1.2.3 | jq .config.Labels
    
    # Trivy can sweep an entire registry — ~1 minute for 1000 images
    $ trivy image --format json --list-all-pkgs \
        registry.acme.com/acme-api:1.2.3 \
      | jq .Metadata.ImageConfig.config.Labels
    
Level 1 · Achievement Checklist

Tick all six and you can publicly claim "we are R2K Level 1"

→ Once achieved: publishable in eng blog / citeable in RFP / badge-able in README

Next step

Level 2 · Trust — Snapshot

L1 stamped the image with identity. L2 ships the facts (OpenAPI / DB schema / config / SBOM) into /r2k/, with /r2k/index.yaml as the manifest + sha256 for tamper-evidence.

L2 implementation guide · coming soon