CI/CD with Docker and Kubernetes — Complete Pipeline
DodaTech
3 min read
In this tutorial, you'll learn about CI/CD with Docker and Kubernetes. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
What You'll Learn
Build a complete CI/CD pipeline — automatically build Docker images, run tests, push to a registry, and deploy to Kubernetes on every git push.
Why It Matters
Manual deployments are error-prone and slow. A CI/CD pipeline ensures consistent, repeatable, and automated delivery of your applications.
Real-World Use
Every push to main deploys to staging automatically, a tag triggers production rollout, or a Pull Request creates a preview environment.
Pipeline Overview
Developer pushes code
↓
CI/CD (GitHub Actions)
├── 1. Checkout code
├── 2. Run tests
├── 3. Build Docker image
├── 4. Push to registry
└── 5. Deploy to Kubernetes
↓
Production cluster
Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
USER app
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
GitHub Actions Pipeline
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
K8S_NAMESPACE: production
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
- run: npm run build
build-and-deploy:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Deploy to Kubernetes
run: |
# Update the image tag in the deployment
sed -i "s|image:.*|image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}|" k8s/deployment.yaml
# Apply to cluster
kubectl apply -f k8s/
kubectl rollout status deployment/my-app \
-n ${{ env.K8S_NAMESPACE }}
env:
KUBECONFIG: ${{ secrets.KUBECONFIG }}
Kubernetes Manifests
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: ghcr.io/myorg/my-app:latest
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /health
port: 3000
readinessProbe:
httpGet:
path: /ready
port: 3000
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: production
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: production
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
Environment-Specific Deployments
# k8s/overlays/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: staging
replicas:
- name: my-app
count: 2
images:
- name: ghcr.io/myorg/my-app
newTag: staging
# k8s/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: production
replicas:
- name: my-app
count: 5
Rollback Strategy
# Check rollout history
kubectl rollout history deployment/my-app -n production
# Rollback to previous version
kubectl rollout undo deployment/my-app -n production
# Rollback to specific revision
kubectl rollout undo deployment/my-app -n production --to-revision=3
Complete Flow
1. Developer pushes code to main
2. GitHub Actions triggers
3. Tests run (unit + integration)
4. Docker image built with Git SHA tag
5. Image pushed to GitHub Container Registry
6. k8s/deployment.yaml updated with new tag
7. kubectl apply updates the cluster
8. Rolling update gradually replaces pods
9. Health checks verify new pods are healthy
10. Old pods are terminated
← Previous
Kubernetes Namespaces and RBAC — Multi-Tenant Clusters
Next →
Docker Networking Deep Dive — Networks, DNS, and Traffic Control
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro