Modern DevOps with Docker and Kubernetes
The containerization revolution has fundamentally transformed how we develop, deploy, and operate applications. Docker and Kubernetes have emerged as the foundational technologies enabling this transformation. This comprehensive guide explores best practices for implementing modern DevOps workflows using containerization and orchestration.
The Container Advantage
Containers solve the “it works on my machine” problem by packaging applications with all their dependencies into portable, lightweight units. Key benefits include:
- Consistency: Applications run identically across development, testing, and production
- Resource efficiency: Containers share the host OS kernel, reducing overhead
- Rapid deployment: Fast startup times enable quick scaling and updates
- Isolation: Applications are isolated from each other and the host system
Docker Fundamentals
Efficient Dockerfile Design
A well-designed Dockerfile is crucial for security, performance, and maintainability:
# Use specific, minimal base images
FROM node:18-alpine AS builder
# Create non-root user for security
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Set working directory
WORKDIR /app
# Copy package files first for better caching
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# Copy source code
COPY --chown=nextjs:nodejs . .
# Build application
RUN npm run build
# Production stage
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Copy built application
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
Multi-Stage Builds for Optimization
Multi-stage builds dramatically reduce image size and improve security:
# Build stage
FROM golang:1.21-alpine AS build
WORKDIR /app
# Install dependencies
COPY go.mod go.sum ./
RUN go mod download
# Copy source and build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# Production stage
FROM alpine:latest
# Install certificates for HTTPS
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy binary from build stage
COPY --from=build /app/main .
# Create non-root user
RUN adduser -D -s /bin/sh appuser
USER appuser
EXPOSE 8080
CMD ["./main"]
Container Security Best Practices
Security should be built into every layer:
# Use official, minimal base images
FROM alpine:3.18
# Update packages and remove package manager
RUN apk update && \
apk upgrade && \
apk add --no-cache ca-certificates && \
rm -rf /var/cache/apk/*
# Create non-root user
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -s /bin/sh -D appuser
# Set secure permissions
COPY --chown=appuser:appgroup app /usr/local/bin/app
RUN chmod 755 /usr/local/bin/app
# Switch to non-root user
USER appuser
# Use HEALTHCHECK for monitoring
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD /usr/local/bin/app --health-check || exit 1
EXPOSE 8080
CMD ["/usr/local/bin/app"]
Docker Compose for Development
Docker Compose simplifies multi-container development environments:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
args:
- NODE_ENV=development
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://user:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-network
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
networks:
- app-network
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
networks:
- app-network
volumes:
postgres_data:
redis_data:
networks:
app-network:
driver: bridge
Kubernetes Architecture Overview
Kubernetes provides a robust platform for container orchestration with several key components:
- Master Node: API server, etcd, scheduler, controller manager
- Worker Nodes: kubelet, kube-proxy, container runtime
- Pods: Smallest deployable units containing one or more containers
- Services: Network abstraction for pod communication
- Ingress: HTTP/HTTPS traffic routing
- ConfigMaps & Secrets: Configuration and sensitive data management
Kubernetes Deployment Strategies
Deployment Manifests
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
labels:
app: web-app
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: myregistry/web-app:v1.2.3
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: log-level
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
imagePullSecrets:
- name: registry-secret
Service Definition
apiVersion: v1
kind: Service
metadata:
name: web-app-service
spec:
selector:
app: web-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-app-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
spec:
tls:
- hosts:
- myapp.example.com
secretName: web-app-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app-service
port:
number: 80
Configuration Management
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
log-level: "info"
cache-ttl: "300"
database-pool-size: "10"
feature-flags: |
new-ui: true
experimental-api: false
---
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
database-url: cG9zdGdyZXNxbDovL3VzZXI6cGFzc3dvcmRAZGI6NTQzMi9teWFwcA==
api-key: c3VwZXJfc2VjcmV0X2FwaV9rZXk=
jwt-secret: and0X3NlY3JldF9mb3Jfc2lnbmluZw==
Advanced Kubernetes Patterns
Horizontal Pod Autoscaling
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
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
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
Network Policies for Security
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-app-netpol
spec:
podSelector:
matchLabels:
app: web-app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
- podSelector:
matchLabels:
app: api-gateway
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- to: []
ports:
- protocol: TCP
port: 443
- protocol: UDP
port: 53
CI/CD Pipeline Integration
GitLab CI/CD Example
# .gitlab-ci.yml
stages:
- test
- build
- security-scan
- deploy-staging
- deploy-production
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
test:
stage: test
image: node:18-alpine
script:
- npm ci
- npm run test:unit
- npm run test:integration
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
build:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
only:
- master
- develop
security-scan:
stage: security-scan
image: aquasec/trivy:latest
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE_TAG
allow_failure: false
dependencies:
- build
deploy-staging:
stage: deploy-staging
image: bitnami/kubectl:latest
environment:
name: staging
url: https://staging.myapp.example.com
script:
- kubectl config use-context staging
- envsubst < k8s/deployment.yaml | kubectl apply -f -
- kubectl rollout status deployment/web-app -n staging
- kubectl get services -n staging
only:
- develop
deploy-production:
stage: deploy-production
image: bitnami/kubectl:latest
environment:
name: production
url: https://myapp.example.com
script:
- kubectl config use-context production
- envsubst < k8s/deployment.yaml | kubectl apply -f -
- kubectl rollout status deployment/web-app -n production
when: manual
only:
- master
ArgoCD GitOps Configuration
# argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/web-app-manifests
targetRevision: HEAD
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: web-app
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Monitoring and Observability
Prometheus Configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
data:
prometheus.yml: |
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alert_rules.yml"
scrape_configs:
- job_name: 'kubernetes-apiservers'
kubernetes_sd_configs:
- role: endpoints
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https
- job_name: 'web-app'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
action: keep
regex: web-app
- source_labels: [__meta_kubernetes_pod_name]
target_label: instance
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: kubernetes_pod_name
Application Metrics
// metrics.go
package main
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "endpoint", "status_code"},
)
httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "endpoint"},
)
activeConnections = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "active_connections",
Help: "Number of active connections",
},
)
)
// Middleware for metrics collection
func metricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Wrap response writer to capture status code
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(wrapped, r)
duration := time.Since(start).Seconds()
httpRequestsTotal.WithLabelValues(
r.Method,
r.URL.Path,
fmt.Sprintf("%d", wrapped.statusCode),
).Inc()
httpRequestDuration.WithLabelValues(
r.Method,
r.URL.Path,
).Observe(duration)
})
}
Best Practices Summary
Container Best Practices
- Use minimal base images: Alpine or scratch for production
- Multi-stage builds: Separate build and runtime environments
- Non-root users: Never run containers as root
- Health checks: Implement liveness and readiness probes
- Resource limits: Always set CPU and memory limits
- Security scanning: Scan images for vulnerabilities
Kubernetes Best Practices
- Namespace isolation: Use namespaces for environment separation
- Resource quotas: Prevent resource monopolization
- Network policies: Implement micro-segmentation
- RBAC: Follow principle of least privilege
- Pod security policies: Enforce security standards
- Regular updates: Keep clusters and images updated
Operations Best Practices
- Infrastructure as Code: Version control all configurations
- GitOps workflows: Use Git as the source of truth
- Monitoring: Implement comprehensive observability
- Backup strategies: Regular backups of persistent data
- Disaster recovery: Test recovery procedures regularly
- Documentation: Maintain runbooks and architecture docs
Conclusion
Modern DevOps practices with Docker and Kubernetes enable organizations to build resilient, scalable applications while maintaining development velocity. Key success factors include:
- Start simple: Begin with basic containerization before complex orchestration
- Security first: Build security into every layer of the stack
- Automate everything: CI/CD pipelines reduce human error and increase reliability
- Monitor continuously: Observability enables rapid issue resolution
- Iterate and improve: DevOps is a journey of continuous improvement
By following these practices and patterns, teams can leverage the full power of containerization and orchestration while maintaining operational excellence.
References
- Gartner, Inc. “Container Management Software Market Guide.” Gartner Research, 2023.
- Docker, Inc. “Docker Best Practices Guide.” Docker Documentation, 2024.
- Cloud Native Computing Foundation. “Kubernetes Documentation.” https://kubernetes.io/docs/
- Fowler, Martin. “Continuous Integration.” Martin Fowler’s Blog, 2006.
- Kim, Gene, et al. The DevOps Handbook. IT Revolution Press, 2021.
- Arundel, John, and Justin Domingus. Cloud Native DevOps with Kubernetes. O’Reilly Media, 2022.