Containerization has revolutionized how we deploy and manage applications. Docker provides the foundation for packaging applications, while Kubernetes orchestrates these containers at scale. This comprehensive guide covers everything you need to know about containerization and orchestration.
Understanding Containerization
What are Containers?
Containers are lightweight, isolated environments that package applications with their dependencies. Unlike virtual machines, containers share the host OS kernel but run in isolated user spaces.
Benefits of Containerization
- Portability: Run anywhere with Docker installed
- Consistency: Same environment across development, staging, and production
- Isolation: Applications don't interfere with each other
- Efficiency: Lower resource overhead compared to VMs
- Scalability: Easy to scale horizontally
Docker Fundamentals
Dockerfile Best Practices
Create efficient, secure Docker images:
# Multi-stage build for Node.js application
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Production stage
FROM node:18-alpine AS production
WORKDIR /app
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
# Copy built application
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
# Change ownership
RUN chown -R nodejs:nodejs /app
USER nodejs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Start application
CMD ["npm", "start"]
Docker Compose for Development
Orchestrate multiple services locally:
# docker-compose.yml
version: "3.8"
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://user:password@db:5432/myapp
depends_on:
- db
- redis
volumes:
- ./src:/app/src
- /app/node_modules
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app
volumes:
postgres_data:
redis_data:
Docker Networking
Understand Docker networking concepts:
# Create custom network
docker network create my-network
# Run containers on custom network
docker run --network my-network --name app my-app
docker run --network my-network --name db postgres
# Inspect network
docker network inspect my-network
Kubernetes Fundamentals
Kubernetes Architecture
Kubernetes consists of:
- Control Plane: Manages cluster state
- Nodes: Run containerized applications
- Pods: Smallest deployable units
- Services: Network abstraction for pods
- Ingress: External access to services
Basic Kubernetes Resources
Pods
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
Deployments
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
imagePullSecrets:
- name: registry-secret
Services
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-service
labels:
app: my-app
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 3000
protocol: TCP
selector:
app: my-app
Ingress
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
Advanced Kubernetes Concepts
ConfigMaps and Secrets
Manage configuration and sensitive data:
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
NODE_ENV: "production"
LOG_LEVEL: "info"
API_VERSION: "v1"
database:
host: "db-service"
port: "5432"
name: "myapp"
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
DATABASE_URL: cG9zdGdyZXNxbDovL3VzZXI6cGFzc3dvcmRAZGI6NTQzMi9teWFwcA==
API_KEY: c2VjcmV0LWtleQ==
JWT_SECRET: anNvbi13ZWItdG9rZW4tc2VjcmV0
Persistent Volumes
Handle persistent storage:
# pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: app-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
storageClassName: local-storage
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-storage
Horizontal Pod Autoscaler
Automatically scale based on metrics:
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Monitoring and Observability
Prometheus and Grafana
Monitor Kubernetes clusters:
# prometheus-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
data:
prometheus.yml: |
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
Application Logging
Implement structured logging:
// logger.js
const winston = require("winston");
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json(),
),
defaultMeta: { service: "my-app" },
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "error.log", level: "error" }),
new winston.transports.File({ filename: "combined.log" }),
],
});
module.exports = logger;
Security Best Practices
Pod Security Standards
Implement security policies:
# psp.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- "configMap"
- "emptyDir"
- "projected"
- "secret"
- "downwardAPI"
- "persistentVolumeClaim"
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
rule: "MustRunAsNonRoot"
seLinux:
rule: "RunAsAny"
supplementalGroups:
rule: "MustRunAs"
ranges:
- min: 1
max: 65535
fsGroup:
rule: "MustRunAs"
ranges:
- min: 1
max: 65535
readOnlyRootFilesystem: true
Network Policies
Control pod-to-pod communication:
# network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-network-policy
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend
ports:
- protocol: TCP
port: 3000
egress:
- to:
- namespaceSelector:
matchLabels:
name: database
ports:
- protocol: TCP
port: 5432
- to: []
ports:
- protocol: TCP
port: 53
- protocol: UDP
port: 53
CI/CD with Kubernetes
GitHub Actions Workflow
Automate deployment:
# .github/workflows/deploy.yml
name: Deploy to Kubernetes
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ secrets.REGISTRY }}/my-app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: "latest"
- name: Configure kubectl
run: |
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/my-app-deployment my-app=${{ secrets.REGISTRY }}/my-app:${{ github.sha }}
kubectl rollout status deployment/my-app-deployment
Helm Charts
Package Kubernetes applications:
# Chart.yaml
apiVersion: v2
name: my-app
description: A Helm chart for My Application
type: application
version: 0.1.0
appVersion: "1.0.0"
# values.yaml
replicaCount: 3
image:
repository: my-app
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
Troubleshooting and Debugging
Common Issues and Solutions
Pod Stuck in Pending
# Check pod events
kubectl describe pod <pod-name>
# Check node resources
kubectl describe node <node-name>
# Check persistent volume claims
kubectl get pvc
Pod CrashLoopBackOff
# Check pod logs
kubectl logs <pod-name>
# Check previous container logs
kubectl logs <pod-name> --previous
# Execute into container
kubectl exec -it <pod-name> -- /bin/sh
Service Not Accessible
# Check service endpoints
kubectl get endpoints <service-name>
# Test service connectivity
kubectl run test-pod --image=busybox --rm -it --restart=Never -- nslookup <service-name>
Best Practices Summary
1. Image Management
- Use multi-stage builds
- Keep images small
- Use specific tags
- Scan for vulnerabilities
2. Resource Management
- Set resource limits
- Use horizontal pod autoscaling
- Monitor resource usage
- Implement proper health checks
3. Security
- Run containers as non-root
- Use network policies
- Implement RBAC
- Scan images regularly
4. Monitoring
- Set up comprehensive logging
- Use distributed tracing
- Monitor application metrics
- Set up alerting
5. Deployment
- Use rolling updates
- Implement blue-green deployments
- Use canary deployments
- Set up proper rollback procedures
Conclusion
Docker and Kubernetes provide a powerful foundation for modern application deployment. Docker handles containerization, while Kubernetes manages orchestration at scale. Understanding both technologies is essential for building resilient, scalable applications.
Start with Docker for local development and containerization, then move to Kubernetes for production orchestration. Remember to follow security best practices, implement proper monitoring, and use CI/CD pipelines for automated deployments.
The key to success is starting simple and gradually adding complexity as your needs grow. Focus on understanding the fundamentals before diving into advanced features.