Docker & Kubernetes Foundations Course
A structured intermediate course covering Docker images, containers, volumes, networking, Compose, and core Kubernetes workloads, services, storage, and troubleshooting.
What you will learn
- Build and optimize Docker images with Dockerfiles
- Run, inspect, and persist container data with volumes
- Connect containers with Docker networks and Compose
- Deploy core Kubernetes workloads with ConfigMaps and Secrets
- Expose applications and troubleshoot Kubernetes clusters
Before you start
- Basic Linux command-line experience
- Familiarity with HTTP, ports, and application deployment
- The Docker & Kubernetes practice bank is useful for self-assessment
Lesson 1 Docker Images and Dockerfiles
A Docker image is a read-only template containing an application, its runtime, libraries, and configuration. Images are built from Dockerfiles using layers: every instruction such as FROM, COPY, RUN, or ENV creates a layer, and unchanged layers are cached to make rebuilds faster. Smaller, more secure images prefer minimal base images, combine related RUN commands, and copy only files the application needs.
The FROM instruction selects the base image. COPY moves files from the build context into the image, while RUN executes commands during the build. CMD provides the default command and arguments used when the container starts, and ENTRYPOINT defines the executable that always runs. When both are present, the entrypoint receives command-line arguments as its parameters. A health check or non-root user can be added with HEALTHCHECK and USER.
Use docker build -t myapp:latest . to build an image and docker image ls to list images. Tag images with meaningful version numbers and avoid using only latest in production. The goal is an image that is deterministic, small, and reproducible from the Dockerfile alone.
Image drill: use a small base image, combine RUN commands, copy only needed files, and run as a non-root user. Order layers so cacheable steps come first, and scan images for vulnerabilities.
Example
A Node.js application needs only its package files to install dependencies and its source code to run. The Dockerfile starts with FROM node:20-alpine, copies package.json first, runs npm ci, copies the source, switches to a non-root user, and defines CMD ["node", "server.js"]. Copying dependencies before source lets Docker reuse the dependency layer until package files change.
Worked example: Multi-stage build: compile in one stage, copy only the runtime binary into the final image.
Lesson 2 Container Lifecycle and Persistent Storage
A container is a running instance of an image. Docker creates, starts, stops, restarts, and removes containers with commands such as docker create, docker start, docker stop, and docker rm. The common shortcut docker run creates and starts a container in one step. Use docker ps for running containers, docker ps -a to include stopped containers, and docker logs to inspect application output.
Container filesystems are ephemeral: when a container is removed, changes written inside it disappear. Volumes and bind mounts solve this. A named volume is managed by Docker and survives container removal; a bind mount maps a host directory into the container. The -v and --mount flags attach storage, and containers can share the same volume. Read-only mounts prevent accidental modification.
For debugging, docker exec -it container bash opens a shell inside a running container, and docker inspect shows configuration and state details. Attach --restart policies only when the application should recover automatically, and keep logs and data outside the container when the container itself is replaceable.
Container drill: know create, start, stop, restart, rm, and run. For persistence, use volumes or bind mounts so data survives container removal; avoid storing state in the writable container layer.
Example
A database container is started with docker run -d --name postgres -v pgdata:/var/lib/postgresql/data -e POSTGRES_PASSWORD=secret postgres:16. The named volume pgdata keeps database files after docker rm postgres, so a replacement container using the same volume restores the same data.
Worked example: Run a database container with a named volume so the data persists after docker rm.
Lesson 3 Docker Networking and Compose
Docker networks let containers communicate by name or IP. The default bridge network provides automatic DNS between containers on the same network, while the host network shares the host network stack and removes isolation. User-defined bridge networks are recommended because they support DNS resolution and can be attached or detached without recreating containers. Publish a container port to the host with -p 8080:80; the left side is the host port and the right side is the container port.
Docker Compose defines multi-container applications in a YAML file. Services declare images, build contexts, ports, volumes, environment variables, and networks. Running docker compose up -d creates the network and starts services together; docker compose down removes containers and the default network while preserving named volumes. Compose also supports dependencies, health checks, and scaling service replicas.
Debug connectivity with docker network ls, docker network inspect, and docker exec. A common failure is a web container that cannot reach a database because they are on different networks or because the application uses localhost instead of the service name. Use service names in Compose so the built-in DNS resolves to the correct container.
Network drill: use custom bridge networks for DNS-based communication, host mode only when isolation is acceptable, and Compose to define multi-container apps. Name services so containers resolve each other by name.
Example
A compose file defines web and db services. The web service maps "8080:3000" and connects to the database using the hostname db and port 5432. The database service mounts a named volume and sets a startup health check. Running docker compose up -d starts both services on one private network, and the browser reaches the app at http://localhost:8080.
Worked example: In Compose, the app service connects to the database service by its service name.
Lesson 4 Kubernetes Core Workloads
Kubernetes runs containerized applications on a cluster of nodes. The smallest deployable unit is a Pod, which wraps one or more containers with shared storage, network, and lifecycle. Pods are usually created by controllers. A Deployment manages stateless replicas with rolling updates and rollbacks; a StatefulSet gives stable network identities and ordered scaling for stateful applications; a DaemonSet runs exactly one Pod on every selected node; a Job runs a workload to completion; and a CronJob runs Jobs on a schedule.
Configuration is separated from code with ConfigMaps and Secrets. A ConfigMap holds non-sensitive data such as environment variables or config files, while a Secret stores sensitive values such as passwords and API tokens. Both can be mounted as volumes or injected as environment variables. Secrets are base64-encoded in the API but should still be protected with RBAC and encryption at rest.
Controllers reconcile desired state: you declare the desired number of replicas, and Kubernetes creates or removes Pods until reality matches. Labels and selectors connect controllers to Pods, so use meaningful labels such as app and tier. Apply manifests with kubectl apply -f and inspect workload status with kubectl get pods, kubectl describe deployment, and kubectl rollout status.
Workload drill: use Deployment for stateless replicas, StatefulSet for stable identity and storage, DaemonSet for per-node agents, and Job/CronJob for batch work. Define resource requests and limits for predictable scheduling.
Example
A Deployment manifest declares three replicas of an nginx container with labels app: web. A ConfigMap supplies the value of APP_MODE, and a Secret supplies DB_PASSWORD. If a node fails, the controller creates replacement Pods on healthy nodes, keeping the desired count at three.
Worked example: A web API uses a Deployment with three replicas and a Service in front.
Lesson 5 Kubernetes Services, Storage, and Troubleshooting
A Service provides a stable network endpoint for a set of Pods. ClusterIP exposes the Service inside the cluster, NodePort opens a port on every node, and LoadBalancer integrates with the cloud provider for external traffic. An Ingress routes HTTP and HTTPS traffic to Services using hostnames and paths, typically through an ingress controller. Selectors in the Service must match Pod labels, and the Service targetPort must match the container port.
Persistent storage uses PersistentVolumeClaim (PVC) to request storage, PersistentVolume (PV) to provide it, and StorageClass to define provisioning behavior. A PVC bound to a PV can be mounted into a Pod; ReclaimPolicy controls whether the volume is retained or deleted when the PVC is released. Stateful workloads such as databases should use stable storage and understand how volume claims map to Pods.
Troubleshooting follows a structured order: kubectl get pods to see status, kubectl describe pod for events and conditions, kubectl logs for application output, and kubectl exec for interactive inspection. Check image pull errors, CrashLoopBackOff causes, readiness probes, Service selectors, and network policies. A review workflow of building images, testing locally, deploying small changes, and rolling back quickly turns Kubernetes practice into a repeatable skill.
Service drill: ClusterIP for internal, NodePort for node-level access, LoadBalancer for cloud external access, and Ingress for host/path routing. Use PersistentVolumeClaims for storage and kubectl logs/describe to debug.
Example
A Pod stays in CrashLoopBackOff after a new image tag is deployed. kubectl describe pod shows the container exit code, kubectl logs reveals a missing environment variable, and the Deployment references a ConfigMap that does not exist. After creating the ConfigMap and redeploying, the Pod reaches Running and the Service starts returning traffic.
Worked example: If a Pod is CrashLoopBackOff, run kubectl logs and kubectl describe pod to find the reason.