Infrastructure, container, and CI/CD discipline — IaC, Dockerfiles and compose files, CI pipelines, platform deploy config, env/secrets handling. INVOKE PROACTIVELY when creating or editing any such file (*.tf, *.bicep, Dockerfile, docker-compose*, .github/workflows/*, vercel.json) — even when nobody says "infra". Not for application code ([[code-standards]]), telemetry ([[observability]]), or schema changes ([[safe-migrations]]).
Scanned 9/2/2026
Install to Claude Code
npx -y skills add PrabhdeepSingh/claude-plugins --skill infra-standards --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Infra Standards?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/prabhdeepsingh-infra-standards)More formats (shields.io, HTML) on the badges page.
---
name: infra-standards
description: >-
Infrastructure, container, and CI/CD discipline — IaC, Dockerfiles and compose files, CI pipelines, platform deploy config, env/secrets handling. INVOKE PROACTIVELY when creating or editing any such file (*.tf, *.bicep, Dockerfile, docker-compose*, .github/workflows/*, vercel.json) — even when nobody says "infra". Not for application code ([[code-standards]]), telemetry ([[observability]]), or schema changes ([[safe-migrations]]).
---
# Infra standards — the outage-shaped mistakes are all preventable at review time
Infrastructure changes fail differently from code changes: there's no unit test that catches a deleted database, and the blast radius of one line can be the whole environment. These rules exist because every one of them maps to a class of incident — drift nobody can reproduce, a plan nobody read, a secret in an image layer, a pipeline greened by disabling its checks. The theme throughout: **infrastructure is code — reviewed, planned, least-privileged, and boring.**
## How to apply this
When touching any infra surface, hold three questions: *what is the blast radius of this change* (what can it delete or replace), *where do the secrets live* (never in the artifact), and *would this survive being run twice* (idempotency). Run the self-check before shipping.
---
## 1. Everything is code — no clickops
Any change made by hand in a cloud console/portal is **drift**: invisible to review, unreproducible, and silently overwritten (or silently load-bearing) the next time the IaC applies. The rules:
- Infrastructure changes go through the IaC files in the repo, PR-reviewed like any code.
- A genuine emergency hand-fix is allowed — and **codifying it is part of the same incident**, not a follow-up ticket that dies in the backlog. Until it's in code, the fix doesn't exist.
- If IaC and reality disagree, reality is the bug: reconcile toward the code (import the resource or fix the code), never by editing the console to match.
## 2. Read the plan before you apply — every time
`terraform plan` / `az deployment ... what-if` / CloudFormation change sets exist because IaC's most dangerous property is that **a one-line diff can mean "destroy and recreate."** Changing an immutable attribute (a resource name, an AZ, a SKU) doesn't edit the resource — it replaces it, and for a database that's data loss wearing a green checkmark.
- Never apply a plan you didn't read. In the plan output, `destroy` and `replace` lines are the whole review — explain each one or stop.
- In CI, the plan is posted for human review before apply on protected environments; auto-apply is for dev sandboxes only.
- **State is sacred** (Terraform): remote backend with locking, never hand-edited, never committed to the repo. Use `terraform state` commands (or `import`/`moved` blocks) for surgery — a corrupted state file makes the plan lie to you.
## 3. Secrets and config: the artifact never contains them
Config varies per environment; secrets are config with consequences. The discipline (the runtime-code side of this lives in [[code-standards]] sections 8–10; the day a secret reaches a remote anyway, [[security]] section 4 owns the response — revoke and reissue *before* purging history):
- **Config comes from the environment** — env vars or platform config (App Service settings, Vercel project env), never hardcoded per-environment values inside the artifact. The file taxonomy, stated once: `.env.example` committed (the template), `.env` never committed, `.env.test` committable only with no real secrets, CI secrets in the platform vault, production secrets in the deployment platform — and **CI never holds production secrets**; even a CI-only test database gets its own secret rather than a hardcoded value, so test credentials can't leak into other contexts by habit.
- **Secrets come from a secret store** — Azure Key Vault, AWS Secrets Manager, Vercel encrypted env, CI's secret mechanism. Never in: source, `.tfvars` committed to the repo, Dockerfile `ENV`/`ARG`, pipeline YAML, or logs (`set -x` in a CI script echoes everything — including the secret you just interpolated).
- **Each environment gets its own secrets.** A staging leak must not be a production leak.
```bicep
// Avoid: the connection string is now in git history forever
param dbConnectionString string = 'Server=prod-db;User=admin;Password=hunter2;'
// Prefer: the template references the store; the value never touches the repo
param keyVaultName string
resource kv 'Microsoft.KeyVault/vaults@2023-07-01' existing = { name: keyVaultName }
// app setting references: '@Microsoft.KeyVault(SecretUri=${kv.properties.vaultUri}secrets/db-connection)'
```
## 4. Dockerfile standards
Images are built once and inspected never — so the mistakes ship silently. The baseline:
- **Pin the base image** — `node:22.12-alpine`, never `:latest` (unreproducible builds that change under you). For supply-chain-sensitive builds, pin the digest.
- **Multi-stage**: build tools, compilers, and dev dependencies stay in the build stage; the runtime stage carries only the artifact and production deps. This is both size and attack surface.
- **Run as non-root**: create and `USER` an unprivileged user in the runtime stage. A container escape from root is a much worse day.
- **Layer order = cache strategy**: copy dependency manifests and install *before* copying source, so a source-only change doesn't reinstall the world.
- **No secrets in any layer** — build `ARG`s and deleted files persist in layer history; use build-time secret mounts or fetch at runtime from the store (section 3).
- **`.dockerignore` exists** and excludes `.git`, `node_modules`, `.env*`, local config — both for size and to keep secrets from being COPY'd by accident.
- **Pinning applies where images are *deployed* too** (compose files, App Service/container-service config): reference a unique tag or digest per release, never `:latest` — rolling back to "latest" is rolling back to nothing.
```dockerfile
# Avoid: latest tag, root, single stage, source copied before deps, .env baked in
FROM node:latest
COPY . .
RUN npm install
CMD ["node", "server.js"]
# Prefer
FROM node:22.12-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
FROM node:22.12-alpine
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=build --chown=app:app /app/dist ./dist
COPY --from=build --chown=app:app /app/node_modules ./node_modules
USER app
CMD ["node", "dist/server.js"]
```
## 5. CI/CD pipeline discipline
The pipeline is code: reviewed, least-privileged, and honest.
- **Never green the pipeline by weakening it.** Skipping the failing test, `continue-on-error: true`, commenting out the failing step, `|| true` — these are [[code-standards]] section 12's suppression rule wearing a hard hat: the signal goes away, the problem ships. A red pipeline is telling you something; fix the cause or genuinely justify (narrow scope, stated reason, removal condition) — never bare-suppress.
- **Fail fast, cache honestly**: cheap checks (lint, types, unit) before expensive ones; dependency caches keyed on the lockfile hash so a lockfile change busts the cache instead of poisoning it.
- **When the pipeline crosses ~10 minutes, optimize in impact order**: cache dependencies → parallelize independent jobs (lint/types/tests/build) → path-filter so only what changed runs (docs-only PRs skip e2e) → shard the test suite → move slow tests off the critical path onto a schedule → bigger runners last. The order matters because the early steps are free and the late ones cost money — reaching for larger runners before caching is paying to skip the checklist.
- **Build once, promote the artifact.** The image/bundle that passed staging is byte-for-byte what reaches production. Rebuilding per environment means deploying something nothing ever tested.
- **Least-privilege tokens**: scope CI credentials to what the job does — in GitHub Actions, set the `permissions:` block explicitly (default it to `contents: read` and grant upward per job) and prefer OIDC federation to cloud providers over long-lived stored keys.
- **Pin third-party actions/tasks** to a version (for supply-chain-critical workflows, a commit SHA) — an unpinned action is someone else's push access to your pipeline.
- Pipeline secrets go through the platform's secret mechanism and are never echoed; remember `set -x` and debug logging both leak.
## 6. Least privilege, everywhere
Every identity — service principals, IAM roles, managed identities, deploy keys — gets the narrowest scope that does the job. `*:*` and owner-role-because-it-was-easier are how a leaked dev credential becomes a company incident. When granting, name the exact operations; when reviewing, treat any wildcard as a finding that needs a justification.
## 7. Environments: parity, previews, and idempotency
- **Staging mirrors production's shape** — same IaC modules, smaller SKUs. Config differences are parameters, not divergent templates; the thing you tested should be structurally the thing you ship.
- **Ephemeral previews** (Vercel preview deploys, PR environments) are production-shaped too: preview env vars are a separate scope from production's, and previews stay non-indexable — the same discipline [[seo]] applies to staging environments.
- **Idempotency is the IaC contract**: applying twice with no changes = zero diff. If a second apply shows changes, something is non-deterministic (a timestamp, an unpinned version, drift) — fix it before it hides a real diff.
---
## 8. Feature flags and staged rollout
[[code-standards]] section 14 owns flag *defaults* (absence must be safe); this section owns the *lifecycle* and the rollout the flag gates.
- **Every flag has an owner and an expiration date**, set at creation. Clean up within a couple of weeks of full rollout — a flag that lives forever is dead code with a pulse, and its stale branch is untested production behavior waiting for someone to flip it.
- **Never nest flags** — combinations grow exponentially and nobody tests the off-off-on states. **CI exercises both states** of any flag gating behavior, or the untested state is the one that ships broken.
- **Write the rollback plan before the deploy, not during the incident**: the trigger conditions ("error rate more than double baseline = roll back; p95 latency +50% = roll back; new client-error types above a fraction of a percent of sessions = roll back; business metric down beyond noise = roll back — between those thresholds, hold and investigate"), the mechanism, and its time budget (a flag flip is seconds; redeploying the previous artifact is minutes; a database rollback is its own [[safe-migrations]] plan). In the first hour after a risky rollout, confirm the rollback mechanism actually works — a dry run if possible — because an escape hatch verified only during the escape is a hope, not a plan.
## Self-check before you ship an infra change
- Is every change expressed in code and reviewed — no console/portal edits left uncodified?
- Did you read the plan/what-if, and can you explain every `destroy`/`replace` line in it?
- Zero secrets in source, tfvars, Dockerfiles, pipeline YAML, images, or logs — everything from a secret store, scoped per environment?
- Dockerfile: pinned base, multi-stage, non-root, deps-before-source layers, `.dockerignore` present — and deploy config references a unique tag per release, never `:latest`?
- Pipeline: no skipped/suppressed checks without a stated justification; artifact built once and promoted; `permissions:` scoped; third-party actions pinned?
- Any wildcard permission grants that lack a written justification?
- Is the change idempotent by construction — nothing non-deterministic (a timestamp, an unpinned version, generated randomness) that would make a second apply show a diff?
## Provenance and maintenance
The disciplines are durable; the tool surfaces drift. Last verified 2026-07:
- Tool syntax in examples (Terraform plan/state behavior, Bicep Key Vault references, GitHub Actions `permissions:`/OIDC, `npm ci`/prune flags) — re-verify against each tool's current docs before leaning on exact syntax.
- Base-image tags in examples are illustrative — pin to whatever is current-LTS at the time of writing, not to this file's example.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!