docs: codify Convention B naming rule and close prior decision plans

Adds "Naming convention" section to README documenting Convention B
(<service>:<version>) as the repo standard, replacing prior Convention A
brainstorms. Incorporates completed scribe OCI label pattern. Closes
blocking dependency on 260528-1110 naming-decision plan.

Stages completed prior planning artifacts for historical record.
This commit is contained in:
2026-05-28 13:44:37 +07:00
parent 4ef74f3317
commit 2f7bfde836
16 changed files with 1363 additions and 3 deletions
+17 -3
View File
@@ -6,16 +6,30 @@ Custom Docker images of legacy/discontinued software — built and published to
| Image | Tag | GHCR package | Notes |
|-------|-----|-------------|-------|
| Couchbase 2.5 | `2.5.2` | [couchbase-2.5](https://github.com/tiennm99/docker-images/pkgs/container/couchbase-2.5) | Legacy NoSQL; no official image exists |
| Couchbase 2.5 | `2.5.2` | [couchbase](https://github.com/tiennm99/docker-images/pkgs/container/couchbase) | Legacy NoSQL; no official image exists |
| Scribe 2.2 | `2.2` | [scribe](https://github.com/tiennm99/docker-images/pkgs/container/scribe) | Facebook's legacy log aggregation daemon |
## Naming convention
This repo uses Docker-Hub-style image names: `<service>:<full-version>`.
- Subdirectory: `<service>-<major-line>/` (e.g. `couchbase-2.5/`, `scribe-2.2/`). Subdir holds the build context for one major version line.
- GHCR + Docker Hub image: `<service>` (e.g. `couchbase`, `scribe`). No major-line suffix in the image name.
- Workflow file: `publish-<service>-<major-line>.yml` (matches subdir for path-trigger clarity).
- Tags published per workflow:
- `<full-version>` (e.g. `2.5.2`, `2.2`)
- `<major-line>` (e.g. `2.5`) - optional alias
- `latest` - canonical build of the only major line this repo carries for the service.
If a second major line of the same service is ever added to this repo, drop `latest` from both workflows and publish `<major-line>-latest` tags instead.
## Couchbase-2.5
```bash
docker pull ghcr.io/tiennm99/couchbase-2.5:2.5.2
docker pull ghcr.io/tiennm99/couchbase:2.5.2
```
More options: https://github.com/tiennm99/docker-images/pkgs/container/couchbase-2.5
More options: https://github.com/tiennm99/docker-images/pkgs/container/couchbase
Docker Compose example: [couchbase-2.5/docker-compose.example.yml](couchbase-2.5/docker-compose.example.yml)
@@ -0,0 +1,50 @@
---
status: superseded
supersededBy: 260528-1324-rename-couchbase-to-convention-b
---
# Image Naming Convention - Decision Parked
> **Superseded by [260528-1324-rename-couchbase-to-convention-b](../260528-1324-rename-couchbase-to-convention-b/plan.md) on 2026-05-28.** User chose Convention B; see superseding plan for rationale.
Status: **SUPERSEDED** - Convention B locked in via 260528-1324
Created: 2026-05-28 11:10
Owner: tiennm99
## Question
How should this repo name its Docker images?
- Convention A (current for 3/4): `<service>-<major-line>:<full-version>` e.g. `couchbase-2.5:2.5.2`
- Convention B (current for scribe): `<service>:<full-version>` e.g. `scribe:2.2` (Docker-Hub-official style)
## Context
- 4 images total at brainstorm time: `couchbase-2.5`, `gradle-8`, `oraclejdk-8`, `scribe`
- `gradle-8` and `oraclejdk-8` dropped 2026-05-28 (this commit) - decision now only spans `couchbase-2.5` + scribe
- scribe diverges from the rest
- Subdir already named `scribe-2.2/` regardless of which convention wins
## Recommendation (from brainstorm)
**Convention A**, rename scribe to `scribe-2.2`. Decisive arguments:
1. `latest` semantics break under B for archived software (newest-wins misleads users searching for legacy versions)
2. Attestation lineage cleaner per-line under A
3. Docker-official `python:3.x` analogy doesn't transfer - those are sliding-window builds; this repo is a museum of frozen artifacts
4. Migration A = ~5 edits; B = ~15 edits + orphan-package cleanup + new `latest` policy
See reports:
- `plans/reports/brainstorm-260528-1007-image-naming-convention-report.md` (initial)
- `plans/reports/brainstorm-260528-1015-image-naming-convention-reframe-report.md` (re-frame, stronger reasoning, same conclusion)
## Decisions pending
1. **Approve Convention A?** If yes: rename scribe to `scribe-2.2` (~5 edits).
2. **Codify rule** in README + CLAUDE.md as part of same change, or separate task?
3. **Orphan packages** post-rename: delete `ghcr.io/tiennm99/scribe` + Docker Hub counterpart, or tombstone in README?
## When to revisit
- Before adding a new image (so the new image follows the chosen convention)
- Or sooner if user wants to lock the rule down
@@ -0,0 +1,165 @@
# Phase 01 — Dockerfile + Workflow Labels
**Status:** pending
**Effort:** ~20m
## Context Links
- Research report: `plans/reports/researcher-260528-1221-scribe-oci-labels-license-verification-report.md`
- metadata-action docs: https://github.com/docker/metadata-action (auto-injects 8 OCI labels by default; `labels:` input overrides them)
## Key findings driving the design
1. `docker/metadata-action@9ec57ed` (workflow line 40) auto-injects: `title`, `description`, `url`, `source`, `version`, `created`, `revision`, `licenses` — values derived from THIS repo's GitHub metadata.
2. CLI `--label` (which `docker/build-push-action` uses via `labels: ${{ steps.meta.outputs.labels }}`) OVERRIDES Dockerfile `LABEL` instructions at build time. So workflow builds today shadow the Dockerfile's labels for `version`, `description`, `source`.
3. Auto-injected `source` and `title` are WRONG for this image (point to our build repo / `docker-images` name, not Scribe upstream). Must be overridden in the workflow's `metadata-action.labels:` block.
4. Auto-injected `created` is correct (build timestamp). Keep it.
5. Manual `docker build` (no CI) skips metadata-action entirely, so Dockerfile labels still matter for that path.
## Final label set on published image
| Key | Value | Source |
|-----|-------|--------|
| `org.opencontainers.image.title` | `Facebook Legacy Scribe` | Dockerfile + workflow override |
| `org.opencontainers.image.description` | `Facebook Legacy Scribe - real-time log aggregation daemon` | Dockerfile + workflow override |
| `org.opencontainers.image.version` | `2.2` | Dockerfile + workflow override |
| `org.opencontainers.image.source` | `https://github.com/facebookarchive/scribe` | Dockerfile + workflow override |
| `org.opencontainers.image.licenses` | `Apache-2.0` | Dockerfile + workflow override |
| `org.opencontainers.image.created` | (build timestamp) | metadata-action auto |
| `org.opencontainers.image.url` | (our repo URL) | metadata-action auto (acceptable — IS where image is built) |
| `org.opencontainers.image.revision` | (git SHA of our repo) | metadata-action auto (acceptable — identifies build recipe commit) |
| `maintainer` | `Tien Nguyen Minh <tiennm99@outlook.com>` | Dockerfile only (not OCI; metadata-action does not auto-inject) |
## Files to modify
### 1. `scribe-2.2/Dockerfile` — replace lines 70-73
**Before:**
```dockerfile
LABEL maintainer="Tien Nguyen Minh <tiennm99@outlook.com>"
LABEL org.opencontainers.image.version="2.2"
LABEL org.opencontainers.image.description="Facebook Legacy Scribe - real-time log aggregation daemon"
LABEL org.opencontainers.image.source="https://github.com/facebookarchive/scribe"
```
**After:**
```dockerfile
LABEL maintainer="Tien Nguyen Minh <tiennm99@outlook.com>"
LABEL org.opencontainers.image.title="Facebook Legacy Scribe"
LABEL org.opencontainers.image.description="Facebook Legacy Scribe - real-time log aggregation daemon"
LABEL org.opencontainers.image.version="2.2"
LABEL org.opencontainers.image.source="https://github.com/facebookarchive/scribe"
LABEL org.opencontainers.image.licenses="Apache-2.0"
```
Order: `maintainer` first, then OCI labels grouped (title, description, version, source, licenses). Plain ASCII only.
### 2. `.github/workflows/publish-scribe-2.2.yml` — extend metadata-action step (lines 38-47)
**Before (lines 38-47):**
```yaml
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: |
ghcr.io/${{ github.actor }}/scribe
${{ secrets.DOCKER_USERNAME }}/scribe
tags: |
type=raw,value=latest
type=raw,value=2.2
```
**After:**
```yaml
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: |
ghcr.io/${{ github.actor }}/scribe
${{ secrets.DOCKER_USERNAME }}/scribe
tags: |
type=raw,value=latest
type=raw,value=2.2
labels: |
org.opencontainers.image.title=Facebook Legacy Scribe
org.opencontainers.image.description=Facebook Legacy Scribe - real-time log aggregation daemon
org.opencontainers.image.version=2.2
org.opencontainers.image.source=https://github.com/facebookarchive/scribe
org.opencontainers.image.licenses=Apache-2.0
```
Note: do NOT quote values in the metadata-action `labels:` block — quotes become part of the label value. metadata-action splits on `=` only.
## Implementation steps
1. Edit `scribe-2.2/Dockerfile` lines 70-73 per spec above.
2. Edit `.github/workflows/publish-scribe-2.2.yml` adding the `labels:` block after `tags:`.
3. Lint/validate yaml manually (no schema check needed — small change).
4. Stage only these 2 files. Leave the pending ASCII cleanup unstaged.
5. Commit: `feat(scribe): expand OCI image-spec labels` (conventional commit, no plan refs in message per global rules).
6. Push. Workflow triggers because path filter `scribe-2.2/**` and `.github/workflows/publish-scribe-2.2.yml` both match.
7. Validate (see below).
## Commit plan
**One commit for this plan.** Bundles Dockerfile + workflow because they describe the same change (label set expansion) and must land together to avoid a window where workflow builds and Dockerfile disagree.
The existing pending ASCII cleanup is a separate logical commit — handle BEFORE or AFTER this plan, never in the same commit. Plan does not stage or modify those files.
Commit message:
```
feat(scribe): expand OCI image-spec labels
Add image.title and image.licenses; override metadata-action defaults
in publish workflow so published image carries Scribe upstream source
and correct title rather than this repo's auto-derived values.
```
## Validation
After workflow run completes and image is published:
```bash
docker buildx imagetools inspect ghcr.io/tiennm99/scribe:2.2 --format '{{json .Manifest}}'
# OR inspect labels specifically:
docker pull ghcr.io/tiennm99/scribe:2.2
docker inspect ghcr.io/tiennm99/scribe:2.2 --format '{{json .Config.Labels}}' | jq
```
Expected: all 5 OCI keys above present with the specified values; `created` set to a recent ISO-8601 timestamp; `url`/`revision` present (metadata-action defaults); `maintainer` present.
For manual-build validation (no CI):
```bash
docker build -t scribe:2.2-local scribe-2.2
docker inspect scribe:2.2-local --format '{{json .Config.Labels}}' | jq
```
Expected: 5 OCI keys + `maintainer` from Dockerfile; no `created`/`url`/`revision` (those only exist via metadata-action).
## Todo
- [ ] Edit `scribe-2.2/Dockerfile` lines 70-73 — final 6-label block
- [ ] Edit `.github/workflows/publish-scribe-2.2.yml` — add `labels:` block to metadata-action step
- [ ] `git add scribe-2.2/Dockerfile .github/workflows/publish-scribe-2.2.yml` (explicit paths; do NOT stage other modified files)
- [ ] Verify `git status` shows only those two files staged; ASCII-cleanup files still unstaged
- [ ] Commit with conventional message above
- [ ] Push to `main`
- [ ] Wait for `publish-scribe-2.2.yml` workflow to complete (GitHub Actions tab)
- [ ] Run `docker inspect` validation against `ghcr.io/tiennm99/scribe:2.2`
- [ ] Confirm all 5 OCI labels carry expected values; tick this phase complete
## Success criteria
- Published image at `ghcr.io/tiennm99/scribe:2.2` AND `${DOCKER_USERNAME}/scribe:2.2` carries:
- `org.opencontainers.image.title=Facebook Legacy Scribe`
- `org.opencontainers.image.source=https://github.com/facebookarchive/scribe` (NOT this repo's URL)
- `org.opencontainers.image.licenses=Apache-2.0`
- `org.opencontainers.image.version=2.2`
- `org.opencontainers.image.description` with `Facebook Legacy Scribe - real-time log aggregation daemon`
- Workflow continues to publish `2.2` and `latest` tags (no regression).
- Manual `docker build scribe-2.2/` still works and produces labeled image.
## Rollback
Single commit revert: `git revert <sha>` and force-trigger workflow. No data, no schema, no dependents — rollback is purely cosmetic metadata.
@@ -0,0 +1,42 @@
---
title: "Expand OCI image-spec labels on scribe-2.2"
description: "Add title/licenses labels and override workflow defaults so published image has correct upstream metadata."
status: completed
priority: P3
effort: 20m
branch: main
tags: [docker, oci, metadata, scribe]
created: 2026-05-28
---
# Scribe-2.2 OCI Labels Expansion
> **Completed 2026-05-28 via commits `afaa631` (ASCII cleanup) and `4ef74f3` (OCI labels). Pattern reused as template for Phase 03 of [260528-1324-rename-couchbase-to-convention-b](../260528-1324-rename-couchbase-to-convention-b/plan.md).**
Single-pass metadata change. Adds `image.title` and `image.licenses` to the Dockerfile, and overrides the GitHub Actions `docker/metadata-action` defaults so the published image carries correct Scribe-upstream values rather than this repo's defaults.
## Why this is not a one-line change
`docker/metadata-action` auto-injects 8 OCI labels with values derived from THIS repo (`tiennm99/docker-images`). CLI `--label` overrides Dockerfile `LABEL` instructions for the same key, so the Dockerfile labels are effectively shadowed at workflow build time. Fix: override the relevant keys in the workflow's `metadata-action` `labels:` block. Keep Dockerfile labels as the manual-build fallback.
## Decisions locked
- Title: `Facebook Legacy Scribe` (consistent with existing description prefix).
- Licenses: `Apache-2.0` (verified, single license, no SPDX expression needed).
- `image.created`: let `metadata-action` auto-inject (build timestamp). No `--build-arg` plumbing.
- Skip: `url`, `documentation`, `vendor`, `revision`, `authors`.
- Override approach: BOTH Dockerfile (manual builds) AND workflow `labels:` block (CI builds). Same key/value in both places — single source of truth in plan, dual placement for two build paths.
## Phases
- [ ] Phase 01 — Dockerfile + workflow labels ([phase-01-dockerfile-and-workflow-labels.md](phase-01-dockerfile-and-workflow-labels.md))
## Files touched
- `scribe-2.2/Dockerfile` (runtime stage, lines 70-73)
- `.github/workflows/publish-scribe-2.2.yml` (metadata-action step, lines 38-47)
## Out of scope
- Existing uncommitted ASCII cleanup across `scribe-2.2/**` — separate logical commit, untouched by this plan.
- Other images in the repo (only `scribe-2.2/` touched).
@@ -0,0 +1,76 @@
---
phase: 1
title: "Codify naming rule and supersede prior plans"
status: code-applied
priority: P2
effort: "15m"
dependencies: []
---
# Phase 1: Codify naming rule and supersede prior plans
## Overview
Document Convention B in `README.md` and close out the two prior decision plans (parked `260528-1110-…` and completed `260528-1221-…`). One docs commit.
## Requirements
- Functional: rule is discoverable in README; prior plans no longer show as "pending" in `ck plan status`.
- Non-functional: plain ASCII; under ~25 lines added to README; no breaking link changes.
## Architecture
Single concern: decision record. Touches 3 markdown files. No code.
## Related Code Files
- Modify: `D:\tiennm99\docker-images\README.md` (append new "## Naming convention" section after the image-index table at L7-10)
- Modify: `D:\tiennm99\docker-images\plans\260528-1110-image-naming-convention-decision\plan.md` (frontmatter `status: superseded` + supersede pointer)
- Modify: `D:\tiennm99\docker-images\plans\260528-1221-scribe-oci-labels-expansion\plan.md` (frontmatter `status: completed` + commit refs)
## Implementation Steps
1. Append the codified rule to `README.md` immediately after the image-index table (before any per-image sections). Use this exact block (plain ASCII, no smart quotes, no em-dashes):
```markdown
## Naming convention
This repo uses Docker-Hub-style image names: `<service>:<full-version>`.
- Subdirectory: `<service>-<major-line>/` (e.g. `couchbase-2.5/`, `scribe-2.2/`). Subdir holds the build context for one major version line.
- GHCR + Docker Hub image: `<service>` (e.g. `couchbase`, `scribe`). No major-line suffix in the image name.
- Workflow file: `publish-<service>-<major-line>.yml` (matches subdir for path-trigger clarity).
- Tags published per workflow:
- `<full-version>` (e.g. `2.5.2`, `2.2`)
- `<major-line>` (e.g. `2.5`) - optional alias
- `latest` - canonical build of the only major line this repo carries for the service.
If a second major line of the same service is ever added to this repo, drop `latest` from both workflows and publish `<major-line>-latest` tags instead.
```
2. Update `plans/260528-1110-image-naming-convention-decision/plan.md`:
- Set frontmatter `status: superseded` (or add the field if missing).
- Add a top-of-body note: `> **Superseded by [260528-1324-rename-couchbase-to-convention-b](../260528-1324-rename-couchbase-to-convention-b/plan.md) on 2026-05-28.** User chose Convention B; see superseding plan for rationale.`
3. Update `plans/260528-1221-scribe-oci-labels-expansion/plan.md`:
- Set frontmatter `status: completed`.
- Add a top-of-body note: `> **Completed 2026-05-28 via commits `afaa631` (ASCII cleanup) and `4ef74f3` (OCI labels). Pattern reused as template for Phase 03 of [260528-1324-rename-couchbase-to-convention-b](../260528-1324-rename-couchbase-to-convention-b/plan.md).**`
4. Commit: `docs: codify Convention B naming rule and close prior decision plans`
## Success Criteria
- [ ] README contains "Naming convention" section with the 5 bullet structure above
- [ ] Parked 1110 plan marked superseded with link forward
- [ ] Completed 1221 plan marked completed with commit refs
- [ ] One commit, message starts with `docs:`
- [ ] `git diff --stat` for this phase touches exactly 3 files
## Risk Assessment
- **Risk:** future contributor adds a 2nd major-line service (e.g. `couchbase-6`) and forgets the `latest` rule, causing `couchbase:latest` to flip to the newer version mid-flight. **Mitigation:** the codified rule explicitly calls this out; future cross-plan scan would catch it.
- **Risk:** prior plans' `status: superseded` field may not be a recognized `ck plan` status. **Mitigation:** if `ck plan status` rejects, fall back to a body-level note plus removing the file from active scan via different means (e.g. archive flag). Verify after step 2.
## Open question (not blocking)
- Should the rule also live in a project-local `CLAUDE.md` so future Claude Code sessions auto-load it? Defaulted to README-only (KISS — repo has no CLAUDE.md today). Trivial to add later if it becomes painful.
@@ -0,0 +1,102 @@
---
phase: 2
title: "Rename couchbase image to <service>:<version>"
status: code-applied
priority: P1
effort: "10m"
dependencies: [1]
---
# Phase 2: Rename couchbase image to `<service>:<version>`
## Overview
Rename the published image from `couchbase-2.5` to `couchbase` across GHCR + Docker Hub. Keep subdirectory and workflow filename unchanged (path-trigger anchor). Add `2.5` major-line alias tag while we're here.
## Requirements
- Functional: after push + workflow run, `docker pull ghcr.io/tiennm99/couchbase:2.5.2` resolves; old `ghcr.io/tiennm99/couchbase-2.5:2.5.2` becomes orphan.
- Non-functional: subdirectory `couchbase-2.5/` not renamed; workflow filename not renamed.
## Architecture
Three files touch the literal string `couchbase-2.5` in an image-reference context:
- `.github/workflows/publish-couchbase-2.5.yml` - `images:` block (L42-43) + `subject-name:` (L61)
- `README.md` - image-index table row link target (`pkgs/container/couchbase-2.5` -> `pkgs/container/couchbase`) and the `docker pull ghcr.io/tiennm99/couchbase-2.5:2.5.2` example block in the Couchbase section.
- `couchbase-2.5/docker-compose.example.yml` - L3 `image: ghcr.io/tiennm99/couchbase-2.5:2.5.2`
Subdir name and workflow filename are NOT image references; they stay.
## Related Code Files
- Modify: `D:\tiennm99\docker-images\.github\workflows\publish-couchbase-2.5.yml`
- Modify: `D:\tiennm99\docker-images\README.md`
- Modify: `D:\tiennm99\docker-images\couchbase-2.5\docker-compose.example.yml`
## Implementation Steps
1. Edit `.github/workflows/publish-couchbase-2.5.yml`:
**Before (L42-47):**
```yaml
images: |
ghcr.io/${{ github.actor }}/couchbase-2.5
${{ secrets.DOCKER_USERNAME }}/couchbase-2.5
tags: |
type=raw,value=latest
type=raw,value=2.5.2
```
**After:**
```yaml
images: |
ghcr.io/${{ github.actor }}/couchbase
${{ secrets.DOCKER_USERNAME }}/couchbase
tags: |
type=raw,value=latest
type=raw,value=2.5
type=raw,value=2.5.2
```
**Before (L61):**
```yaml
subject-name: ghcr.io/${{ github.actor }}/couchbase-2.5
```
**After:**
```yaml
subject-name: ghcr.io/${{ github.actor }}/couchbase
```
2. Edit `README.md`:
- Image-index table row: link `[couchbase-2.5](https://github.com/tiennm99/docker-images/pkgs/container/couchbase-2.5)` -> `[couchbase](https://github.com/tiennm99/docker-images/pkgs/container/couchbase)`. Display text in the link also flips to `couchbase`.
- Couchbase section `docker pull ghcr.io/tiennm99/couchbase-2.5:2.5.2` example -> `docker pull ghcr.io/tiennm99/couchbase:2.5.2`
- "More options" GHCR URL in the same section also flips.
3. Edit `couchbase-2.5/docker-compose.example.yml` L3:
- `image: ghcr.io/tiennm99/couchbase-2.5:2.5.2` -> `image: ghcr.io/tiennm99/couchbase:2.5.2`
4. Commit: `refactor(couchbase): rename image package to couchbase (Convention B)`
## Success Criteria
- [ ] Workflow `images:` block lists `couchbase`, not `couchbase-2.5`
- [ ] Workflow `tags:` includes new `2.5` major-line alias
- [ ] Workflow `subject-name:` ends in `/couchbase`
- [ ] README image-index row link target is `pkgs/container/couchbase`
- [ ] `couchbase-2.5/docker-compose.example.yml` pulls from `ghcr.io/tiennm99/couchbase:2.5.2`
- [ ] `grep -r "tiennm99/couchbase-2.5" D:\tiennm99\docker-images\` (excluding plans/reports) returns 0 results
- [ ] One commit, message starts with `refactor(couchbase):`
## Risk Assessment
- **Risk:** stale `docker pull ghcr.io/tiennm99/couchbase-2.5:2.5.2` references in user environments break after orphan deletion. **Mitigation:** repo is one-maintainer; no known external consumers. Old package remains pullable until user deletes via UI (manual step, deferred).
- **Risk:** `2.5` tag alias confuses users into expecting it to track patches within 2.5.x. **Mitigation:** only one 2.5.x release exists (2.5.2); `2.5` is effectively a synonym. Codified rule in Phase 01 calls it an "optional alias".
## Validation (after push, in Phase 03 or later)
- Workflow run completes successfully on GitHub Actions
- `docker pull ghcr.io/tiennm99/couchbase:2.5.2` resolves
- `docker pull ghcr.io/tiennm99/couchbase:latest` resolves
- `docker pull ghcr.io/tiennm99/couchbase:2.5` resolves
- Root README image-index link resolves to a real GHCR page
@@ -0,0 +1,104 @@
---
phase: 3
title: "Port OCI labels to couchbase image"
status: code-applied
priority: P2
effort: "10m"
dependencies: [2]
---
# Phase 3: Port OCI labels to couchbase image
## Overview
Add OCI image-spec labels to couchbase using the proven pattern from `scribe-2.2/Dockerfile` + `.github/workflows/publish-scribe-2.2.yml`. Both Dockerfile (manual-build path) and workflow `metadata-action.labels:` block (CI-build path) set the same values, since CLI `--label` from metadata-action overrides Dockerfile `LABEL` instructions.
## Requirements
- Functional: published image at `ghcr.io/tiennm99/couchbase:2.5.2` carries 5 OCI labels with correct values; metadata-action's auto-defaults (derived from THIS repo) do not shadow upstream-Couchbase metadata.
- Non-functional: plain ASCII; pattern matches scribe verbatim for consistency.
## Architecture
Mirror scribe-2.2's structure:
- `Dockerfile` runtime stage carries 5 `LABEL org.opencontainers.image.*` lines after `LABEL maintainer`.
- Workflow `metadata-action` step has a `labels:` block in YAML setting the same 5 keys (overrides metadata-action defaults).
## Research input
- `plans/reports/researcher-260528-1324-couchbase-license-and-source-verification-report.md`
- License: `Apache-2.0` (Couchbase Server 2.5.2, Dec 2012; BSL adoption was later, post-2021)
- `image.source`: `https://github.com/couchbase/docker` (general repo, not pinned commit)
## Label values
| Key | Value |
|-----|-------|
| `org.opencontainers.image.title` | `Couchbase Server 2.5` |
| `org.opencontainers.image.description` | `Legacy Couchbase Server (no official upstream image)` |
| `org.opencontainers.image.version` | `2.5.2` |
| `org.opencontainers.image.source` | `https://github.com/couchbase/docker` |
| `org.opencontainers.image.licenses` | `Apache-2.0` |
`org.opencontainers.image.created`, `url`, `revision` left to metadata-action defaults (acceptable).
## Related Code Files
- Modify: `D:\tiennm99\docker-images\couchbase-2.5\Dockerfile` (insert 5 LABEL lines after L3 `LABEL maintainer`)
- Modify: `D:\tiennm99\docker-images\.github\workflows\publish-couchbase-2.5.yml` (add `labels:` block after `tags:` in metadata-action step)
## Implementation Steps
1. Edit `couchbase-2.5/Dockerfile`:
**Before (L3):**
```dockerfile
LABEL maintainer="Tien Nguyen Minh <tiennm99@outlook.com>"
```
**After:**
```dockerfile
LABEL maintainer="Tien Nguyen Minh <tiennm99@outlook.com>"
LABEL org.opencontainers.image.title="Couchbase Server 2.5"
LABEL org.opencontainers.image.description="Legacy Couchbase Server (no official upstream image)"
LABEL org.opencontainers.image.version="2.5.2"
LABEL org.opencontainers.image.source="https://github.com/couchbase/docker"
LABEL org.opencontainers.image.licenses="Apache-2.0"
```
2. Edit `.github/workflows/publish-couchbase-2.5.yml` metadata-action step. Insert `labels:` block immediately after the `tags:` block (after Phase 02's edits):
```yaml
labels: |
org.opencontainers.image.title=Couchbase Server 2.5
org.opencontainers.image.description=Legacy Couchbase Server (no official upstream image)
org.opencontainers.image.version=2.5.2
org.opencontainers.image.source=https://github.com/couchbase/docker
org.opencontainers.image.licenses=Apache-2.0
```
Do NOT quote values in the `labels:` block - metadata-action splits on `=` only, quotes become part of the value.
3. Commit: `feat(couchbase): expand OCI image-spec labels`
## Success Criteria
- [ ] Couchbase Dockerfile has 5 OCI LABEL lines after maintainer
- [ ] Workflow `metadata-action` step has `labels:` block with the same 5 keys, no quoted values
- [ ] One commit, message starts with `feat(couchbase):`
- [ ] After workflow runs: `docker inspect ghcr.io/tiennm99/couchbase:2.5.2 --format '{{json .Config.Labels}}'` returns all 5 OCI keys with upstream-Couchbase values (not auto-derived from this repo)
## Risk Assessment
- **Risk:** license verification relied on Wikipedia + Couchbase blog; if 2.5.2 binaries are actually licensed differently from Couchbase open-source code, the label is wrong. **Mitigation:** label is metadata, not legal claim; updateable. If Couchbase publishes a contradicting LICENSE.txt for 2.5.2, swap the label value in a one-line follow-up commit.
- **Risk:** workflow `labels:` block YAML indentation drift (it sits inside `metadata-action.with:`). **Mitigation:** match scribe's exact indentation in `publish-scribe-2.2.yml` L48-53 (proven working).
## Validation (after push)
- Workflow run on `main` after this phase's commit completes successfully.
- `docker pull ghcr.io/tiennm99/couchbase:2.5.2` then `docker inspect couchbase:2.5.2 --format '{{json .Config.Labels}}' | jq`. Expect:
- All 5 OCI keys above with specified values
- `org.opencontainers.image.created` set to recent ISO-8601 timestamp (metadata-action default)
- `org.opencontainers.image.url` and `org.opencontainers.image.revision` set to this repo's metadata (metadata-action default, acceptable)
- `maintainer` set to `Tien Nguyen Minh <tiennm99@outlook.com>` (Dockerfile only)
- Manual `docker build couchbase-2.5/ -t couchbase:test`; `docker inspect couchbase:test --format '{{json .Config.Labels}}'` shows 5 OCI keys + maintainer (no `created`/`url`/`revision` since metadata-action absent on manual build).
@@ -0,0 +1,53 @@
---
title: "Apply Convention B naming: rename couchbase image and codify rule"
description: "Migrate this repo to Docker-Hub-style image naming (<service>:<version>). Rename ghcr.io/tiennm99/couchbase-2.5 -> couchbase. Port OCI labels to couchbase. Codify the rule in README. Close out prior naming-decision plans."
status: in_progress
priority: P2
branch: "main"
tags: [docker, naming, convention, couchbase, oci]
blockedBy: []
blocks: [260528-1110-image-naming-convention-decision]
created: "2026-05-28T06:30:23.203Z"
createdBy: "ck:plan"
source: skill
---
# Apply Convention B naming: rename couchbase image and codify rule
## Overview
Three brainstorm rounds had recommended Convention A (`<service>-<major>:<full-version>`). User overruled on YAGNI grounds — A's load-bearing argument (`latest` semantics breaking on multi-major-line coexistence) is a future hypothetical, not a current need. Repo has only 2 images, both single-major-line. Convention B (`<service>:<full-version>`) is the Docker-Hub standard, shorter, cheaper to migrate.
This plan implements Convention B: rename `ghcr.io/tiennm99/couchbase-2.5` to `couchbase` (and Docker Hub counterpart), keep `scribe` as-is (already conformant), port OCI labels to couchbase using the proven scribe pattern, codify the rule in `README.md`, and close out prior decision plans.
## Phases
| Phase | Name | Status |
|-------|------|--------|
| 1 | [Codify naming rule and supersede prior plans](./phase-01-codify-naming-rule-and-supersede-prior-plans.md) | Code applied (uncommitted) |
| 2 | [Rename couchbase image to <service>:<version>](./phase-02-rename-couchbase-image-to-service-version.md) | Code applied (uncommitted) |
| 3 | [Port OCI labels to couchbase image](./phase-03-port-oci-labels-to-couchbase-image.md) | Code applied (uncommitted) |
## Dependencies
- **Supersedes:** `plans/260528-1110-image-naming-convention-decision/` (parked "decide later" plan; this plan locks in Convention B and closes the loop).
- **Builds on:** `plans/260528-1221-scribe-oci-labels-expansion/` (completed via commits `afaa631` + `4ef74f3`). That plan's OCI label pattern is the template for Phase 03.
## Research input
- `plans/reports/researcher-260528-1324-couchbase-license-and-source-verification-report.md` — confirms Couchbase 2.5.2 license is `Apache-2.0`; recommends `image.source=https://github.com/couchbase/docker` (general repo, not pinned commit).
## Commit strategy
One commit per phase, three commits total. Pattern matches the just-shipped scribe work: `docs:`/`refactor:`/`feat:`.
## Post-execution manual actions (not part of cook)
1. Delete orphan packages via UI: `ghcr.io/tiennm99/couchbase-2.5` and `docker.io/${DOCKER_USERNAME}/couchbase-2.5`.
2. Validate: `docker pull ghcr.io/tiennm99/couchbase:2.5.2` and `docker inspect` shows OCI labels.
## Out of scope
- Subdirectory rename (`couchbase-2.5/` stays; subdir name is decoupled from image name under Convention B — codified in Phase 01's rule).
- Workflow filename rename (`publish-couchbase-2.5.yml` stays; path-trigger needs to match the subdir).
- New project-local `CLAUDE.md` — defaulted to README-only for KISS; flagged in Phase 01 as an optional add.
@@ -0,0 +1,108 @@
# Image Naming Convention - Brainstorm
## Current state (verified)
| Subdir | GHCR image | Tags | Notes |
|---|---|---|---|
| couchbase-2.5/ | `couchbase-2.5` | `latest`, `2.5.2` | matches subdir |
| gradle-8/ | `gradle-8` | `latest`, `8.13` | matches subdir |
| oraclejdk-8/ | `oraclejdk-8` | `latest`, `8u201` | matches subdir |
| scribe-2.2/ | `scribe` | `latest`, `2.2` | **outlier** |
Scribe diverges in `publish-scribe-2.2.yml` L43-44 (`images:`), L61 (`subject-name:`), and `README.md` L12 (package link). Subdir already correctly named `scribe-2.2/`. Workflow first added ~7 weeks ago - package fresh, blast radius low.
## Convention
`<service>-<major-line>` = **image name** (preserved product line). `<full-version>` = **tag** (specific build). Differs from Docker Hub officials (`python`, `node`) because this repo specializes in archived/legacy software - each major line is its own write-once artifact with potentially divergent Dockerfile/base OS, not a sliding window. Scribe is same shape (archived 2.2, no 2.3 ever) - line name belongs in image name, not just tag.
## Options for scribe
### Option A - Rename to `scribe-2.2` (full alignment)
- Changes: workflow L43-44 + L61, README L12, possibly `scribe-2.2/README.md` and `scribe-2.2/example/*` if any pull-URL refs
- Pros: consistent with 3/3 siblings; reserves `scribe` for hypothetical fork; alphabetical sort lines up; one rule to teach; future-proof
- Cons: breaks any existing `docker pull ghcr.io/tiennm99/scribe:2.2`; old GHCR + Docker Hub packages orphaned (inert)
- Blast radius: low. 7 weeks old, no known external consumers
### Option B - Keep as `scribe` (document exception)
- Pros: zero migration, no orphans, shorter name
- Cons: convention dies on first stress test; future contributors must memorize special case; irregular sort; retroactively ugly if `scribe-3.x` appears
- Blast radius: zero now, debt later
### Option C - Dual-publish for transition
- Pros: smoothest external transition
- Cons: doubles artifacts; sunset date easy to forget; complexity unjustified for a personal legacy archive
- Blast radius: medium
### Option D - Drop suffix from all siblings (inverted)
- Reject: breaks multi-major-line model; requires renaming 3 existing packages; strictly worse
## Trade-off matrix
| Dim | A (rename) | B (keep) | C (dual) | D (drop suffix) |
|---|---|---|---|---|
| Consistency | full | one outlier | during transition | inverted |
| Migration cost | low (~5 edits) | zero | low + ongoing | high (3 pkgs) |
| Orphans | 2 | 0 | 2 eventually | 6+ |
| Future-proof | yes | no | yes | no |
| Discoverability | alphabetical | irregular | irregular | alphabetical |
| Teach cost | 1 rule | 1 rule + exception | 1 rule + sunset | 1 worse rule |
## Recommendation: Option A - rename to `scribe-2.2`
- Package 7 weeks old, no known external consumers - blast radius minimal
- ~5 line edits, one workflow run
- Establishes rule cleanly on first stress test
- Orphan packages inert; deletable in 30 seconds via UI
## `latest` tag policy - keep unchanged
For write-once archived images, `latest` is semantically equivalent to canonical version tag. Usual objection ("silently shifts") doesn't apply - nothing shifts. Ergonomic default for casual `docker pull`. Edge case: CVE rebuild would shift latest, but consumers can pin to version tag.
## Repo-wide rule to codify
Add to `README.md` (new "Naming convention" section) and `CLAUDE.md`:
```
Naming convention for new images
- Subdirectory: <service>-<major-line> e.g. redis-3/, mongodb-3.6/
- GHCR + Docker Hub image name: <service>-<major-line> (= subdir name)
- Workflow file: .github/workflows/publish-<service>-<major-line>.yml
- Tags published:
- latest (alias for canonical/only version)
- <full-version> e.g. 3.6.23, 7.4.5, 8u201
- All three (subdir, image name, workflow basename) MUST match.
```
Rule: **subdir = image name = workflow basename**, all three derived from `<service>-<major-line>`.
## Risks / considerations
- Attestation `subject-name:` (L61) must also update - easy to miss
- Orphan packages harmless but visible in GHCR UI - decide: delete vs tombstone in README
- Grep `scribe-2.2/` for any `ghcr.io/tiennm99/scribe` literal before committing
- Commit message: `refactor(scribe): align image name with subdir naming convention`
## Success metrics
- `gh api /users/tiennm99/packages/container/scribe-2.2` returns 200 after first workflow run
- README image-index links resolve
- Naming-convention section exists in README, referenced from CLAUDE.md
- Next image added follows rule without prompting
## Next steps
1. User confirms Option A (or picks B/C)
2. If A: edit workflow + README + grep/fix any pull-URL refs in scribe-2.2/
3. Codify naming rule in README + project CLAUDE.md
4. Decide on orphan deletion vs retention
## Unresolved questions
1. Delete orphaned `ghcr.io/tiennm99/scribe` + `docker.io/tiennm99/scribe` after rename, or tombstone in README? (Lean: delete.)
2. Does `scribe-2.2/example/docker-compose.yml` reference the published image, or only local build context? Quick grep needed before rename.
3. README image-index table shows GHCR only - add Docker Hub coords too? (Adjacent, out of scope.)
4. `gradle-8` / `oraclejdk-8` GHCR links missing from README (show `-`) - intentional pending first publish, or oversight?
@@ -0,0 +1,128 @@
# Image Naming Convention - Re-brainstorm (A vs B, no scribe special-casing)
## TL;DR
**Recommend Convention A** (`<service>-<major-line>:<full-version>`). Same conclusion as previous brainstorm, narrower reasoning. Single strongest argument: `latest` semantics break under B for archived software.
## The two conventions
| Convention | Image name | Tag | Example | Used by |
|---|---|---|---|---|
| A | `<service>-<major-line>` | `<full-version>` | `couchbase-2.5:2.5.2` | 3 of 4 images in repo (current) |
| B | `<service>` | `<full-version>` | `couchbase:2.5.2` | scribe (current); Docker Hub officials (`python`, `redis`, `postgres`) |
## Steel-mans
### A — museum collection
Each major line is a write-once artifact: different upstream source, often different base OS, separate EOL/CVE story. Per-package `latest`, README, badge, attestation history. GHCR list IS the catalog.
### B — library catalog
One package per service, tag namespace handles divergence (`3.11-bookworm`, `3.11-alpine`). Adding a new major line is `new subdir + new tag` instead of `new package`. Matches `docker pull python:3.11` muscle memory.
## Where the choice actually matters
### Adding Couchbase 6 hypothetically
| Aspect | A | B |
|---|---|---|
| GHCR discoverability | 2 rows side-by-side | 1 row, tags hidden |
| README image-index table | grows linearly | stays narrow |
| GHCR package list | growing alphabetical | compact cluster |
| Workflow files | 1:1 with subdir | 1:1 subdir, but **multiple workflows push to same package** |
| Attestation `subject-name` | per-line lineage | mixed lineage |
| `latest` semantics | bounded per line | ambiguous - newest wins? meaningless? |
| Adding a build | heavyweight (new package) | lightweight (new tag) |
### Multiple builds within one line (alpha/stable)
Tag suffixes work in both. Wash.
### Base-OS divergence
Docker officials use tag suffixes (`python:3.11-alpine`). But that pattern is for SAME software on different runtimes. Here, `couchbase-2.5` (CentOS) vs `couchbase-6` (Ubuntu) is DIFFERENT software trees - more like `python` vs `pypy`. Analogy doesn't transfer.
## `latest` tag - the decisive argument
### Under A
`latest` per package = canonical build of that frozen line. Stable, bounded, doesn't drift across major lines.
### Under B
- `couchbase:latest` = newest tag wins (Docker Hub default). User landing here for legacy 2.5 gets 6.6.0. **Misleading.**
- Fix: don't publish `latest` under B. Every README example must spell out a version tag. No quick-pull demo.
- Alternative: pin `latest` to a specific version. Which? "Oldest" is weird; "most-popular" needs telemetry; "last added" is arbitrary.
A's `latest` is mildly defensible. B's `latest` is broken or must be dropped. Net point for A.
## Workflow / CI
- A: each workflow owns its package. Zero concurrency risk between major lines.
- B: multiple workflows push to same package on different tags. Tag uniqueness avoids data loss, but:
- `latest` pointer flaps if both publish `latest` (must disable on all but one)
- Attestation history mixes lines - harder to audit
- Concurrency groups need per-tag rescoping
Net point for A.
## Migration cost if B were chosen
| File | Edit |
|---|---|
| 3x `publish-*.yml` (couchbase, gradle, oraclejdk) | strip `-<major>` from `images:` and `subject-name:` |
| `publish-scribe-2.2.yml` | no change (already B-style) |
| `README.md` | image-index table + pull examples + package links |
| 4x `<subdir>/README.md` | regrep for pull URLs |
| 4x `<subdir>/example/*` | regrep for pull URLs |
| Tag policy | drop `latest` or pin per-service - new decision needed |
| GHCR + Docker Hub cleanup | 6 orphan packages |
~12-15 edits + new tag-policy decision + 6 orphans + concurrency rescoping. **~3x cost of A-renames-scribe**, lands in worse place.
A-route: ~5 edits, no tag-policy question, 2 orphans.
## Why A wins (5 reasons)
1. **`latest` breaks under B for archives.** Strongest argument. A's `latest` per-line is stable; B's is misleading or must be dropped.
2. **Attestation lineage per-line under A, mixed under B.** Matters for supply-chain trust on a repo whose whole point is "I built these, trust them".
3. **Docker-official analogy doesn't transfer.** Officials are sliding-window for maintained software; this repo is a museum of frozen artifacts. Different domain.
4. **Migration asymmetry.** Don't pay 3x to land in worse place.
5. **Per-line discoverability matches the artifact model.** Each exhibit gets its own plinth. GHCR package list IS the catalog.
The one cost of A - divergence from Docker official conventions - is a feature here. Visitors are specifically looking for legacy software; seeing 4 distinct rows is more discoverable than one mystery `couchbase` with hidden tag list.
## Codified rule
```
## Naming convention
This repo packages archived/legacy software. Each major version line is a
write-once artifact with its own base OS, Dockerfile, attestation history,
and `latest` pointer.
Rule:
Subdirectory : <service>-<major-line>/
GHCR image : <service>-<major-line>
Docker Hub image : <service>-<major-line>
Workflow file : publish-<service>-<major-line>.yml
Tags published :
- <full-version> e.g. 2.5.2, 8.13, 8u201, 2.2
- latest (alias for canonical build of THIS line)
Constraint:
subdir == GHCR image == Docker Hub image == workflow basename. No exceptions.
Why not <service>:<full-version>?
This repo is a museum, not a sliding window. `latest` per major line is
meaningful and stable. `latest` across a service would be misleading
(newest version wins, even when users came here specifically for the
archived line). Attestation lineage is also cleaner per line.
New major line of an existing service?
New subdir, new workflow, new GHCR + Docker Hub package. Not a new tag
under the existing package.
```
## Unresolved questions
1. `latest` on CVE rebuild - shifts digest. Document "pin by version tag for reproducibility"?
2. Orphan cleanup for `scribe` + `scribe:latest` - delete vs tombstone? (Lean: delete.)
3. Docker Hub coords in README image-index table - add column? (Defer until rename lands.)
4. `gradle-8` / `oraclejdk-8` GHCR links show `-` - pending first publish or never triggered?
5. Codified rule in README only, or README + CLAUDE.md? (Lean: both.)
@@ -0,0 +1,98 @@
# Image Naming Convention -- Final Verdict (Round 3)
State verified 2026-05-28:
- 2 images only: couchbase-2.5/, scribe-2.2/ (commit ff5e5f6 dropped gradle/oraclejdk)
- scribe carries OCI labels including image.title, image.licenses=Apache-2.0, image.source (commits afaa631 + 4ef7dfb). Couchbase does NOT.
- Workflow labels-block pattern proven in publish-scribe-2.2.yml L48-53.
## Does new state alter the verdict?
Two real changes:
1. Sample collapsed to 2. Defend-the-convention frame is dead. 50/50 split = clean pick, no incumbent.
2. OCI image.title on scribe. Weakens ONE sub-argument (image-name-as-identifier) but does not touch the load-bearing one: latest semantics across major lines. docker inspect showing the title does not help a user typing docker pull -- they see the name, not labels, until after pull.
Neither change reaches the Round-2 core argument: archived-software repo = museum, latest per major line is the only stable semantic.
## Three options
### Option A -- rename scribe to scribe-2.2
- Edits: workflow L43,44,67 (3); scribe-2.2/README.md (4); scribe-2.2/example/docker-compose.yml (1); root README.md L10 (1). ~9 mechanical edits.
- latest: stable per line. scribe-2.2:latest = canonical 2.2 build, always.
- New couchbase-6 later: new subdir, new workflow, new package. Trivial. No collision.
- Hypothetical scribe-3: new subdir scribe-3.x/, new package. Old scribe-2.2 untouched, no latest drift.
- Discoverability: GHCR list shows couchbase-2.5, scribe-2.2 side-by-side, alphabetical, lineage explicit.
- OCI label: complementary, not redundant. Name says which line; label says what it is.
### Option B -- rename couchbase-2.5 to couchbase
- Edits: workflow L43,44,61 (3); root README.md L9 link + L15 pull (2); couchbase-2.5/docker-compose.example.yml (1). ~6 edits + 1 orphan package.
- latest: BREAKS the moment couchbase-6 lands. couchbase:latest = newest-published = 6.x. User searching for 2.5 gets 6 silently. Must drop latest or pin awkwardly.
- New couchbase-6 later: same package, new tag 6.x.x. Cheap, but forces latest-policy reckoning. Two workflows pushing to one package -- tag uniqueness saves data but latest flaps.
- Hypothetical scribe-3: same story. Repeated problem.
- Discoverability: GHCR shows couchbase + scribe. Major lines hidden behind tag list. Docker-official muscle memory yes, but this is not sliding-window software.
- OCI label: helps identification post-pull, does NOT solve latest ambiguity pre-pull.
### Option C -- codified asymmetry (lazy migration)
- Rule: 1 line per service = service:version; more-than-1 line = rename to service-major:full lazily.
- Under C, today state = couchbase + scribe (B-shaped). Lazy migration = the painful B-to-A rename at the worst moment (adding new line under deadline).
- latest: works today, breaks at migration.
- Forces the most expensive migration (rename + orphan + consumer breakage) at the moment of highest cognitive load. Anti-KISS.
## Verdict: Option A (unchanged from prior rounds)
1. Decisive argument (latest stability across major lines for archived software) unchanged by both new state-changes. Sample size and OCI labels do not touch it.
2. OCI image.title is COMPLEMENTARY to the line-suffix name, not a substitute. Pull-time discoverability (docker search, GHCR URL, badge) happens before any inspect.
3. Migration cost asymmetry stays: A = ~9 edits, no latest-policy rewrite. B = ~6 edits + new latest policy + landmine for next major line + orphan. C = pay later, more, at worse time.
4. With only 2 images, A locks in the rule cheaply NOW. Cost of B later (when couchbase-6 arrives) is provably higher than A now. Asymmetric optionality favors A.
5. couchbase-2.5-is-the-lone-convention-follower framing reversible: scribe rename has cheaper blast radius (7-week-old workflow, 1 example-compose consumer, fresh package). Sunk-cost cuts against B, not A.
## Codified rule (README + CLAUDE.md, copy-pasteable)
```
## Naming convention
This repo packages archived/legacy software. Each major version line is a
write-once artifact with its own base OS, Dockerfile, attestation history,
and latest pointer.
Naming rule (all four MUST match):
Subdirectory : <service>-<major-line>/
GHCR image : <service>-<major-line>
Docker Hub image : <service>-<major-line>
Workflow file : publish-<service>-<major-line>.yml
Examples: couchbase-2.5, scribe-2.2. Future: couchbase-6, redis-3.
Tags published per image:
- <full-version> e.g. 2.5.2, 2.2, 8u201
- latest alias for canonical build of THIS major line
Adding a new major line of an existing service = new subdir + new workflow
+ new package. NOT a new tag under the existing package. Keeps latest
semantics stable per line and attestation lineage clean.
OCI image-spec labels (org.opencontainers.image.*) REQUIRED on every image
for metadata-level identification independent of name. See
scribe-2.2/Dockerfile for the canonical set.
```
## Migration order if A approved
1. Rename literals in scribe (9 edits above).
2. Push, verify GHCR package scribe-2.2 populates with latest + 2.2.
3. Delete orphan ghcr.io/tiennm99/scribe + Docker Hub tiennm99/scribe via UI.
4. Add naming-convention section to root README.md + project-local CLAUDE.md.
5. Separate task: port OCI labels to couchbase using the labels: pattern from publish-scribe-2.2.yml L48-53.
## Unresolved questions
1. Orphan ghcr.io/tiennm99/scribe after rename -- delete or tombstone? Lean delete (7 weeks old).
2. Pre-check Docker Hub docker.io/tiennm99/scribe-2.2 namespace availability before rename push? Trivial check, do it.
3. CLAUDE.md scope: user-global vs project-local. Convention belongs in project-local D:/tiennm99/docker-images/CLAUDE.md (create if absent). Confirm.
4. OCI labels on couchbase -- same task as rename, or separate? Lean separate to keep rename diff minimal.
@@ -0,0 +1,141 @@
# Scribe-2.2 Cleanup Verification Review
**Date:** 2026-05-28
**Scope:** Uncommitted changes in `scribe-2.2/` vs HEAD
**Reviewer:** code-reviewer
## Files Reviewed
- `scribe-2.2/Dockerfile`
- `scribe-2.2/README.md`
- `scribe-2.2/config/scribe.conf`
- `scribe-2.2/example/README.md`
- `scribe-2.2/example/docker-compose.yml`
- `scribe-2.2/example/sender/main.go`
- `scribe-2.2/scripts/docker-entrypoint.sh`
## Overall Assessment
Pure cosmetic cleanup. No functional regressions. All build steps, runtime behavior, ports, volumes, users, configs unchanged. One concern: removed metadata LABELs.
## Critical Issues
None.
## High Priority
None.
## Medium Priority — Concerns (do not auto-revert)
### C1. Removed LABEL metadata in runtime stage
- HEAD had: `LABEL maintainer`, `LABEL description`, `LABEL version="2.2"`
- Working tree keeps only `LABEL maintainer`
- `description` and `version` labels removed
- Impact:
- `docker inspect scribe:2.2` no longer reports version/description
- Image registries (Harbor, GHCR) lose searchable description
- Image scanners / SBOM tools lose declared version field
- `org.opencontainers.image.version` / `.description` were not used (only plain `version`/`description`), so OCI compliance was already partial
- Status: **flag as intentional-or-regression question for user**. Image tag (`scribe:2.2`) still encodes the version, so loss is minor. But `description` had documentation value
- Recommended (if user wants to re-add): use OCI-standard labels:
- `org.opencontainers.image.version="2.2"`
- `org.opencontainers.image.description="Facebook Legacy Scribe - real-time log aggregation daemon"`
- `org.opencontainers.image.source="https://github.com/facebookarchive/scribe"`
## Low Priority
None.
## Per-Question Verification
### 1. Dockerfile build steps & package list
- **Build steps:** no removals/alterations. Only banner-comment lines and inline `# ──` comments removed. All RUN blocks identical
- **Builder yum list:** same 14 packages, sorted alphabetically:
- HEAD: `gcc, gcc-c++, make, autoconf, automake, libtool, git, wget, flex, bison, boost-devel, libevent-devel, openssl-devel, zlib-devel` (14)
- Working: `autoconf, automake, bison, boost-devel, flex, gcc, gcc-c++, git, libevent-devel, libtool, make, openssl-devel, wget, zlib-devel` (14)
- Set-equal, count-equal. One-package-per-line improves readability
- **Runtime yum list:** unchanged (boost-filesystem, boost-system, boost-thread, libevent, openssl)
- **EXPOSE 1463, USER scribe, VOLUME, ENTRYPOINT:** all unchanged
### 2. Removed LABELs
- `LABEL maintainer="Tien Nguyen Minh <tiennm99@outlook.com>"` still present
- `LABEL description=...` and `LABEL version="2.2"` removed
- See C1 above. Flagged, not auto-reverted
### 3. Go file `example/sender/main.go`
Four diff hunks, all non-logic:
- L5 (file header comment): em-dash to hyphen
- L125 comment: `messageBegin — detect` to `messageBegin: detect`
- L161 comment: `no return field present → treat as OK` to `no return field present; treat as OK`
- L211 string literal (user-visible): `Hello from Go sender — message %d/%d` to `Hello from Go sender - message %d/%d`
No logic touched. No control flow, no struct fields, no Thrift wire-encoding bytes, no retry logic, no timeouts changed.
**README sync check:** `example/README.md:41` already updated to `Hello from Go sender - message N/5`. Consistent with main.go output. Confirmed via grep — only the two expected occurrences exist, both hyphen.
### 4. `config/scribe.conf`
Only comment formatting:
- L1 header em-dash to hyphen
- 4 section-header `# ── Name ──────` lines simplified to `# Name`
Directives unchanged: `port=1463`, `max_msg_per_second=100000`, `max_msg_size=16777216`, `check_interval=3`, default store block (file, /var/log/scribe, hourly rotation, write_meta=true). No store directive added/removed.
### 5. Em-dash semantic breakage
Reviewed all em-dash to hyphen and em-dash to `:`/`;` replacements:
- `example/README.md:51` `Message category → log subdirectory name` to `Message category to log subdirectory name` — arrow replaced with "to". Reads naturally
- `example/README.md:62` `[link] — see the [other link]` to `[link] - see the [other link]` — fine
- `main.go:161` `→ treat as OK` to `; treat as OK` — semantically equivalent (consequence vs sequence). OK
- `main.go:125` `messageBegin — detect` to `messageBegin: detect` — annotation style. OK
- All other em-dash to hyphen replacements are parenthetical-aside style. No semantic loss
No broken Markdown links, no broken Mermaid/code-fence boundaries.
### 6. Residual Unicode
Grep for `[-―─-╿‘’“”…←-↓]` across all of `scribe-2.2/`: **0 matches**. Cleanup is complete.
Also grep for `→ — ─ │ ┌ └ ├ ┤`: **0 matches** in any of the 7 modified files.
## Positive Observations
- Alphabetized yum package list improves diff readability for future deps changes
- One-package-per-line aids future bisect of build failures
- ASCII-only sources improve grep/sed/IDE tooling on Windows where Unicode rendering varies
- Go string literal change keeps user-visible message ASCII — safer for log-grepping pipelines downstream
- No commit yet, so easy to fold back C1 if user agrees
## Trust-Boundary / Security Review
- No auth changes, no input validation paths touched
- No new env vars, no new ports, no new file paths
- Non-root `scribe` user retained
- No new ADD/COPY from external sources
- No secrets in diff
## Metrics
- Files changed: 7
- Lines net: comment/whitespace-only delta
- Functional code changes: 0
- Logic changes: 0
- Build-graph changes: 0
- Removed metadata labels: 2
## Recommended Actions
1. Decide on C1 (LABEL description/version removal):
- **Keep removed** — accept minor metadata loss
- **Re-add** — preferably as OCI-standard `org.opencontainers.image.*` labels
2. If C1 accepted as-is, commit cleanup as `style(scribe): normalize ASCII formatting in scribe-2.2/`
## Unresolved Questions
1. Was removal of `LABEL description` and `LABEL version` intentional, or collateral damage during banner-comment cleanup? Image tag still carries version, so functional impact is nil, but discoverability via `docker inspect` is lost.
@@ -0,0 +1,95 @@
# Code Review: scribe-2.2 OCI labels + cleanup verification
**Date:** 2026-05-28
**Scope:** uncommitted `scribe-2.2/` changes vs HEAD (`ff5e5f6`)
**Focus:** OCI label re-addition delta (cleanup already cleared in `code-review-260528-0925-scribe-cleanup-verification-report.md`)
## Scope
- Files changed: 7 (Dockerfile, README, config/scribe.conf, example/README, example/docker-compose.yml, example/sender/main.go, scripts/docker-entrypoint.sh)
- LOC: +38 / -57
- Delta under review: 3 `LABEL org.opencontainers.image.*` lines at Dockerfile:71-73
## Overall Assessment
Clean. OCI label addition is correct, well-formed, properly placed. Cleanup portion unchanged from prior approval. Ready to commit.
## OCI Label Verification
### Key names — all spec-compliant
Verified against https://github.com/opencontainers/image-spec/blob/main/annotations.md:
- `org.opencontainers.image.version` — OK, "version of the packaged software"
- `org.opencontainers.image.description` — OK, "human-readable description"
- `org.opencontainers.image.source` — OK, "URL to get source code"
### Values — well-formed
- `"2.2"` — plain ASCII string, OK
- `"Facebook Legacy Scribe - real-time log aggregation daemon"` — ASCII hyphen (was `` U+2013 before cleanup), no smart quotes, no escaping issues
- `"https://github.com/facebookarchive/scribe"` — HTTP 200 verified, sane
### Placement — correct
Dockerfile:68-73 — runtime stage (`FROM centos:7`), after `LABEL maintainer`. Builder stage correctly left unlabeled (only runtime image gets metadata).
## Optional OCI labels worth considering (NOT auto-added)
Recommendations only — user decides:
| Label | Suggested value | Rationale |
|-------|----------------|-----------|
| `image.title` | `"Facebook Legacy Scribe"` | Short human-readable name, complements description. Most-commonly-used OCI label after version. Worth adding. |
| `image.licenses` | `"Apache-2.0"` | Scribe's license (SPDX expression). Useful for downstream consumers / compliance scans. Worth adding. |
| `image.url` | could point to this repo or README | Marginal value since `image.source` already covers provenance. Skip unless you want to distinguish "where to learn" from "where the source lives". |
| `image.revision` | scribe upstream git SHA at build time | Scribe is `--depth 1` cloned (Dockerfile:60) → revision drifts silently between rebuilds. Pinning would need a build ARG. Defer; not blocker. |
| `image.created` | RFC 3339 timestamp | BuildKit auto-injects this; manual labeling not needed. |
| `image.vendor` | `"tiennm99"` or org | Optional, marginal. Skip. |
**Strongest recommendation:** add `image.title` + `image.licenses`. The other four are skippable for an archive image.
## Diff Drift Check
Confirmed: full scribe-2.2 diff = (previously approved cleanup) + (3 OCI labels). No unintended edits.
Per-file:
- **Dockerfile** — cleanup (box-drawing comments, alpha-sorted builder deps, en-dash → hyphen) + OCI label swap. Clean.
- **README.md** — 2 en-dash → hyphen (cleanup). Clean.
- **config/scribe.conf** — 6 box-drawing/en-dash → ASCII (cleanup). Clean.
- **example/README.md** — 8 en-dash → hyphen (cleanup). Clean.
- **example/docker-compose.yml** — 1 en-dash → colon (cleanup). Clean.
- **example/sender/main.go** — 4 en-dash → hyphen/semicolon (cleanup, includes user-visible string `Hello from Go sender - message N/5`). Clean.
- **scripts/docker-entrypoint.sh** — 1 en-dash → hyphen (cleanup). Clean.
No new files. No deletions. No file moves.
## Critical Issues
None.
## High Priority
None.
## Medium Priority
None blocking. See "Optional OCI labels" above for incremental improvement.
## Low Priority
- CRLF warnings from git (LF will be replaced by CRLF on Windows checkout). Pre-existing repo behavior, not a delta concern.
## Positive Observations
- Migrated from non-standard `LABEL description` / `LABEL version` to OCI-standard keys — proper fix to the prior concern.
- All ASCII; no Unicode dashes carried over from cleanup pass into label values.
- Single-line `LABEL` per key (readable; multi-line continuation would also be fine, current style is clearer).
- `image.source` URL matches the actual upstream cloned at Dockerfile:60 (`https://github.com/facebookarchive/scribe.git`) — consistent.
## Recommended Actions
1. **Ship as-is** — no blockers.
2. *(Optional, recommend)* — add two more OCI labels before committing:
```dockerfile
LABEL org.opencontainers.image.title="Facebook Legacy Scribe"
LABEL org.opencontainers.image.licenses="Apache-2.0"
```
Confirm Scribe upstream license is Apache-2.0 first (facebookarchive/scribe LICENSE file).
## Metrics
- OCI spec conformance: 3/3 keys valid
- Label value well-formedness: pass
- Source URL reachability: HTTP 200
- Drift beyond approved cleanup: zero
## Unresolved Questions
- Do you want to add `image.title` + `image.licenses` now, or ship the minimal 3-label set? (Recommend adding both; trivial extra metadata.)
- Confirm Scribe upstream license = Apache-2.0 before setting `image.licenses` (worth a 5s check at https://github.com/facebookarchive/scribe/blob/master/LICENSE).
@@ -0,0 +1,61 @@
# Scribe OCI Image Labels — License & Metadata Verification
## Q1: License Identifier & Multi-License Analysis
**Finding: SPDX identifier is `Apache-2.0` (single license).**
### Evidence
- **Scribe upstream:** https://github.com/facebookarchive/scribe — Licensed under Apache License 2.0 (verified via LICENSE file).
- **SPDX value:** `Apache-2.0`
- **Quote:** "Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License."
### Multi-License Check (Thrift Dependency)
Dockerfile explicitly builds Apache Thrift 0.9.1 (line 28). Thrift is also Apache-2.0 licensed.
**Conclusion:** No additional license expression needed. Both Scribe and bundled Thrift are Apache-2.0. SPDX label value:
```
LABEL org.opencontainers.image.licenses="Apache-2.0"
```
Not required to use SPDX expression (`Apache-2.0 AND Apache-2.0`) — single `Apache-2.0` is correct.
---
## Q2: Additional OCI Image Annotations — Recommendation Table
| Label | Recommend? | Reason |
|-------|------------|--------|
| `image.title` | **YES** | Short human-readable name; trivial to add. E.g., `"Scribe Log Aggregation Daemon"` |
| `image.url` | NO | Project is archived (read-only); homepage is same as `image.source`. Redundant. |
| `image.documentation` | NO | No maintained docs site. README is in archived repo — already discoverable via `image.source`. Skip. |
| `image.vendor` | NO | Original author (Facebook) no longer maintains. Current maintainer is builder (you). Not load-bearing for legacy image. Skip. |
| `image.authors` | **MAYBE** | If you want attribution, add `"Tien Nguyen Minh"` (matches Dockerfile maintainer). Otherwise skip — not critical for archived image. |
| `image.created` | **YES** | Build timestamp is meaningful for reproducibility. Add via `--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')` at build time. |
| `image.revision` | NO | Dockerfile uses `git clone --depth 1` (line 58), losing commit history. Revision would be stale/misleading. Skip unless build args fix the clone depth. |
---
## Recommended Additions (Final Label Set)
```dockerfile
# Add these two lines to the LABEL block (lines 71-73)
LABEL org.opencontainers.image.title="Scribe Log Aggregation Daemon"
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL org.opencontainers.image.created="${BUILD_DATE}"
```
Optional (low priority):
```dockerfile
LABEL org.opencontainers.image.authors="Tien Nguyen Minh <tiennm99@outlook.com>"
```
Build command to inject BUILD_DATE:
```bash
docker build --build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" -t scribe:2.2 .
```
---
## Unresolved Questions
None. License is clear (Apache-2.0), dependency check complete, and OCI spec is authoritative.
@@ -0,0 +1,29 @@
# Couchbase 2.5 License & Source Verification
## Q1: License (SPDX Identifier)
**Finding:** `Apache-2.0`
**Evidence:** Wikipedia: "Couchbase Server is a packaged version of Couchbase's open source software technology and is available in a community edition without recent bug fixes with an Apache 2.0 license" ([Couchbase Server - Wikipedia](https://en.wikipedia.org/wiki/Couchbase_Server)). Couchbase Server 2.0 released Dec 2012; v2.5.2 inherits same Apache 2.0 license applied since open-source inception (2011, post Membase/CouchOne merger).
**SPDX ID:** `Apache-2.0`
No mixing of licenses in Server 2.5.x; only one license applies to the binary.
---
## Q2: `image.source` URL
**Recommendation:** `https://github.com/couchbase/docker`
**One-line reason:** General upstream repo URL maximizes durability; pinned commit hashes are infrastructure detail, not user-facing metadata.
**Alternatives rejected:**
- Pinned branch URLs (`…/tree/14f9e5f37…`) — commit references rot when branches are deleted or rebased; violates OCI-label durability principle (user should find repo, not fight dead links).
- Scribe precedent uses `https://github.com/facebookarchive/scribe` (upstream source root), not a pinned archive commit, so Couchbase should match that pattern.
---
## Unresolved Questions
None. Both values verified via public sources.
@@ -0,0 +1,94 @@
# Scribe-2.2 Cleanup Verification Report
**Date:** 2026-05-28
**Scope:** Cosmetic cleanup of Dockerfile, docker-compose.yml, and Go sender (em-dash → ASCII conversions)
**Status:** PASS — All checks successful. No regressions detected.
---
## Check 1: Go Sender Compile Verification
**Command:**
```bash
cd scribe-2.2/example/sender
go vet .
go build .
```
**Result:** ✓ PASS
- `go vet`: No issues reported
- `go build`: Compiled successfully to `scribe-example-sender.exe` (3.5 MB)
- No syntax errors, no dead code warnings
---
## Check 2: docker-compose.yml Syntax
**Command:**
```bash
docker compose -f scribe-2.2/example/docker-compose.yml config
```
**Result:** ✓ PASS
- Config validated and expanded successfully
- Services: `scribe` (using `ghcr.io/tiennm99/scribe:2.2`), `sender` (build from `./sender`)
- Environment variables correctly set: `SCRIBE_HOST=scribe`, `SCRIBE_PORT=1463`, `SCRIBE_COUNT=5`
- Volumes and depends_on constraints resolved correctly
- No syntax errors
---
## Check 3: Dockerfile Build (Skipped)
**Rationale:** Per instruction, skipped full `docker build` to avoid slow/brittle CentOS vault pull and multi-stage Thrift+Scribe compilation.
**Changes Reviewed:**
- Cosmetic comment reformatting: em-dashes → ASCII, ASCII arrows → ASCII
- No build-step logic changed
- Removed redundant LABEL fields (description, version) — no functional impact
---
## Check 4: Output String Verification
**File:** `scribe-2.2/example/sender/main.go` line 211
**Expected (per README):**
```
Hello from Go sender - message N/5
```
**Actual (post-cleanup):**
```go
fmt.Sprintf("Hello from Go sender - message %d/%d", i, count)
```
**Result:** ✓ MATCH
The em-dash `—` was converted to ASCII hyphen `-`. Format matches README claim exactly.
When `count=5` and loop runs `i=1..5`, output will be:
- `Hello from Go sender - message 1/5`
- `Hello from Go sender - message 2/5`
- ... (5 total)
---
## Summary
| Component | Status | Notes |
|-----------|--------|-------|
| Go sender (vet + build) | ✓ PASS | Binary compiles, no warnings |
| docker-compose.yml | ✓ PASS | Syntax valid, env vars & volumes correct |
| Dockerfile | ⊘ SKIPPED | Changes are cosmetic only; build step skipped per instruction |
| Output format | ✓ MATCH | Message string aligns with README |
---
## Verdict
**No regressions detected.** The cleanup is purely cosmetic (em-dash → ASCII character conversions in comments and one log-message string). The Go sender will compile and run identically, and the compose stack will deploy as before.
---
## Unresolved Questions
None. All checks complete and passing.