CI/CD Pipelines & YAML Best Practices

Lessons Learned from Real-World Azure + Bitbucket Deployments
Designing CI/CD pipelines feels straightforward…
until your builds become inconsistent, deployments start failing, and environment management turns messy.

After working extensively with:

  • Bitbucket Pipelines
  • Azure App Service (Linux)
  • Node.js backends
  • React / Angular frontends
  • Monorepos

…I’ve gathered practical lessons that saved hours of debugging and prevented production issues.

This post focuses on real-world patterns, not textbook theory.

🧱 1️⃣ Deterministic Builds Are Non-Negotiable
One of the most common CI/CD mistakes:

npm install
 

❌ Why this is risky in pipelines

  • Can modify package-lock.json
  • Non-reproducible builds
  • “Works locally, fails in CI”

✅ Best Practice

npm ci
 

For deployment bundles:

npm ci --omit=dev
 

✔ Benefits

  • Enforces lockfile
  • Faster installs
  • Reproducible builds

📦 2️⃣ Don’t Ship Dev Dependencies to Production
Early pipelines often bundle everything:

node_modules/
 

❌ Consequences

  • Bloated artifacts
  • Slower deployments
  • Higher memory usage
  • Cold start delays

✅ Better Strategy
Reinstall only production dependencies:

npm ci --omit=dev
 

✔ Result

  • Smaller ZIP
  • Faster startup
  • Cleaner runtime

⚛ 3️⃣ Frontend Configuration: The Silent Problem
For React / Angular / Vite apps:

npm run build
 

👉 Environment variables become hardcoded into JS bundles
❌ Symptoms

  • Azure App Settings changes do nothing
  • Rebuild required per environment

✅ Scalable Solution → Runtime Config
Create an env.js file:

window.__env = {
  API_URL: "https://api.example.com"
};
 

Load before the app bundle:

<script src="/env.js"></script>
 

Access in React:

const apiUrl = window.__env?.API_URL;
 

✔ Advantages

  • Build once, deploy anywhere
  • No rebuild per environment
  • Easy config updates
  • Customer-specific deployments

🔐 4️⃣ Frontend Secrets? They Don’t Exist.
Important reality:
❌ You cannot truly hide secrets in frontend apps
Anything delivered to the browser can be inspected.

🚨 Never expose

  • API keys
  • DB credentials
  • Client secrets

✅ Correct Architecture

Frontend → Backend → Third-party
 

Secrets live only in:

  • Backend
  • Azure App Settings
  • Key Vault

🌱 5️⃣ One YAML vs Multiple YAML Files
As projects grow:
👉 Should we create separate YAML per branch?

❌ Why this becomes painful

  • Pipeline drift
  • Duplication
  • Maintenance overhead

✅ Recommended
Use one common YAML with branch logic:

branches:
  dev:
  test:
  main:
 

🔁 6️⃣ Repeated Steps? Use YAML Anchors
Avoid copy-paste pipelines.
✅ Use Anchors

definitions:
  steps:
    - step: &build_step
 

Reuse:

- step: *build_step
 

✔ Benefits

  • Cleaner YAML
  • Easier updates
  • Less duplication

☁️ 7️⃣ Azure Deployment: Pipe vs CLI
Two popular approaches:

✅ Atlassian Azure Pipe (Preferred)

pipe: atlassian/azure-web-apps-deploy
 

✔ Why it’s great

  • Minimal scripting
  • Cleaner pipelines
  • Fewer failure points

🧰 Azure CLI (Use When Needed)

Best for:

  • Slot swaps
  • Infra provisioning
  • Advanced automation

❌ Avoid installing CLI manually

curl InstallAzureCLIDeb
 

✅ Use Azure CLI image instead

image: mcr.microsoft.com/azure-cli
 

🚨 8️⃣ Dangerous Commands to Avoid (Unless Justified)

Examples:

az resource update ... allow=true
PORT=8080 hardcoding
sleep 30 hacks
 

✅ Only acceptable when

  • Legacy constraints
  • Debugging scenarios
  • Platform requirements
  • Always document the reason.

🔐 9️⃣ Repository vs Deployment Variables
Misusing variables causes many CI/CD issues.
✅ Repository Variables

Use for:

  • Shared config
  • Build constants

Examples:

  • NODE_VERSION
  • LINT_FLAGS

✅ Deployment Variables
Use for:

  • Environment-specific values
  • Secrets

Examples:

  • API_URL
  • Azure credentials

🏆 Final Takeaways

✅ Pipelines must be deterministic
✅ Use npm ci, not npm install
✅ Install prod dependencies only
✅ Artifacts should be environment-agnostic
✅ Never store secrets in frontend
✅ Prefer runtime config for SPAs
✅ Use one YAML with branch logic
✅ Use YAML anchors to avoid duplication
✅ Prefer deployment pipes over complex scripts
✅ Use Azure CLI only when flexibility required

🏆 Conclusion: CI/CD is Easy… Until It Isn’t

Setting up a pipeline that “works” is not difficult.

But designing a pipeline that is:

  • Deterministic
  • Environment-agnostic
  • Secure
  • Scalable

Easy to maintain
👉 requires deliberate engineering decisions.

Small shortcuts like:
❌ Using npm install
❌ Embedding secrets in frontend
❌ Duplicating YAML per branch
❌ Shipping devDependencies
…may work initially but create serious friction as projects scale.

Final Thought

Your CI/CD pipeline is not just automation…
👉 It is part of your system architecture.
Treat it with the same care as application code.

In this article:
Share on social media: