CI/CD Reliability Architecture
Out of scope: Business logic (architecture-guidelines), value-stream
optimization (system-optimization). Code style and release procedures
follow your project's own conventions.
Core Directives
- Idempotent — same result every run (§1).
- Self-Contained — explicit inputs, outputs, failure mode (§2).
- Immutable Artifacts — build once, promote; config at deploy time (§3).
- Self-Healing — retry transient, fail-fast permanent (§4).
- Zero-Downtime — preview environment, atomic promotion (§5).
- Zero-Knowledge — OIDC / federated identity, no stored secrets (§6).
1. Idempotency
| Anti-Pattern |
Fix |
Why |
npm install |
npm ci |
Lock-file exact match |
mkdir build |
mkdir -p |
No-op if exists |
| Delete then create |
Create then delete |
Gap causes downtime |
git commit --amend published |
Create new commit |
Never amend pushed work |
| Assume upstream state |
Explicit needs: + download artifacts |
Prevents race conditions |
Checklist:
2. Self-Contained Jobs
Each job declares inputs, steps, outputs, failure mode:
jobs:
build:
needs: [lint, test] # Explicit upstream dependencies
outputs:
artifact: BUILD_PATH # Named output; consumers reference by name
steps:
- name: Download artifacts
download: test-results # Explicit artifact fetch; never assume presence
- name: Setup runtime
tool: node@20
cache: npm # Caching is NOT implicit state — it is a performance hint
- name: Build
id: build
run: npm run build
continue-on-error: false # Fail-fast
timeout: 15m # Prevent stuck jobs
Rules:
- Never assume upstream state; always download artifacts explicitly
- Caching (dependencies, browsers, etc.) does not violate self-containment — it
is scoped performance optimization within job isolation
- Namespace all artifacts uniquely (e.g., include branch name or PR number)
- Never write to shared paths without explicit scoping
- Declare failure mode explicitly (
continue-on-error or default fail-fast)
3. Immutable Artifacts
Principle: Build once, promote the same artifact across environments. Never
rebuild to change target environment.
| Anti-Pattern |
Fix |
Why |
| Rebuild per environment |
Build once; promote the same output |
Eliminates "works in staging" divergence |
| Bake URLs/secrets into build output |
Inject config at deploy time (env vars, config files) |
Same artifact, different config |
| Tag artifacts with branch name only |
Tag with commit SHA (+ optional semver) |
SHA is immutable; branch names move |
| Store artifacts only in CI cache |
Publish to an artifact registry |
Decouples build from deploy; enables rollback |
Rules:
- The build step produces a versioned, immutable artifact (archive, image,
bundle) tagged with the commit SHA
- Environment-specific values (API URLs, feature flags, secrets) are injected at
deploy time, never at build time
- Promoting to production means deploying the same artifact that passed
staging — not triggering a new build
- Rollback means redeploying a previous known-good artifact, not reverting
code and rebuilding
- Never delegate the build to the deploy platform's implicit builder (Oryx,
Cloud Native Buildpacks, Vercel/Netlify auto-build, etc.). Platform builders
frequently report
success even when a sub-build (TS compile, webpack, native
module) fails, silently shipping stale or incomplete artifacts. Run every
build in a dedicated CI step with continue-on-error: false, and pass the
pre-built output to the deploy action (skip_app_build: true,
skip_api_build: true, or equivalent)
4. Self-Healing
| Failure Type |
Retry? |
Example |
| HTTP 5xx, timeout, ECONNREFUSED |
Yes |
Retry 3x with 5s, 10s, 20s delays |
| HTTP 4xx, missing file, syntax error |
No |
Fail immediately; fix code |
| Disk full, out of memory |
No |
Escalate to ops |
Exponential backoff (sufficient for single-step retry):
for attempt in 1 2 3; do
command && exit 0
delay=$((5 * 2 ** (attempt - 1))) # 5s, 10s, 20s
sleep "$delay"
done
exit 1
Post-deploy health check (mandatory):
- name: Deploy
run: ./deploy.sh
timeout: 15m # Prevent stuck jobs
- name: Health Check
run: curl -f https://deployed-url/health || exit 1
timeout: 5m
- name: Rollback on Failure
on_failure: true
run: ./rollback.sh
Rules:
- Always set an explicit timeout on every long-running step (prevents default
hangs)
- Transient failures: retry 3x with exponential backoff + jitter
- Permanent failures: fail fast, no retry
- All deployments must emit a health signal; rollback on failure
- Never apply partial state
5. Zero-Downtime
| Layer |
Pattern |
Why |
| Frontend |
Deploy to isolated preview environment per PR; atomically promote to production on merge |
Production untouched during validation; safe to retry |
| Backend |
Deploy to staging slot; health-check; swap (platform handles connection draining) |
Graceful shutdown; in-flight requests complete |
| API versioning |
New endpoints /v2/... alongside /v1/...; deprecate, never delete |
Clients remain backwards-compatible |
| PR concurrency |
Cancel in-progress runs for the same branch; only latest commit deploys |
Prevent old commits overwriting newer deployments |
Rules:
- Never force-stop running instances (drops in-flight connections)
- Always test in a staging/preview environment before promoting to production
- Adding fields to API payloads is safe; removing or renaming fields breaks
clients
- If E2E tests fail on a preview environment: block the merge; preview
auto-cleaned on PR close
- For multi-tenant data layers, apply the Expand/Contract pattern for schema
changes
6. Zero-Knowledge Secrets
Principle: Minimize permanent credentials. Prove identity via
challenge/signature (OIDC), not secret exchange.
| Credential Type |
Store as long-lived secret? |
How to obtain at runtime |
Notes |
| Cloud provider auth |
1 federated identity only |
OIDC federated credential |
Rotate quarterly; no password |
| API keys |
Only if no OAuth available |
STS / service principal exchange |
Auto-expire; never store value |
| Encryption / HMAC keys |
1 per environment |
Generate once; rotate via dual-deploy |
Create new, apply, delete old |
| DB connection strings |
Never |
Managed Identity / service binding |
No secret needed |
| OAuth client secrets |
Never |
Client credentials + certificate |
Certificate-based auth |
OIDC pattern (pseudocode):
permissions:
id-token: write # Pipeline requests a short-lived identity token
contents: read
jobs:
deploy:
steps:
- name: Authenticate to cloud (OIDC)
# CI platform presents signed JWT to cloud provider's STS.
# Cloud issues short-lived access token — no stored credential exchanged.
cloud-login:
method: oidc
client-id: $SECRET_CLIENT_ID
Secret rotation (zero-downtime):
- Create new credential
- Apply new credential everywhere it is used
- Verify all consumers are using the new credential
- Delete the old credential
Audit logging (mandatory):
- Log all secret reads: timestamp, actor, resource, purpose
- Never log secret values
- Enable audit logging on your secrets manager
Secret hygiene:
- Enable secret scanning in your SCM (passive, on every push)
- Block accidental commits of
.env, keys, credentials via .gitignore +
pre-commit hooks
- If a secret leaks: rotate immediately, revoke old credential, audit access
logs
7. Infrastructure Idempotency
For ad-hoc environment config, imperative CLI commands are acceptable when they
are idempotent (create-or-update semantics, --no-fail-on-existing guards).
They become fragile at scale.
When managing infrastructure at scale (multi-tenant, scaling policies, resource
groups), use declarative IaC (Bicep, Terraform, Pulumi). Declarative tools
enforce idempotency by design; imperative scripts require manual guards.
Delete-and-Recreate Pattern (Immutable Resources)
Some resources cannot be updated in-place (e.g., AWS security groups, Azure
Entra policies, some Kubernetes resources). For these, use definition-based
comparison to detect changes and safely replace:
# 1. Compute hash of desired state
DESIRED_HASH=$(echo "${DEFINITION}" | sha256sum | cut -d' ' -f1)
# 2. Fetch existing resource and hash its definition
EXISTING=$(curl -s https://api/resource/current)
EXISTING_HASH=$(echo "${EXISTING}" | sha256sum | cut -d' ' -f1)
# 3. If unchanged, skip (idempotent)
if [ "${DESIRED_HASH}" = "${EXISTING_HASH}" ]; then
echo "Resource up-to-date, skipping"
exit 0
fi
# 4. If changed, delete old and create new (atomic from API perspective)
curl -X DELETE https://api/resource/current
curl -X POST https://api/resource -d "${DEFINITION}"
Rules:
- Always hash/checksum the definition, not just presence checks
- Delete before create (not after) to avoid transient conflicts
- Wrap creation in idempotent guard (e.g., check if already exists)
- Log state transitions: "definition changed, updating"
8. Pre-Merge Checklist
CRITICAL (Must-Have)
ADVANCED (Nice-to-Have)
9. See also
defect-shift-left — where each pipeline check belongs on the stage ladder.
system-optimization — value-stream optimization built on top of a reliable pipeline.
architecture-guidelines — first-principles rules out of scope here (idempotency etc. as system-level concerns).
Source: l-gevity/l-gevity-skills — distributed by TomeVault.
1---2name: l-gevity-l-gevity-skills-ci-cd-reliability-architecture3description: CI/CD Reliability Architecture4---56# CI/CD Reliability Architecture78> **Out of scope**: Business logic (`architecture-guidelines`), value-stream9> optimization (`system-optimization`). Code style and release procedures10> follow your project's own conventions.1112> **Core Directives**13>14> 1. **Idempotent** — same result every run (§1).15> 2. **Self-Contained** — explicit inputs, outputs, failure mode (§2).16> 3. **Immutable Artifacts** — build once, promote; config at deploy time (§3).17> 4. **Self-Healing** — retry transient, fail-fast permanent (§4).18> 5. **Zero-Downtime** — preview environment, atomic promotion (§5).19> 6. **Zero-Knowledge** — OIDC / federated identity, no stored secrets (§6).2021---2223## 1. Idempotency2425| Anti-Pattern | Fix | Why |26| ------------------------------ | -------------------------------------- | ------------------------ |27| `npm install` | `npm ci` | Lock-file exact match |28| `mkdir build` | `mkdir -p` | No-op if exists |29| Delete then create | Create then delete | Gap causes downtime |30| `git commit --amend` published | Create new commit | Never amend pushed work |31| Assume upstream state | Explicit `needs:` + download artifacts | Prevents race conditions |3233**Checklist:**3435- [ ] Produces same output if run 1x, 3x, or 0x36- [ ] File operations use safe defaults (`-p`, `-f`, `--force`)37- [ ] Secrets: create new first, apply everywhere, then delete old38- [ ] DB updates use conditional writes (`WHERE version = X`)3940---4142## 2. Self-Contained Jobs4344Each job declares **inputs**, **steps**, **outputs**, **failure mode**:4546```yaml47jobs:48 build:49 needs: [lint, test] # Explicit upstream dependencies50 outputs:51 artifact: BUILD_PATH # Named output; consumers reference by name52 steps:53 - name: Download artifacts54 download: test-results # Explicit artifact fetch; never assume presence55 - name: Setup runtime56 tool: node@2057 cache: npm # Caching is NOT implicit state — it is a performance hint58 - name: Build59 id: build60 run: npm run build61 continue-on-error: false # Fail-fast62 timeout: 15m # Prevent stuck jobs63```6465**Rules:**6667- Never assume upstream state; always download artifacts explicitly68- Caching (dependencies, browsers, etc.) does not violate self-containment — it69 is scoped performance optimization within job isolation70- Namespace all artifacts uniquely (e.g., include branch name or PR number)71- Never write to shared paths without explicit scoping72- Declare failure mode explicitly (`continue-on-error` or default fail-fast)7374---7576## 3. Immutable Artifacts7778**Principle**: Build once, promote the same artifact across environments. Never79rebuild to change target environment.8081| Anti-Pattern | Fix | Why |82| ----------------------------------- | ----------------------------------------------------- | --------------------------------------------- |83| Rebuild per environment | Build once; promote the same output | Eliminates "works in staging" divergence |84| Bake URLs/secrets into build output | Inject config at deploy time (env vars, config files) | Same artifact, different config |85| Tag artifacts with branch name only | Tag with commit SHA (+ optional semver) | SHA is immutable; branch names move |86| Store artifacts only in CI cache | Publish to an artifact registry | Decouples build from deploy; enables rollback |8788**Rules:**8990- The build step produces a **versioned, immutable artifact** (archive, image,91 bundle) tagged with the commit SHA92- Environment-specific values (API URLs, feature flags, secrets) are injected at93 **deploy time**, never at build time94- Promoting to production means deploying the **same artifact** that passed95 staging — not triggering a new build96- Rollback means redeploying a **previous known-good artifact**, not reverting97 code and rebuilding98- **Never delegate the build to the deploy platform's implicit builder** (Oryx,99 Cloud Native Buildpacks, Vercel/Netlify auto-build, etc.). Platform builders100 frequently report `success` even when a sub-build (TS compile, webpack, native101 module) fails, silently shipping stale or incomplete artifacts. Run every102 build in a dedicated CI step with `continue-on-error: false`, and pass the103 pre-built output to the deploy action (`skip_app_build: true`,104 `skip_api_build: true`, or equivalent)105106---107108## 4. Self-Healing109110| Failure Type | Retry? | Example |111| ------------------------------------ | ------ | --------------------------------- |112| HTTP 5xx, timeout, ECONNREFUSED | Yes | Retry 3x with 5s, 10s, 20s delays |113| HTTP 4xx, missing file, syntax error | No | Fail immediately; fix code |114| Disk full, out of memory | No | Escalate to ops |115116**Exponential backoff (sufficient for single-step retry):**117118```bash119for attempt in 1 2 3; do120 command && exit 0121 delay=$((5 * 2 ** (attempt - 1))) # 5s, 10s, 20s122 sleep "$delay"123done124exit 1125```126127**Post-deploy health check (mandatory):**128129```yaml130- name: Deploy131 run: ./deploy.sh132 timeout: 15m # Prevent stuck jobs133134- name: Health Check135 run: curl -f https://deployed-url/health || exit 1136 timeout: 5m137138- name: Rollback on Failure139 on_failure: true140 run: ./rollback.sh141```142143**Rules:**144145- Always set an explicit timeout on every long-running step (prevents default146 hangs)147- Transient failures: retry 3x with exponential backoff + jitter148- Permanent failures: fail fast, no retry149- All deployments must emit a health signal; rollback on failure150- Never apply partial state151152---153154## 5. Zero-Downtime155156| Layer | Pattern | Why |157| ------------------ | ---------------------------------------------------------------------------------------- | ----------------------------------------------------- |158| **Frontend** | Deploy to isolated preview environment per PR; atomically promote to production on merge | Production untouched during validation; safe to retry |159| **Backend** | Deploy to staging slot; health-check; swap (platform handles connection draining) | Graceful shutdown; in-flight requests complete |160| **API versioning** | New endpoints `/v2/...` alongside `/v1/...`; deprecate, never delete | Clients remain backwards-compatible |161| **PR concurrency** | Cancel in-progress runs for the same branch; only latest commit deploys | Prevent old commits overwriting newer deployments |162163**Rules:**164165- Never force-stop running instances (drops in-flight connections)166- Always test in a staging/preview environment before promoting to production167- Adding fields to API payloads is safe; removing or renaming fields breaks168 clients169- If E2E tests fail on a preview environment: block the merge; preview170 auto-cleaned on PR close171- For multi-tenant data layers, apply the Expand/Contract pattern for schema172 changes173174---175176## 6. Zero-Knowledge Secrets177178**Principle**: Minimize permanent credentials. Prove identity via179challenge/signature (OIDC), not secret exchange.180181| Credential Type | Store as long-lived secret? | How to obtain at runtime | Notes |182| ---------------------- | --------------------------- | ------------------------------------- | ------------------------------ |183| Cloud provider auth | 1 federated identity only | OIDC federated credential | Rotate quarterly; no password |184| API keys | Only if no OAuth available | STS / service principal exchange | Auto-expire; never store value |185| Encryption / HMAC keys | 1 per environment | Generate once; rotate via dual-deploy | Create new, apply, delete old |186| DB connection strings | Never | Managed Identity / service binding | No secret needed |187| OAuth client secrets | Never | Client credentials + certificate | Certificate-based auth |188189**OIDC pattern (pseudocode):**190191```yaml192permissions:193 id-token: write # Pipeline requests a short-lived identity token194 contents: read195196jobs:197 deploy:198 steps:199 - name: Authenticate to cloud (OIDC)200 # CI platform presents signed JWT to cloud provider's STS.201 # Cloud issues short-lived access token — no stored credential exchanged.202 cloud-login:203 method: oidc204 client-id: $SECRET_CLIENT_ID205```206207**Secret rotation (zero-downtime):**2082091. Create new credential2102. Apply new credential everywhere it is used2113. Verify all consumers are using the new credential2124. Delete the old credential213214**Audit logging (mandatory):**215216- Log all secret reads: timestamp, actor, resource, purpose217- Never log secret values218- Enable audit logging on your secrets manager219220**Secret hygiene:**221222- Enable secret scanning in your SCM (passive, on every push)223- Block accidental commits of `.env`, keys, credentials via `.gitignore` +224 pre-commit hooks225- If a secret leaks: rotate immediately, revoke old credential, audit access226 logs227228---229230## 7. Infrastructure Idempotency231232For ad-hoc environment config, imperative CLI commands are acceptable when they233are **idempotent** (create-or-update semantics, `--no-fail-on-existing` guards).234They become fragile at scale.235236When managing infrastructure at scale (multi-tenant, scaling policies, resource237groups), use **declarative IaC** (Bicep, Terraform, Pulumi). Declarative tools238enforce idempotency by design; imperative scripts require manual guards.239240### Delete-and-Recreate Pattern (Immutable Resources)241242Some resources cannot be updated in-place (e.g., AWS security groups, Azure243Entra policies, some Kubernetes resources). For these, use definition-based244comparison to detect changes and safely replace:245246```bash247# 1. Compute hash of desired state248DESIRED_HASH=$(echo "${DEFINITION}" | sha256sum | cut -d' ' -f1)249250# 2. Fetch existing resource and hash its definition251EXISTING=$(curl -s https://api/resource/current)252EXISTING_HASH=$(echo "${EXISTING}" | sha256sum | cut -d' ' -f1)253254# 3. If unchanged, skip (idempotent)255if [ "${DESIRED_HASH}" = "${EXISTING_HASH}" ]; then256 echo "Resource up-to-date, skipping"257 exit 0258fi259260# 4. If changed, delete old and create new (atomic from API perspective)261curl -X DELETE https://api/resource/current262curl -X POST https://api/resource -d "${DEFINITION}"263```264265**Rules:**266267- Always hash/checksum the definition, not just presence checks268- Delete before create (not after) to avoid transient conflicts269- Wrap creation in idempotent guard (e.g., check if already exists)270- Log state transitions: "definition changed, updating"271272---273274## 8. Pre-Merge Checklist275276### CRITICAL (Must-Have)277278- [ ] **Idempotency**: Safe to run 0x, 1x, or Nx; same result every time279- [ ] **Timeouts**: All long-running steps have explicit timeout values280- [ ] **Immutable artifacts**: Build once, promote same artifact; config281 injected at deploy time282- [ ] **Build in CI, not in the deploy platform**: every build runs as a283 dedicated fail-fast CI step; deploy action receives a pre-built artifact284 (no reliance on Oryx/Buildpacks/Vercel auto-build)285- [ ] **Secrets**: OIDC/federated identity for cloud auth; no stored cloud286 credentials287- [ ] **Health check**: Post-deploy validation present; rollback on failure288- [ ] **E2E tests**: Failure blocks merge via branch protection rule289- [ ] **Preview environments**: All deployments use isolated preview; production290 promoted atomically291- [ ] **PR concurrency**: Cancel-in-progress enabled; only the latest commit292 deploys293294### ADVANCED (Nice-to-Have)295296- [ ] API backward-compatibility: add fields, never remove; deprecation297 documented298- [ ] IaC migration: declarative infrastructure for resources managed at scale299- [ ] DB migrations: Expand/Contract pattern for schema changes (multi-tenant)300- [ ] Secret rotation audit: quarterly seed secret rotation logged301302## 9. See also303304- **`defect-shift-left`** — where each pipeline check belongs on the stage ladder.305- **`system-optimization`** — value-stream optimization built on top of a reliable pipeline.306- **`architecture-guidelines`** — first-principles rules out of scope here (idempotency etc. as system-level concerns).307308---309> Source: [l-gevity/l-gevity-skills](https://github.com/l-gevity/l-gevity-skills) — distributed by [TomeVault](https://tomevault.io).310<!-- tomevault:4.0:skill_md:2026-05-22 -->