Deep-dive tutorials on Docker, Kubernetes, CI/CD, Terraform, AWS, and MLOps — written by engineers who run these in production.
From container fundamentals to production-grade images, networking, volumes, registries, and Docker Compose — everything you need to ship confidently.
Why Containers Changed Software Delivery
Docker uses Linux kernel features — namespaces (for isolation) and cgroups (for resource limits) — to create containers. Namespaces partition OS resources so each container sees its own isolated set (network, filesystem, process tree). cgroups limit how much CPU, memory, and I/O a container can use. On Mac and Windows, Docker Desktop runs a lightweight Linux VM to host the Docker daemon, which is why you'll find volume data stored inside that VM. In production, containers run natively on Linux hosts without any VM overhead.
Engine, Daemon, Client & Registry
| Component | Role |
|---|---|
| Docker Client (CLI) | The tool you type commands into. Sends instructions to the daemon via REST API. Examples: docker build, docker run, docker push. |
| Docker Daemon (dockerd) | Long-running background process. Manages images, containers, networks, and volumes. Listens for API requests. |
| Docker Images | Read-only templates used to create containers. Stored as layered filesystems. Pulled from or pushed to registries. |
| Docker Containers | Running instances of images. Isolated using Linux namespaces and cgroups. Has its own network interface, filesystem, and process space. |
| Docker Registry | Storage for Docker images. Docker Hub is the default public registry. AWS ECR, Azure ACR, GCR are private options. |
| Docker Volumes | Persistent storage mechanism. Data survives container deletion. Managed by Docker CLI independently of containers. |
The Docker daemon communicates with the container runtime (containerd) which actually creates and manages containers. This separation was introduced to allow Kubernetes and Docker to share containerd as a common runtime. On Linux, the Docker socket is at /var/run/docker.sock — mounting this socket into a container gives it full Docker control (dangerous in production). The Docker REST API is versioned — always check compatibility when upgrading Docker Engine in production environments.
Docker Desktop & Docker Engine Setup
| Platform | Install Method | Notes |
|---|---|---|
| Mac (Apple Silicon / Intel) | Docker Desktop from docker.com | Includes Docker Engine, Compose, Kubernetes. Uses lightweight VM. |
| Windows | Docker Desktop with WSL2 backend | WSL2 provides near-native Linux performance on Windows. |
| Ubuntu / Debian | Docker Engine via apt repository | apt-get install docker-ce docker-ce-cli containerd.io |
| RHEL / CentOS | Docker Engine via yum repository | yum install docker-ce — best for production servers. |
| All Platforms | Verify with: docker version | Shows client + server version. Both must show correctly. |
On production Linux servers, always pin Docker to a specific version — avoid auto-upgrades that could break your workloads. Use: apt-mark hold docker-ce after installing. Test upgrades in staging first.
Docker Desktop on Mac runs a stripped-down Linux VM (using Apple's Hypervisor framework on M1/M2, or HyperKit on Intel). All container data, volumes, and the Docker daemon run inside this VM. This is why docker inspect shows volume paths like /var/lib/docker/volumes — that path is inside the VM, not on your Mac filesystem. On Apple Silicon (M1/M2/M3), Docker uses Rosetta 2 for amd64 images — most work fine but some performance-sensitive workloads may need arm64-native images. Use --platform linux/amd64 to force x86 emulation.
Images, Containers & Lifecycle Management
Host port must be unique — two containers cannot bind the same host port. But multiple containers can listen on the same container port (e.g., both on port 80 internally). Use -p 8080:80 -p 8081:80 for two nginx containers.
Docker commands evolved from docker run, docker ps etc. to docker container run, docker container ps in newer versions (management command format). Both work, but the management command format is cleaner in scripts. Use docker container ls --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" for readable output in CI pipelines. The --rm flag automatically removes the container when it exits — useful for one-off tasks and CI jobs to avoid container accumulation.
Bridge, Host & Custom Networks
Default bridge: containers must use IP addresses to talk to each other. Custom bridge: containers talk by name (e.g., mongodb, api-service). Always use custom bridge networks for microservices.
Docker DNS works by embedding a DNS server inside the Docker daemon that resolves container names within the same user-defined network. This is why custom bridge networks support name resolution but the default bridge network does not — the default bridge predates the embedded DNS feature. In production, Docker overlay networks enable multi-host communication across a Docker Swarm cluster. For Kubernetes environments, the container networking is handled by CNI plugins (Calico, Cilium, Flannel) rather than Docker networking. MacVLAN and IPVLAN network drivers are used when containers need to appear as physical devices on the host network — useful in bare-metal networking scenarios.
Instructions, Layers & Best Practices
| Instruction | Description | Example |
|---|---|---|
| FROM | Sets the base image. Every Dockerfile starts with FROM. | FROM openjdk:18-jdk-alpine |
| WORKDIR | Sets working directory for subsequent instructions. | WORKDIR /app |
| COPY | Copies files from build context to image filesystem. | COPY target/app.jar /app/app.jar |
| ADD | Like COPY but also supports URLs and tar extraction. | ADD app.tar.gz /app/ |
| RUN | Executes commands during build time — creates a new layer. | RUN npm install |
| ENV | Sets environment variables available at runtime. | ENV NODE_ENV=production |
| ARG | Build-time variable — passed via --build-arg flag. | ARG JAVA_VERSION=18-jdk |
| EXPOSE | Documents which port the container listens on (informational). | EXPOSE 8080 |
| ENTRYPOINT | Defines the command that always runs. Args are appended. | ENTRYPOINT ["java", "-jar"] |
| CMD | Default command/args. Overridden by docker run arguments. | CMD ["app.jar"] |
| VOLUME | Creates a mount point for external volumes. | VOLUME ["/data"] |
| LABEL | Adds metadata key-value pairs to the image. | LABEL version="1.0" |
ENTRYPOINT vs CMD: ENTRYPOINT defines the fixed executable that always runs; CMD provides default arguments that can be overridden at docker run time. Combining them: ENTRYPOINT ["java", "-jar"] CMD ["app.jar"] — you can override just the jar name at runtime with docker run myimage different-app.jar. Use exec form (["java", "-jar"]) not shell form (java -jar) for ENTRYPOINT so the process receives OS signals directly — critical for graceful shutdown handling. ARG variables are NOT available at runtime (only during build). Use ENV for runtime variables. Never put secrets in ENV in Dockerfiles — use Docker secrets or environment injection at deployment time.
Multi-Stage Builds, Caching & .dockerignore
| Approach | Image Size | Build Time |
|---|---|---|
| Single-stage (npm start) | ~520 MB | Slow (no caching) |
| Single-stage (serve build) | ~480 MB | Slow (no caching) |
| Multi-stage (Nginx) | ~22 MB | Fast (with caching) |
| Multi-stage + cache + ignore | ~22 MB | Very fast (cached layers) |
Layer caching works by hashing each instruction and its inputs. If the hash matches a cached layer, Docker reuses it without re-executing. COPY instructions are sensitive — Docker checksums file contents. For Go applications, use: COPY go.mod go.sum ./ then RUN go mod download before copying source. For Java Maven apps: COPY pom.xml ./ then RUN mvn dependency:go-offline. BuildKit (enabled by default in Docker 23+) adds parallel stage building, better caching, and cache mounts (RUN --mount=type=cache,target=/root/.m2 mvn package) that dramatically speed up builds in CI pipelines.
Data Persistence & Bind Mounts
| Type | Description | Best For |
|---|---|---|
| Named Volume | Docker manages the storage location. Created with docker volume create. | Databases (MongoDB, PostgreSQL, MySQL) |
| Anonymous Volume | Auto-created by Docker with a random name. Not reusable easily. | Temporary scratch space |
| Bind Mount | Maps a specific host directory path into the container. | Dev workflow — edit code on host, see changes in container instantly |
Deleting a volume permanently destroys all data stored in it. Docker does NOT delete volumes when you delete containers — this is intentional protection. Always backup volume data before deleting. Use docker volume rm only when you are certain the data is no longer needed.
Volumes are stored at /var/lib/docker/volumes/ on Linux hosts and inside the Docker Desktop VM on Mac/Windows. Docker volume drivers (like Rex-Ray, Portworx, AWS EFS driver) enable volumes that span multiple hosts — essential for stateful containers in Docker Swarm or Kubernetes. In Kubernetes, this concept maps to PersistentVolumes and PersistentVolumeClaims. Bind mounts are great for development but should not be used in production — they tie the container to a specific host path and break portability. For application configuration in production, use Docker secrets or environment variables instead of bind-mounting config files.
Pushing & Pulling Images
Set ECR lifecycle policies to auto-delete old image tags and control storage costs: keep only last 10 images per repository. In AWS Console → ECR → Repository → Lifecycle Policy.
ECR integrates seamlessly with IAM — EC2 instances and ECS tasks with the right IAM role can pull images without any explicit login command. For Kubernetes (EKS), use the ecr-credential-helper or the ECR public gallery for public images. ECR image scanning (powered by Clair/Inspector) automatically scans images for known CVEs on push. Enable immutable image tags in ECR production repos to prevent accidental tag overwriting — once pushed, a tag cannot be overwritten. Container image signing with AWS Signer or Cosign/Sigstore provides supply chain security verification.
Multi-Container Applications
| Feature | Description |
|---|---|
| depends_on | Controls startup order. Service waits for listed services to be running (not healthy). Use with healthcheck for production. |
| networks | Services on the same network communicate by service name as DNS. Auto-creates network if not specified. |
| volumes | Named volumes persist data across docker compose down/up cycles. Anonymous volumes do not. |
| restart | never / on-failure / always / unless-stopped. Use unless-stopped for production services. |
| environment | Pass env vars to containers. Reference .env file variables with ${VAR_NAME}. |
| build | Build image from local Dockerfile instead of pulling. Specify context and dockerfile path. |
When you change only one service in docker-compose.yml and run docker compose up -d — only that changed service is recreated. Other services are left untouched. This makes Compose very efficient for iterative development.
Docker Compose V2 (docker compose, no hyphen) is the current version — built in Go, ships with Docker Desktop, faster and more reliable than V1 (docker-compose, Python). Always use V2. Compose profiles (profiles: [dev]) let you define services that only start in certain environments — run docker compose --profile dev up to include dev-only services like mock servers or test databases. Compose Watch (docker compose watch) provides hot-reload for development: automatically rebuilds and restarts services when source files change — no more manual docker compose up --build cycles.
Architecture, Performance & Trade-offs
| Aspect | Virtual Machine (VM) | Docker Container |
|---|---|---|
| Isolation Level | Hardware virtualisation (hypervisor) | OS-level virtualisation (namespaces/cgroups) |
| Guest OS | Full OS per VM (GBs of overhead) | Shares host OS kernel (no guest OS) |
| Size | GBs (OS + app + libs) | MBs (app + libs only) |
| Startup Time | Minutes (boot full OS) | Milliseconds (start process) |
| Resource Usage | High (dedicated CPU/RAM per VM) | Low (shared kernel, minimal overhead) |
| Portability | Image tied to hypervisor type | Run on any Docker-compatible host |
| Security Isolation | Stronger (separate kernel per VM) | Good but shared kernel is a larger attack surface |
| Use Case | Legacy apps, different OS, full isolation | Microservices, CI/CD, modern cloud-native apps |
AWS EC2 instances ARE virtual machines — when you run Docker containers on EC2, you have VMs running containers (both layers). AWS Fargate goes one step further: it abstracts away the EC2 VM and lets you run containers without managing any servers — fully serverless containers. The security model matters: containers share the host kernel, so a kernel exploit in one container potentially affects others. VMs with separate kernels prevent this. For highly sensitive multi-tenant workloads, use dedicated VMs per tenant or gVisor/Kata Containers which add a sandboxed kernel layer to containers, combining VM-level security with container-level performance.
Commands, Flags & Patterns
| Flag | Command | Meaning |
|---|---|---|
| -d | docker run | Detached mode (background) |
| -p host:container | docker run | Port mapping |
| -e KEY=value | docker run | Environment variable |
| -v vol:/path | docker run | Volume mount |
| --name | docker run | Assign container name |
| --network | docker run | Connect to network |
| --rm | docker run | Auto-remove on exit |
| -it | docker run / exec | Interactive terminal |
| -f / --file | docker build | Specify Dockerfile name |
| -t | docker build | Tag the built image |
| --build-arg | docker build | Pass ARG variable |
| -a | docker ps | Show all containers (incl. stopped) |
| -q | docker ps | Show only container IDs |
Docker BuildKit (now default in Docker 23+) unlocks advanced features: parallel multi-stage builds, cache mounts (persist package manager caches between builds), secret mounts (securely pass secrets during build without them landing in image layers), and SSH mounts (access private git repos during build). Cache mounts alone can reduce Node.js build times from 2 minutes to 10 seconds in CI. Example: RUN --mount=type=cache,target=/root/.npm npm ci. Container image signing with Cosign and the Sigstore project enables cryptographic verification that images haven't been tampered with between build and deployment — increasingly required in regulated industries and government cloud environments.
A production-grade reference covering container orchestration — from fundamentals to advanced operations, security, and scaling.
Container Orchestration Fundamentals
| Feature | Description |
|---|---|
| Self-Healing | Automatically restarts failed containers and replaces pods when nodes die. |
| Auto Scaling | Scales applications up/down based on CPU, memory, or custom metrics. |
| Load Balancing | Distributes traffic across multiple pod replicas automatically. |
| Rolling Updates | Deploys new versions gradually without downtime. |
| Secret Management | Stores and manages sensitive data like passwords and API keys. |
| Automatic Bin Packing | Places containers on nodes based on resource requirements. |
Control Plane & Worker Nodes
In production, run multiple control plane nodes for high availability. If etcd loses data, you lose your entire cluster state — always have backups!
Local Development & kubectl
| Tool | Best For | Notes |
|---|---|---|
| Minikube | Learning, development | Multi-node support, add-ons, VM or Docker driver |
| Kind | CI/CD, testing | Kubernetes in Docker, fast startup, multi-node |
| k3s | Edge, IoT, low resources | Lightweight (~50MB), uses SQLite instead of etcd |
| Docker Desktop | Mac/Windows dev | Built-in K8s option, single node |
The Smallest Deployable Unit
Pods are ephemeral — they can be killed and recreated anytime. Never deploy pods directly. Use Deployments or StatefulSets for production workloads.
Self-Healing & Rolling Updates
Use kubernetes.io/change-cause annotation to document changes. It appears in rollout history and helps teams understand what changed in each revision.
Stable Networking & Load Balancing
| Type | Scope | Use Case |
|---|---|---|
| ClusterIP | Internal only | Default. Inter-service communication within cluster. |
| NodePort | External via node | Opens port 30000-32767 on all nodes. Development use. |
| LoadBalancer | External via cloud LB | Provisions cloud load balancer. Production external access. |
| ExternalName | DNS alias | Maps service to external DNS name. |
NodePort opens ports on every node — security concern in production. Use LoadBalancer or Ingress instead.
HTTP Routing & TLS
Use cert-manager to automatically provision and renew TLS certificates from Let's Encrypt.
Cluster Organization & Isolation
| Namespace | Purpose |
|---|---|
| default | Resources created without specifying namespace |
| kube-system | Kubernetes system components (API server, scheduler, etc.) |
| kube-public | Publicly readable resources, cluster info |
| kube-node-lease | Node heartbeat leases for health detection |
Deleting a namespace deletes ALL resources in it. Be extremely careful with kubectl delete namespace in production!
Persistent Storage
| Type | Lifetime | Use Case |
|---|---|---|
| emptyDir | Pod lifetime | Scratch space, cache shared between containers |
| hostPath | Node lifetime | Access node filesystem (avoid in production) |
| configMap/secret | Pod lifetime | Mount config files or secrets |
| persistentVolumeClaim | Independent | Databases, stateful apps — survives pod/node restarts |
| Mode | Description |
|---|---|
| ReadWriteOnce (RWO) | Single node read-write |
| ReadOnlyMany (ROX) | Many nodes read-only |
| ReadWriteMany (RWX) | Many nodes read-write |
Use StorageClass with volumeBindingMode: WaitForFirstConsumer to create PVs in the same zone as the pod that needs them.
Stateful Application Management
| Aspect | Deployment | StatefulSet |
|---|---|---|
| Pod Names | Random (catalog-7d9f8b-x2k4j) | Ordered (postgres-0, postgres-1) |
| Creation Order | Parallel | Sequential (0 → 1 → 2) |
| Deletion Order | Random | Reverse (2 → 1 → 0) |
| Storage | Shared PVC | Per-pod PVC |
| Network ID | Changes on restart | Stable DNS per pod |
Configuration Management
Secrets are base64 encoded, not encrypted! Anyone with cluster access can decode them. Use external secret managers (Vault, AWS Secrets Manager) for production.
Liveness, Readiness & Startup
| Mechanism | Success Condition | Best For |
|---|---|---|
| httpGet | HTTP 2xx-3xx response | Web services |
| exec | Command exit code 0 | Custom scripts |
| tcpSocket | TCP connection succeeds | Databases |
| grpc | gRPC health check | gRPC services |
Liveness probes should NEVER check external dependencies. A database outage shouldn't restart your app — it should stop receiving traffic (readiness).
Requests, Limits & QoS
| Requests | Limits | |
|---|---|---|
| Purpose | Minimum guaranteed | Maximum allowed |
| Scheduling | Used by scheduler | Not considered |
| CPU Exceeded | N/A | Throttled |
| Memory Exceeded | N/A | OOMKilled |
| Class | Condition | Eviction Priority |
|---|---|---|
| Guaranteed | requests == limits | Last |
| Burstable | At least one request/limit | Middle |
| BestEffort | No requests or limits | First |
Start with requests = typical usage, limits = 2-3x requests. Monitor actual usage with kubectl top and adjust based on real data.
Affinity, Taints & Tolerations
| Mechanism | Purpose |
|---|---|
| nodeSelector | Simple label matching |
| Node Affinity | Advanced selection with operators |
| Pod Affinity | Co-locate with other pods |
| Pod Anti-Affinity | Spread away from pods |
| Taints/Tolerations | Repel pods from nodes |
Use pod anti-affinity with topologyKey: kubernetes.io/hostname to spread replicas across nodes for high availability.
Role-Based Access Control
| Type | Scope |
|---|---|
| Role + RoleBinding | Single namespace |
| ClusterRole + ClusterRoleBinding | Entire cluster |
Name, email, and mobile number on registration. Passwords stored using Argon2id hashing — never plaintext. OTPs cleared immediately after use.
Authentication and personalising your experience only. Never sold or shared with third parties.
HTTPS everywhere. HttpOnly + Secure + SameSite=Strict cookies. CSRF tokens on all POST actions.
One session cookie for authentication. No tracking or advertising cookies.
Email bsuresh@devopsclicks.com for data deletion or privacy concerns.
Content is for personal learning only. Commercial redistribution requires written consent.
Always verify commands against official documentation before production use.
Keep credentials secure. DevOpsClicks is not liable for unauthorized access from user negligence.
Terms may update at any time. Continued use constitutes acceptance.
Log in with your mobile number and password.