signed-* release tag. Beta/Production deploys are gated by manual approval and the C3 Four-Eyes Principle (approver ≠ requester). Every container reports COMMIT_HASH, DOCKER_TAG, and APP_VERSION via /health so the discovery step can be automated.
1. Overview
This document specifies the continuous integration and continuous deployment (CI/CD) pipeline architecture for a monorepo environment containing multiple applications. The pipeline supports both traditional releases (deploying new code) and snapshot releases (promoting existing deployed state), with cryptographic authorization via signed tags.
Key Characteristics
- Monorepo model: Multiple apps built from a single
mainbranch - Sequential build numbers: Docker images tagged with incrementing build numbers (22, 33, 44...)
- Multi-environment: Alpha (dev) auto-deployment, Beta/Production manual releases
- Signed tag governance: Integration with SDLC-PLAYBOOK authorization framework
- Dual tagging: Same release tag applied to both Git commits and Docker images
Intended Audience
- DevOps engineers implementing pipeline infrastructure
- Development teams triggering builds and releases
- IT operations managing production deployments
- Product managers authorizing releases
1.1 How to Read This Document
This is a comprehensive technical specification (~750 lines). Different readers have different needs:
If you're new to the system (30 minutes):
- Read Section 1 (Overview) for key characteristics and audience → 5 min
- Review Section 2 (Architecture Principles) for core concepts → 10 min
- View the ASCII Timeline (hero section above) to visualize the two-layer model → 5 min
- Jump to Section 6 (Snapshot Releases) to understand the key differentiator → 15 min
If you're implementing the pipeline (focus on mechanics):
- Start with Section 3 (Pipeline Stages) for CI/CD/Release workflows
- Reference Section 4 (Environment Matrix) for trigger patterns and approval gates
- Study Section 5 (Tag Lifecycle) for Docker tagging strategy
- Consult Section 2.3 (Environment Variable Contract) for the runtime env-var contract and example
- See
/scripts/release-tagger.jsfor snapshot automation code
If you're operating the system (day-to-day usage):
- Familiarize yourself with Section 6 (Snapshot Releases) for operational workflows
- Bookmark Section 7 (Roles and Responsibilities) for authority boundaries
- Keep Section 8 (Operational Notes) handy for evidence chain and governance integration
- Reference Appendix: Glossary for terminology clarification
If you're troubleshooting:
- Check Section 6.2 (Discovery Methods) for querying deployed state
- Review Section 8.2 (Evidence Chain Integration) for traceability queries
- Inspect environment variables per Section 2.4 contract
- Cross-reference with SDLC-PLAYBOOK Appendix A for snapshot workflow details
2. Architecture Principles
2.1 Monorepo Build Model
The pipeline operates on a monorepo where:
- All apps share a single Git repository with a unified
mainbranch - Each app/package maintains its own semantic version (v2.3.0, v1.0.6)
- Commits to
maintrigger CI pipeline evaluation to detect changed apps - Only apps with changes (source code or dependencies) are rebuilt
Incremental build strategy:
- The CI pipeline compares current commit to previous commit (
git diff) - Apps with changed files trigger Docker image builds
- Dependency changes (library versions) also trigger rebuilds to capture snapshots
2.2 Docker Image Tagging Strategy
Each Docker image receives three types of tags:
| Tag Type | Format | Example | Purpose |
|---|---|---|---|
| Sequential | Plain integers (monorepo-wide) | 22, 27, 31, 41 |
Chronological build order |
| Semantic | Per-app semver | v2.3.0, v1.0.6, v0.9.3 |
Human-readable app version |
| Signed | signed-YYYY-MM-DD.N (date + sequence) |
signed-2026-02-15.1, signed-2026-04-30.1 |
Governance/authorization marker |
2.3 Environment Variable Contract
On deployment, each app container receives environment variables capturing full traceability. These variables form the contract between the CI/CD pipeline and runtime applications, enabling discovery, debugging, and audit trails.
Lifecycle Table
| Variable | Set By | Set When | Consumed By | Purpose |
|---|---|---|---|---|
COMMIT_HASH |
CI Pipeline | Docker build | App /health endpoint, DevOps scripts, monitoring | Git traceability - links runtime to source |
DOCKER_TAG |
CI Pipeline | Docker build | Discovery scripts (release-tagger.js), operations | Sequential build number for ordering |
GIT_TAG |
Release Pipeline | Deployment | Monitoring dashboards, audit logs | Identifies release version (signed-*) |
APP_VERSION |
CI Pipeline (from package.json) | Docker build | UI footer, /health endpoint, support tickets | User-facing semantic version |
ENVIRONMENT |
Deployment script | Deployment | App runtime (feature flags, config selection) | Environment-specific behavior |
BUILD_DATE |
CI Pipeline | Docker build | Support, debugging | Temporal correlation |
DEPLOY_DATE |
Release Pipeline | Deployment | Operations, SLA tracking | Deployment timeline |
Critical for Snapshot Discovery
The following variables must be exposed via /health endpoint for automated snapshot discovery:
COMMIT_HASH- To determine which Git commit produced the running codeDOCKER_TAG- To identify the sequential build number (used for "highest tag" logic)APP_VERSION- For human readability in release manifests
Example Contract
# Git metadata
COMMIT_HASH=d4e5f6789012345678901234567890abcdef0123
GIT_TAG=signed-2026-02-15.1
GIT_BRANCH=main
# Docker metadata
DOCKER_TAG=27
DOCKER_IMAGE=duckbook.azurecr.io/app1:27
# App metadata
APP_VERSION=2.3.0
BUILD_DATE=2026-02-14T12:34:56Z
# Environment context
ENVIRONMENT=beta
Usage scenarios:
- Discovery: Query /health on all Alpha apps → extract DOCKER_TAG → find highest tag → identify release candidate
- Debugging: Support ticket references APP_VERSION=2.3.0 → lookup COMMIT_HASH → git show d4e5f6 → view exact source
- Audit trails: Compliance query "What was running in production on 2026-02-15?" → Check GIT_TAG and DEPLOY_DATE from environment variables
3. Pipeline Stages
3.1 Continuous Integration (CI)
Continuous Integration Pipeline
Trigger: Commit to main branch
3.2 Continuous Deployment (CD) - Alpha Environment
Continuous Deployment (Alpha)
Characteristics:
- Fully automated - no manual approval
- Continuous updates - every commit to
maindeploys - Dev team access - for testing and validation
3.3 Release Pipeline - Beta/Production
Release Pipeline (Beta/Production)
Trigger: Detection of signed-* tag in Git repository
4. Environment Deployment Matrix
4.1 Pipeline Trigger Patterns
| Event | CI Pipeline | CD Pipeline (Alpha) | Release Pipeline (Beta/Prod) |
|---|---|---|---|
Commit to main |
✅ Build all changes | ✅ Auto-deploy | ❌ No trigger |
Create rc-v* tag |
✅ Build + security scan | ❌ No deploy | ❌ No trigger |
Create signed-* tag |
❌ No build | ❌ No deploy | ✅ Trigger + manual approval |
| Manual trigger | ⚠️ Optional rebuild | ⚠️ Optional redeploy | ✅ Standard release workflow |
| Dependency update | ✅ Rebuild affected apps | ✅ Auto-deploy | ❌ No trigger |
Legend: ✅ = Automatic trigger · ❌ = No action · ⚠️ = Available via manual trigger only
4.2 Environment Characteristics
Alpha (Dev)
- Deployment: Automatic on commit
- Tags: Sequential (22, 27, 31, 41...)
- Approval: None
- Frequency: Continuous (daily)
- Rollback: Automatic
- Access: Dev team
Beta (Staging)
- Deployment: Manual (signed tags)
- Tags: signed-2026-02-15.1
- Approval: Manual (C3)
- Frequency: Weekly/monthly
- Rollback: Manual with approval
- Access: Dev + IT + stakeholders
Production
- Deployment: Manual (signed tags)
- Tags: signed-2026-04-30.1
- Approval: Manual (C3)
- Frequency: Quarterly or on-demand
- Rollback: Manual with approval
- Access: IT ops + executives
5. Docker Tag Lifecycle
5.1 Tag Progression Example
| Commit | App | Sequential | Semantic | Signed Tag | Environment |
|---|---|---|---|---|---|
a1b2c3 |
app1 | 22 | v2.2.0 | - | Alpha |
d4e5f6 |
app1 | 27 | v2.3.0 | - | Alpha |
g7h8i9 |
app1 | 31 | v2.3.1 | signed-2026-02-15.1 |
Beta |
abc245 |
app2 | 35 | v2.0.3 | - | Alpha |
j1k2l3 |
app1 | 41 | v3.0.0 | signed-2026-04-30.1 |
Production |
5.2 Tag Timeline Visualization
6. Snapshot-Based Releases
6.1 Conceptual Model
What is a snapshot release?
A snapshot release promotes existing deployed apps (already running in Alpha) to Beta/Production WITHOUT rebuilding code. Instead of deploying new commits, we "snapshot" the current state and tag it for promotion.
Why snapshot releases exist:
- Reduce risk: Deploy exactly what's been tested in Alpha for days/weeks
- Decouple timing: Enable quarterly releases even if dev work is paused or incomplete
- Operational flexibility: Promote stable state without waiting for new features
- Incremental validation: Test in Alpha continuously, promote when confidence is high
Key difference from traditional releases:
| Aspect | Traditional Release | Snapshot Release |
|---|---|---|
| Trigger | NEW code committed | EXISTING deployment stable |
| Process | Commit → Build → Deploy | Discover → Tag → Promote |
| Risk | Deploying untested code | Deploying pre-validated code |
| Timing | Coupled to dev completion | Decoupled from dev work |
| Example | "Deploy feature X that merged today" | "Promote whatever's in Alpha to Beta" |
Visual model:
Traditional: [New Code] → CI Build → Alpha → Manual Tag → Beta
Snapshot: [Alpha State] → Discovery → Manual Tag → Beta
↑
(Already deployed,
already tested)
Stable apps: same bytes, new tag
A monorepo typically contains apps that change rarely — internal tools, settled services, libraries that haven't needed a fix in months. In the hero timeline, app4 (v1.2.0, build #5) is one of these: it hasn't been rebuilt since its initial deploy, but it must still ship as part of every signed release. Snapshot releases handle this naturally — we apply the new signed-* Docker tag to the existing app4:5 image. No rebuild, no version bump; the bytes that have been running in Alpha for months are the same bytes that ship to Beta and Production. Traditional release flows would force a rebuild here just to attach a new tag; snapshot flows don't.
6.2 Discovery Workflow: Highest Tag Logic
When creating a snapshot release, we need to determine the Git commit that represents the current Alpha state. Since multiple apps may have been built from different commits, we use the highest Docker tag as the anchor point.
Definition: "Highest Tag"
Highest tag = max(DOCKER_TAG) across all deployed apps in target environment
- Sequential Docker tags are monotonically increasing across the monorepo (22, 33, 44...)
- The app with the highest tag represents the most recent build
- Git commits are immutable snapshots of the entire monorepo at a point in time
- The commit associated with the highest tag captures all code deployed up to that point
Discovery Algorithm
1. Query /health endpoint for each app in Alpha
2. Extract DOCKER_TAG from each response
3. Identify highest_tag = max(DOCKER_TAG)
4. Find commit_hash for the app with highest_tag
5. Use commit_hash as Git tag target for signed-* release tag
Example: Highest Tag Selection
Scenario: Four apps deployed in Alpha (includes app4, a stable app with no recent rebuilds)
| App | COMMIT_HASH | DOCKER_TAG | APP_VERSION | Deployed At |
|---|---|---|---|---|
| app1 | g7h8i9 | 31 | v2.3.1 | 2026-02-10 ← HIGHEST |
| app2 | p6q7r8 | 13 | v1.0.6 | 2026-01-20 |
| app3 | e1f2g3 | 19 | v0.9.3 | 2026-02-05 |
| app4 | n0o1p2 | 5 | v1.2.0 | 2025-08-15 (stable; no rebuild) |
Result:
- Highest tag =
31(from app1) - Release candidate commit =
g7h8i9 - Git tag:
git tag signed-2026-02-15.1 g7h8i9
Insight: The commit g7h8i9 (tag 31) is an immutable snapshot of the monorepo that includes the current source for every app — including app4, which hasn't been rebuilt in months. We tag the existing app4 image (build #5) with the same signed-* tag; no rebuild is required because its bytes haven't changed.
Edge Cases
Case 1: App not rebuilt in months (the "stable app" case)
app1: tag 27 (rebuilt 2 weeks ago)
app4: tag 5 (rebuilt 9 months ago - stable, no changes)
app3: tag 19 (rebuilt yesterday)
Outcome: Highest tag = 27 (app1). The commit for tag 27 includes all code, including app4's unchanged state from 9 months ago. The existing app4:5 image is tagged with the new signed-* alias — no rebuild.
Case 2: Multiple apps with same highest tag
Commit abc245 changed app1 AND app2:
app1: tag 34
app2: tag 35 ← Both from same commit
Outcome: Either tag 34 or 35 can be used — they point to the same commit (abc245).
Case 3: App deleted from monorepo
app1: tag 41
app2: tag 38
app3: REMOVED (no longer exists)
Outcome: Highest tag = 41. Old app3 Docker images remain in registry but are not deployed.
Discovery Methods
Apps expose version information through multiple channels:
| Method | Access Pattern | Example Output | Use Case |
|---|---|---|---|
| Environment Vars | echo $COMMIT_HASH |
g7h8i9d4e5f6 |
Direct server access |
| Health Endpoint | GET /health |
{"commit":"g7h8i9","tag":31} |
Automated discovery (primary) |
| UI Footer | Visual inspection | v2.3.1 (g7h8i9) #31 |
Manual verification |
| Container Metadata | docker inspect <image> |
Labels: commit=g7h8i9, tag=31 |
Registry queries |
| Azure Portal | View environment vars | COMMIT_HASH=g7h8i9, DOCKER_TAG=31 |
IT operations |
Health endpoint response example:
{
"status": "healthy",
"version": {
"app": "2.3.1",
"commit": "g7h8i9d4e5f6",
"commitShort": "g7h8i9d",
"tag": "signed-2026-02-15.1",
"branch": "main"
},
"docker": {
"image": "duckbook.azurecr.io/app1:31",
"tag": "31",
"registry": "duckbook.azurecr.io"
},
"build": {
"number": 31,
"date": "2026-02-10T12:34:56Z"
},
"environment": "beta",
"deployedAt": "2026-02-15T15:20:10Z"
}
6.3 Operational Workflow
This section describes the step-by-step procedure for creating and deploying a snapshot release.
Step-by-Step Flowchart
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 1: READINESS ASSESSMENT (Manual - PM/DevOps) │
└─────────────────────────────────────────────────────────────────┘
↓
[PM determines Alpha is stable and ready]
↓
[DevOps queries deployed state in Alpha]
↓
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 2: DISCOVERY (Automated - release-tagger.js) │
└─────────────────────────────────────────────────────────────────┘
↓
$ node scripts/release-tagger.js --discover --env alpha
↓
Query /health on all apps → Extract DOCKER_TAG
↓
Find max(DOCKER_TAG) → Identify commit_hash
↓
Output:
app1: commit g7h8i9, tag 31 ← HIGHEST
app2: commit p6q7r8, tag 13
app3: commit e1f2g3, tag 19
app4: commit n0o1p2, tag 5 (stable, no rebuild)
Release candidate: commit g7h8i9
↓
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 3: APPROVAL GATE (Manual - C3 Handshake) │
└─────────────────────────────────────────────────────────────────┘
↓
[Create release ticket: REL-2026-02-15]
↓
[Requester documents readiness evidence]
↓
[Approver reviews (approver ≠ requester)]
↓
[Approver grants authorization]
↓
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 4: DUAL TAGGING (Automated - release-tagger.js) │
└─────────────────────────────────────────────────────────────────┘
↓
$ git tag signed-2026-02-15.1 g7h8i9
$ git push origin signed-2026-02-15.1
↓
$ node scripts/release-tagger.js --tag signed-2026-02-15.1 --auto
↓
Tags applied to Docker images:
duckbook.azurecr.io/app1:31 → signed-2026-02-15.1
duckbook.azurecr.io/app2:13 → signed-2026-02-15.1
duckbook.azurecr.io/app3:19 → signed-2026-02-15.1
duckbook.azurecr.io/app4:5 → signed-2026-02-15.1 (stable)
↓
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 5: PIPELINE DEPLOYMENT (Automated - Azure Pipelines) │
└─────────────────────────────────────────────────────────────────┘
↓
Azure Pipeline detects signed-* Git tag
↓
Pull Docker images by signed tag
↓
Deploy to Beta environment
↓
Update /governance/ledger.md
↓
┌─────────────────────────────────────────────────────────────────┐
│ DEPLOYED: Beta/Production │
│ All apps now running signed-2026-02-15.1 │
└─────────────────────────────────────────────────────────────────┘
Automation Script Example
The /scripts/release-tagger.js tool automates discovery and tagging:
# ═══════════════════════════════════════════════════════════
# SNAPSHOT RELEASE EXAMPLE
# ═══════════════════════════════════════════════════════════
# Step 1: Discover current deployed state
node scripts/release-tagger.js --discover --env alpha
# Output:
# app1: commit g7h8i9, docker tag 31 ← HIGHEST
# app2: commit p6q7r8, docker tag 13
# app3: commit e1f2g3, docker tag 19
# app4: commit n0o1p2, docker tag 5 (stable, no rebuild)
#
# Release candidate: commit g7h8i9 (tag 31)
# Step 2: Create Git tag (manual - requires authorization)
git tag signed-2026-02-15.1 g7h8i9
git push origin signed-2026-02-15.1
# Step 3: Tag Docker images (automated)
node scripts/release-tagger.js --tag signed-2026-02-15.1 --auto
# This command tags:
# • duckbook.azurecr.io/app1:31 → signed-2026-02-15.1
# • duckbook.azurecr.io/app2:13 → signed-2026-02-15.1
# • duckbook.azurecr.io/app3:19 → signed-2026-02-15.1
# • duckbook.azurecr.io/app4:5 → signed-2026-02-15.1 (existing image; bytes unchanged)
# Step 4: Pipeline auto-triggers on signed-* tag
# Azure Pipeline → Manual Approval (C3) → Deploy to Beta
Human + Automation Handoffs
| Phase | Actor | Type | Tool/Action |
|---|---|---|---|
| Readiness Assessment | PM/DevOps | Manual | Visual inspection, testing, stakeholder consensus |
| Discovery | Automation | Automated | release-tagger.js --discover |
| Approval Gate | Approver | Manual | C3 handshake, ticket review |
| Dual Tagging (Git) | DevOps | Manual | git tag signed-* |
| Dual Tagging (Docker) | Automation | Automated | release-tagger.js --tag |
| Pipeline Deployment | Automation | Automated | Azure Pipelines |
| Manual Approval (C3) | Approver | Manual | Azure Pipeline approval gate |
| Ledger Update | Automation | Automated | Pipeline script |
6.4 Governance & Rules
Snapshot releases follow the same governance framework as traditional releases, integrated with the SDLC-PLAYBOOK authorization model.
Authorization Requirements
C3 Signed Handshake (Four-Eyes Principle):
- Approver identity ≠ Requester identity - Enforced programmatically by Azure Pipeline
- Security scan results = "Green" - Mend.io scans from original builds must have passed
- Functional validation completed - Evidence of Alpha testing (demo, walkthrough, logs)
- Bet Record linkage - Release ticket references business justification
Evidence Chain
Artifacts created for each snapshot release:
| Artifact | Location | Content | Purpose |
|---|---|---|---|
| Release manifest | /releases/signed-2026-02-15.1.json |
Commit-to-tag mapping for all apps | Traceability |
| Ledger entry | /governance/ledger.md |
Approver identity, timestamp, ticket ID | Audit trail |
| Git tag | Repository | signed-2026-02-15.1 → commit g7h8i9 |
Source control anchor |
| Docker tags | Container registry | app1:31 → signed-2026-02-15.1 (all apps) |
Deployment artifact |
| Pipeline logs | Azure DevOps | Deployment steps, approval records | Operational record |
Evidence chain flow:
Snapshot workflow:
Deployment State → Discovery → Ticket (REL-2026-02-15) → C3 Approval →
signed-* Git tag → Docker tags → Manifest → Ledger Entry → Deployment
Comparison to traditional workflow:
Traditional workflow:
Bet Record → Commits → rc-* tag → Build → Pipeline Logs →
signed-* tag → Ledger Entry → Deployment
Integration with SDLC-PLAYBOOK
- Professional Handshake Protocol: Snapshot releases use Milestone-based handshake (Section 2.2 of SDLC-PLAYBOOK)
- Triple Guardrail Controls: C3 (Four-Eyes Principle) enforced via Azure Pipeline approval gates
- Artifact Library: Release manifests stored in
/releases/directory alongside traditional release artifacts
7. Roles and Responsibilities
7.1 Development Team
- Commit code to
mainbranch following Trunk-Based Development practices - Monitor CI pipeline for build failures
- Validate deployments in Alpha environment
- Create
rc-*tags to signal release candidates (traditional workflow) - Discover deployed state for snapshot releases (query /health endpoints)
- Request release approval from authorized approvers
7.2 IT Operations
- Monitor Beta/Production environments
- Execute manual approval gates in Release Pipeline
- Validate release manifests against deployed state
- Perform rollbacks if issues detected post-deployment
- Maintain container registry (duckbook.azurecr.io)
7.3 Authorization Gates
Every production release must pass through the C3 Signed Handshake (Four-Eyes Principle):
- Approver identity ≠ Requester identity
- Security scan results = "Green" (Mend.io block mode passed)
- Functional validation completed (demo or walkthrough)
- Bet Record exists and is linked in commit message or manifest
8. Operational Notes
8.1 Manual Release Triggers
Release pipeline triggers are intentionally manual. This design enforces:
- Evidence-backed deployments - Every release must have authorization artifact
- Deliberate promotion - Production changes are conscious decisions
- Governance compliance - Manual gate ensures C3 Signed Handshake is verified
- Blast radius control - Prevents cascading failures from auto-promoting broken builds
8.2 Evidence Chain Integration
The CI/CD pipeline integrates with the SDLC-PLAYBOOK governance framework:
Traditional workflow:
Bet Record → Commits → rc-* tag → Pipeline Logs → signed-* tag → Ledger Entry
Snapshot workflow:
Deployment State → Discovery → signed-* tag → Manifest → Ledger Entry
Appendix: Glossary
| Term | Definition |
|---|---|
| Sequential Tag | An incrementing build number (22, 33, 44...) assigned by CI pipeline to each Docker image build. Shared across all apps in monorepo. |
| Semantic Tag | A per-app version tag following semver (v2.3.0, v1.0.6) representing the app's release version. |
| Signed Tag | A signed-* tag (e.g., signed-2026-02-15.1) representing authorized release approval. |
| Snapshot Release | A release created by tagging the current deployed state across all apps, rather than deploying new code. |
| Dual Tagging | Applying the same signed-* tag to both Git repository (commit) and Docker images in container registry. |
| Discovery Phase | Querying deployed apps to determine their current commit hashes and Docker tags before creating a snapshot release. |
| Highest Tag | In monorepo, the Docker image with the highest sequential tag number, indicating the most recent build. |
| C3 Handshake | Four-Eyes Principle: production promotion requires approval from an identity other than the requester. |
| Monorepo | Single Git repository containing multiple apps/packages with unified version control and shared dependencies. |
| Evidence Chain | Traceable linkage from business requirement → code → security scan → approval → deployment. |
SDLC-PLAYBOOK - Governance framework and authorization protocols
SDLC-PLAYBOOK Appendix A - Snapshot release operational guide
/scripts/release-tagger.js - Snapshot automation tool/releases/README.md - Release manifest documentation/governance/ledger.md - Production release history