Warrior, this is where Kubernetes becomes part of a real software delivery system.
Until now, you've mostly learned:
Developer
โ
Docker
โ
Kubernetes
โ
ApplicationNow we'll build:
Developer
โ
Git
โ
CI/CD
โ
Docker Image
โ
Container Registry
โ
Kubernetes
โ
ProductionCI/CD stands for:
Whenever developers push code:
Git Push
โ
Build
โ
Test
โ
Quality checksThe goal is to catch problems early.
After CI succeeds:
Build Image
โ
Push Image
โ
Deploy
โ
KubernetesDepending on the organization, "CD" can mean:
Imagine manually deploying:
Developer
โ
docker build
โ
docker push
โ
kubectl set image
โ
kubectl rollout statusThis becomes painful.
Every deployment requires manual work.
And humans make mistakes.
Instead:
Developer
โ
โ git push
โ
GitHub
โ
โ
CI/CD Pipeline
โ
โโโ Test
โโโ Build
โโโ Security Scan
โโโ Docker Build
โโโ Push Image
โโโ Deploy
โ
โ
KubernetesYou push code.
The pipeline handles the repetitive work.
For your Node.js application:
Developer
โ
git push
โ
โ
GitHub
โ
โ
GitHub Actions
โ
โโโโโโโโโโโโผโโโโโโโโโโโ
โ โ โ
Test Lint Build
โ โ โ
โโโโโโโโโโโโผโโโโโโโโโโโ
โ
Docker Build
โ
โ
Container Registry
โ
โ
Kubernetes
โ
Deployment
โ
โ
Rolling Update
โ
โ
PodsSuppose:
node-api/
โ
โโโ src/
โ โโโ server.js
โ
โโโ package.json
โโโ package-lock.json
โโโ Dockerfile
โโโ k8s/
โโโ deployment.yaml
โโโ service.yamlApplication:
const express = require("express");
const app = express();
app.get("/", (req, res) => {
res.json({
message: "Hello Kubernetes"
});
});
app.get("/health", (req, res) => {
res.json({
status: "ok"
});
});
app.listen(3000, "0.0.0.0", () => {
console.log("Server running on port 3000");
});FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "src/server.js"]Now:
Source code
โ
Dockerfile
โ
Docker imageYour CI server builds:
node-api:1.0.0But Kubernetes needs to retrieve that image.
So:
CI
โ
Docker Image
โ
Container Registry
โ
KubernetesExamples:
AWS โ ECR
Google โ Artifact Registry
Azure โ ACR
GitHub โ GHCR
Docker โ Docker HubNever rely on:
latestfor production deployments.
Instead use:
node-api:1.0.0
node-api:1.0.1
node-api:1.1.0or a Git commit SHA:
node-api:a84f92cEven better, production systems can deploy immutable image digests:
node-api@sha256:...Why?
Suppose:
node-api:latesttoday means:
Version 1Tomorrow someone pushes another image:
node-api:latestNow the same tag points somewhere else.
That makes debugging and rollback harder.
Use:
Git commit
โ
Docker image
โ
Tag with commit SHAFor example:
node-api:8f31ac2Then Kubernetes knows exactly which build you're running.
A common pattern is to use both:
node-api:1.5.0
node-api:8f31ac2and deploy by immutable digest where appropriate.
Let's use GitHub Actions as our CI/CD example.
Create:
.github/
โโโ workflows/
โโโ deploy.ymlA basic CI workflow:
name: Node API CI
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm testNow:
git push
โ
GitHub
โ
GitHub Actions
โ
npm ci
โ
npm testSuppose the test fails:
npm test
โ
FAILThen:
Docker build โ
Deployment โYou don't want broken code reaching production.
So:
Test
โ
FAIL
โ
STOPThis is fundamental CI behavior.
Add:
- name: Build Docker image
run: |
docker build \
-t node-api:${{ github.sha }} .Now:
Git commit SHA
โ
Docker tagFor example:
node-api:8e7f32a...Suppose you're using GHCR.
The general architecture becomes:
GitHub Actions
โ
Docker Build
โ
GHCR
โ
KubernetesThe workflow needs credentials/permissions to push.
Never write credentials directly into:
run: docker login ... --password mypasswordUse GitHub Actions secrets or supported workload identity/OIDC approaches.
Common secrets:
AWS credentials
Registry credentials
Kubernetes credentials
API keys
Database credentialsDo not put them in:
Git repository
Dockerfile
source codeUse:
GitHub Actions Secrets
OIDC / workload identity
Cloud secret managersA particularly important production pattern is:
GitHub Actions
โ
OIDC
โ
Cloud IAM
โ
AWS / GCP / AzureInstead of storing long-lived cloud access keys in GitHub.
For AWS, GitHub Actions can use OIDC to assume an IAM role.
Conceptually:
GitHub
โ
OIDC identity
โ
AWS IAM Role
โ
ECR / EKSThis reduces long-lived credential exposure.
Separate the concepts.
Checkout
โ
Install
โ
Lint
โ
Test
โ
BuildBuild image
โ
Push registry
โ
Update Kubernetes
โ
Rollout
โ
VerifyCombined:
CI
โ
CDThere are several strategies.
kubectl set image#For example:
kubectl set image deployment/node-api \
node-api=registry.example.com/node-api:${GITHUB_SHA}Then:
kubectl rollout status deployment/node-apiThis is simple.
Your Deployment contains:
image: registry.example.com/node-api:1.0.0CI changes it to:
image: registry.example.com/node-api:1.1.0Then:
kubectl apply -f deployment.yamlSince you've just learned Kustomize, this is a natural fit.
Production:
images:
- name: registry.example.com/node-api
newTag: "8f31ac2"CI updates the image tag.
Then:
kubectl apply -k k8s/overlays/productionArchitecture:
Git
โ
CI
โ
Build image
โ
Registry
โ
Update Kustomize image
โ
kubectl apply -k
โ
KubernetesYou can also update:
image:
repository: registry.example.com/node-api
tag: "1.5.0"Then:
helm upgrade node-api ./chartSo:
Kustomizeand:
Helmboth integrate naturally with CI/CD.
Let's design one:
Developer
โ
โ
git push
โ
โ
GitHub
โ
โ
GitHub Actions
โ
โโโ Checkout
โโโ npm ci
โโโ Lint
โโโ Unit tests
โโโ Security scan
โโโ Docker build
โโโ Docker push
โ
โ
Container Registry
โ
โ
Update Kustomize
โ
โ
kubectl apply -k
โ
โ
Kubernetes
โ
โ
Rolling Deployment
โ
โ
Readiness probes
โ
โ
SuccessDon't stop after:
kubectl applyYou need to verify.
Use:
kubectl rollout status deployment/node-apiIf successful:
deployment "node-api" successfully rolled outThen:
kubectl get podsThen:
kubectl get svcThen check application health.
Suppose the new image has a bug.
You might get:
CrashLoopBackOffor:
Readiness probe failedThe pipeline should detect rollout failure.
For example:
kubectl rollout status deployment/node-api --timeout=5mIf it fails:
Deployment โThen you can rollback.
You've already learned:
kubectl rollout undo deployment/node-apiSo:
v1
โ
v2
โ
v2 broken
โ
rollback
โ
v1A CI/CD system should make this process controlled and observable.
Suppose you deploy:
v2with:
readinessProbe:
httpGet:
path: /health
port: 3000If new Pods aren't ready:
v2 Pod
โ
Readiness FAIL
โ
Not ready
โ
Service doesn't route normal traffic to itThis is why Chapter 14 and Chapter 24 were important.
CI/CD and Kubernetes features work together.
New image
โ
Deployment
โ
New Pod
โ
Startup
โ
Startup probe
โ
Readiness probe
โ
Ready
โ
Service trafficIf readiness fails:
New Pod
โ
NotReady
โ
Traffic not sent normallyAnother deployment strategy.
Suppose:
Blue = v1
Green = v2You run both:
Service
โ
โ
Blue
v1Deploy green:
โโโโโโโโโ Service
โ
โโโ Blue v1
โ
โโโ Green v2Test green.
Then switch Service traffic:
Service
โ
โ
Green
v2Blue remains available for rollback.
Instead of sending everyone to v2:
100% โ v1you might gradually shift traffic:
95% โ v1
5% โ v2then:
90% โ v1
10% โ v2then:
50% โ v1
50% โ v2then:
0% โ v1
100% โ v2This reduces deployment risk when implemented with suitable traffic-routing tooling.
| Strategy | Idea |
|---|---|
| Rolling | Gradually replace old Pods |
| Blue-Green | Run two environments and switch traffic |
| Canary | Gradually shift traffic |
| Recreate | Stop old before starting new |
Simple mental model:
Rolling
v1 โ v1/v2 โ v2Blue-Green
v1 + v2 โ switchCanary
v1 100% โ 95/5 โ 90/10 โ 100% v2You shouldn't necessarily deploy every branch directly to production.
A common structure:
feature branch
โ
Pull Request
โ
CI
โ
Tests
โ
merge main
โ
staging
โ
approval
โ
productionExample:
Development
โ
Staging
โ
ProductionA simple model:
feature/*
โ
Pull Request
โ
main
โ
Staging
โ
ProductionOr some teams use:
main
โ
Production
feature/*
โ
Preview environmentsThere isn't one universally correct branching model.
The important thing is that the deployment process is predictable.
Never do:
if production:
hardcoded passwordInstead:
Kubernetes ConfigMap
Kubernetes Secret
External Secret ManagerFor example:
Development
โ
values-dev.yaml
Staging
โ
values-staging.yaml
Production
โ
values-prod.yamlOr Kustomize:
base
โ
dev overlay
staging overlay
production overlaySuppose:
helm/
โโโ node-api/
โโโ Chart.yaml
โโโ values.yaml
โโโ templates/CI builds:
node-api:1.5.0Then:
helm upgrade --install node-api ./node-api \
--set image.tag=1.5.0Then:
kubectl rollout status deployment/node-apiArchitecture:
CI
โ
Docker
โ
Registry
โ
Helm
โ
KubernetesAlternatively:
CI
โ
Docker
โ
Registry
โ
Kustomize
โ
kubectl apply -kExample:
images:
- name: registry.example.com/node-api
newTag: "1.5.0"Then:
kubectl apply -k k8s/overlays/productionNow we reach a very important concept.
Traditional CI/CD:
CI
โ
kubectl
โ
KubernetesGitOps changes the model.
Git
โ
Desired state
โ
GitOps controller
โ
KubernetesWe'll study this deeply in Chapter 31 with Argo CD.
For now:
Git becomes the source of truth for the desired Kubernetes state.
GitHub Actions
โ
โ
kubectl apply
โ
โ
KubernetesGit
โ
โ desired state
โ
Argo CD
โ
โ reconcile
โ
KubernetesThis connects directly to Kubernetes' fundamental desired state + reconciliation model from Chapter 1.
A production pipeline should consider:
npm auditor specialized scanners.
Scan:
Docker imagefor vulnerabilities.
Detect accidentally committed:
API keys
passwords
tokens
private keysCheck for:
privileged: true
runAsRoot
excessive permissionsTools vary by organization.
Imagine:
Developer pushes code
โ
Tests pass
โ
ProductionThis may be fine for some teams, but not all systems.
For critical production environments, you may require:
Tests
โ
Security
โ
Staging
โ
Smoke tests
โ
Approval
โ
ProductionAfter deployment:
Deployment
โ
Health check
โ
GET /
โ
GET /health
โ
Verify responseExample:
curl https://api.example.com/healthExpected:
{
"status": "ok"
}If it fails:
Deployment verification โA strong production pipeline can look like:
Git
โ
โ
Pull Request
โ
โ
โโโโโโโโโโโโโโโ
โ CI Pipeline โ
โโโโโโโโฌโโโโโโโ
โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โ โ โ
Lint Test Security
โ โ โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โ
Docker Build
โ
Container Scan
โ
Registry
โ
Staging
โ
Smoke Tests
โ
Approval
โ
Production
โ
Kubernetes
โ
Rolling/Canary
โ
MonitoringLet's trace it.
New image
โ
Deployment
โ
Pod starts
โ
Readiness probe
โ
FAILKubernetes:
Pod = NotReadyCI:
rollout status
โ
timeout/failurePipeline:
Deployment failedThen:
Rollback
โ
Previous ReplicaSet
โ
Healthy PodsMonitoring should also alert the team.
This is the most important architecture in the entire course so far:
Git
โ
โ
CI
โ
โโโโโโโโโโโโดโโโโโโโโโโโ
โ โ
Testing Docker
โ
โ
Container Registry
โ
โ
Kubernetes
โ
โโโโโโโโดโโโโโโโ
โ โ
Deployment Service
โ โ
โ โ
Pods โโโโโโโโโโโโโ
โ
โโโโโโโโโผโโโโโโโโโ
โ โ โ
HPA Probes NetworkPolicy
โ
โ
Node Autoscaling
โ
โ
NodesThen:
Monitoring
โ
Prometheus/Grafana
โ
Alertsand:
Logging
โ
Centralized logsHere's a simplified example.
name: Node API CI/CD
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install
run: npm ci
- name: Test
run: npm test
build:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build Docker image
run: |
docker build \
-t node-api:${{ github.sha }} .Notice:
needs: testThis means:
test
โ
success
โ
buildIf tests fail:
test โ
โ
build doesn't runSuppose production currently runs:
node-api:a81f2d1Then new deployment:
node-api:b73ac92If something breaks:
Which version?Answer:
b73ac92You can trace it back to Git.
This gives:
Git commit
โ
Docker image
โ
Kubernetes deploymentThat's extremely valuable for debugging.
Better:
node-api@sha256:abc123...than relying only on:
node-api:latestbecause the digest identifies the exact image content.
Production systems often use immutable image references or otherwise enforce immutable versioning.
Continuous Integration automatically builds and tests code changes so integration problems are detected early.
Continuous Delivery/Deployment automates or streamlines delivery of validated changes to environments such as staging or production.
Kubernetes needs a reliable location from which nodes can pull container images.
latest?#Because it is mutable and makes deployments, auditing, reproducibility and rollback harder.
Typical flow:
Git
โ
CI
โ
Test
โ
Docker build
โ
Registry
โ
Update manifest/Helm/Kustomize
โ
kubectl/Argo CD
โ
Kuberneteskubectl rollout status deployment/node-apiplus:
kubectl get pods
kubectl logs
health/smoke tests
monitoringThe new Pod is not considered ready and normally won't receive regular Service traffic; the rollout can stall or fail depending on Deployment configuration.
Traditional:
CI โ kubectl โ KubernetesGitOps:
Git โ GitOps controller โ KubernetesCI
โ
Build + Test + Security
CD
โ
Deliver/Deploy
Docker
โ
Build image
Registry
โ
Store image
Kubernetes
โ
Run image
Deployment
โ
Rolling update
Readiness
โ
Safe traffic
Rollout status
โ
Deployment verification
Rollback
โ
Previous versionDeveloper
โ
Git Push
โ
CI
โโโ Test
โโโ Lint
โโโ Security
โโโ Build
โ
Docker Image
โ
Registry
โ
Deploy
โ
Kubernetes
โ
Readiness Probes
โ
Smoke Tests
โ
ProductionTraditional CI/CD
Git โ CI โ kubectl โ Kubernetes
GitOps
Git โ Argo CD โ KubernetesThis is one of the most important production Kubernetes concepts.
We'll learn:
kubectl apply can become problematic at scaleMark it as complete to track your mastery.