Containers have transformed the way we package and deploy applications. Unlike virtual machines, containers share the host kernel while isolating processes. This makes them lightweight, portable, and fast to start. But running containers at scale is not as simple as docker run
. In this article, we'll explore advanced containerization topics with practical examples, focusing on Kubernetes as the de facto orchestrator.
A container image is the foundation of your workload. Poorly designed images lead to slow deployments, security risks, and wasted storage.
# Stage 1: Builder
FROM golang:1.20 AS builder
WORKDIR /src
COPY . .
RUN go build -o app .
# Stage 2: Runtime
FROM gcr.io/distroless/base
COPY --from=builder /src/app /app
CMD ["/app"]
Explanation:
distroless
base image with no shell or package manager, reducing attack surface and image size.Before pushing, scan the image:
trivy image myregistry/myapp:1.0.0
This reports CVEs and vulnerable dependencies.
Kubernetes originally supported Docker directly via dockershim. As of v1.24, dockershim was removed. Kubernetes now relies on CRI-compliant runtimes:
What this means: You can still build images with Docker, but Kubernetes runs them using containerd or CRI-O under the hood. Tooling that depends on the Docker socket may break and should be updated.
Containers are ephemeral, but many applications need persistence. Kubernetes handles this through Persistent Volumes (PVs) and Persistent Volume Claims (PVCs).
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: "postgres"
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
Explanation:
Kubernetes networking is more than just exposing a Service. You often need fine-grained traffic control and security.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
Explanation: This routes traffic from myapp.example.com
to the internal service.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
spec:
podSelector:
matchLabels:
role: backend
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
This allows only frontend
pods to connect to backend
pods.
Kubernetes supports several deployment strategies beyond "scale replicas up or down."
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- myapp.example.com
http:
- route:
- destination:
host: myapp
subset: stable
weight: 90
- destination:
host: myapp
subset: canary
weight: 10
Explanation:
Security should be applied at multiple layers: cluster, pod, and runtime.
apiVersion: v1
kind: Pod
metadata:
name: restricted
spec:
containers:
- name: app
image: myapp:1.0.0
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
Explanation:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
This restricts access to only reading pods in the dev
namespace.
Without observability, debugging distributed systems is guesswork.
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
Pods annotated with prometheus.io/scrape=true
are automatically monitored.
Use OpenTelemetry to trace requests across microservices. This helps identify latency bottlenecks.
Modern workflows deploy continuously and declaratively.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
spec:
destination:
namespace: default
server: https://kubernetes.default.svc
source:
repoURL: 'https://github.com/myorg/myapp-configs'
targetRevision: main
path: overlays/prod
syncPolicy:
automated:
prune: true
selfHeal: true
Explanation:
By following these practices, you can run resilient, secure, and scalable workloads in production.