R2K/ Levels/ Level 1 · Identify

L1 · Identify · 5 行 Dockerfile

LABEL 開始

把 image 標上身份 — OCI 標準 + R2K 規格 + Vendor 擴充,三個 namespace 各司其職,任何 scanner 秒掃。

Cost1–2 工程週
Win90% 查詢秒回
StackDockerfile · CI · scanner
  1. Dockerfile · 三組 LABEL

    ARG 接收,用 LABEL 烘進 image

    三組 namespace,在同一個 Dockerfile 內各佔一個 prefix。不要寫死值 — 所有「會變」的欄位(commit / branch / tag / build-time / version)都用 ARG 接 build-arg。

    Dockerfiledockerfile
    # syntax=docker/dockerfile:1.7
    
    # ─── Build args (CI 端注入,build 當下決定) ───
    ARG VERSION
    ARG COMMIT_SHA
    ARG GIT_BRANCH
    ARG GIT_TAG=""
    ARG BUILD_TIME
    
    FROM nginx:1.27-alpine
    
    # ─── Group 1 · OCI standard (任何工具都讀得懂) ───
    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 (R2K 工具鏈專用) ───
    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 (反向域名,自家業務) ───
    LABEL com.acme.case_type="api" \
          com.acme.build_id="${COMMIT_SHA}"
    
    # ─── 你原本就有的東西 ───
    COPY ./dist /usr/share/nginx/html
    EXPOSE 80
    

    三條原則:

    • OCI 用 org.opencontainers.image.* — 寫給所有 container 工具看(Trivy / Cosign / docker inspect 原生支援)
    • R2K 用 dev.releaseasknowledge.* — 寫給 R2K 工具鏈讀(CLI / scanner / diff plugin)
    • 自家欄位用反向域名com.<yourco>.*)— 不汙染標準 namespace
  2. CI · build-arg 注入

    git 串成 build 那一刻的事實

    所有的「真相」都來自 git:commit / branch / tag, 加上 date -u 的 build time。 不要另外維護一個 VERSION.txt 讓人手動改 — 那會變成第二份 source of truth,終究會跟 git 不一致。

    scripts/build.shbash
    #!/usr/bin/env bash
    set -euo pipefail
    
    # 一切從 git 來,不從手動維護的檔案來
    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 版本(直接抄進 .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 }   # 需要完整 git history 才有 tag
    
          - 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 秒讀

    不需要 pull 整顆 image,50 ms 就拿到全部 LABEL

    Image 一推上 registry,任何工具都能用 兩個 HTTP call、~7 KB 讀完所有 LABEL — 不必 pull 完整 image。 本機驗的時候 docker inspect 最快,正式驗的時候用 oras / crane 直接讀 registry。

    本機檢查bash
    # 看所有 LABEL 是否落地
    $ docker inspect acme-api:1.2.3 \
        --format '{{json .Config.Labels}}' | jq .
    
    # 只挑 R2K 的 LABEL
    $ docker inspect acme-api:1.2.3 \
        --format '{{json .Config.Labels}}' \
      | jq 'with_entries(select(.key | startswith("dev.releaseasknowledge.")))'
    

    輸出(節錄):

    { "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" }
    遠端讀(不需要 pull image)bash
    # oras 直接從 registry 讀 image config (~7 KB · ~50 ms)
    $ oras manifest fetch-config --pretty \
        registry.acme.com/acme-api:1.2.3
    
    # 或用 crane
    $ crane config registry.acme.com/acme-api:1.2.3 | jq .config.Labels
    
    # Trivy 一行掃完整個 registry,1000 image 大約 1 分鐘
    $ trivy image --format json --list-all-pkgs \
        registry.acme.com/acme-api:1.2.3 \
      | jq .Metadata.ImageConfig.config.Labels
    
Level 1 · 達成 Checklist

勾完這 6 項,你就可以對外宣告「我們是 R2K Level 1」

→ 達成後可寫進工程 blog / 放進 RFP / 在 README 掛 R2K Level 1 badge

Next · 下一步

Level 2 · Trust — Snapshot

L1 把身份掛上去了,L2 把事實(OpenAPI / DB schema / config / SBOM)寫進 /r2k/ , 用 /r2k/index.yaml 列表 + sha256 防竄改。

L2 實作範例 · coming soon