
Meet your instructor, Gourav Shah, a DevOps coach, corporate trainer, author, and speaker who has spent 18 years building this step-by-step learning path. He has trained engineers at companies like Cisco, Walmart Labs, Visa, and Intuit, taught over 60,000 students across 160 countries, and authored courses for the Linux Foundation. By the end, you will know the background and teaching approach behind this course.
Understand why the world moved to container-based software delivery using a simple shipping container analogy. You will see how a container image gives you one standard, OCI-compliant packaging format for any application, whether it is Java, Node.js, Python, or Golang. After this, you can explain why the same container image runs the same way on your laptop, your data center, or the cloud.
Learn what containers really are by comparing them with bare metal servers and virtual machines. You will walk through the boot process and the overhead a VM carries before your application even starts, and see how a container runtime replaces the hypervisor layer. After this, you can explain why containers are lightweight and fast: they run just your application and its runtime, with no bios, boot loader, or extra kernel to spin up.
Watch a side-by-side demo of a VM and a container so the difference stops being theory. You will see the dozens of housekeeping processes a VM runs versus a container that holds only your application, launch a container with docker run in under a second, and compare network interfaces, PIDs, and the operating system inside each. After this, you can point to exactly where the container's isolation comes from and why it feels like a virtual environment without being one.
Go under the hood and learn how Linux namespaces make a process look like it runs in its own isolated environment. You will see the PID, network, mount, UTS, IPC, and user namespaces at work, and trace how eth0 inside a container maps to a veth pair on the host. After this, you can explain why a container shares the host kernel, why an Alpine container runs on an Ubuntu host, and why you cannot run a Windows container directly on Linux.
Learn how cgroups, or control groups, put limits on CPU, memory, disk, and network so one container with a memory leak cannot starve the others or the host. You will also see how cgroups do resource accounting, which is exactly what docker stats reads when it shows live CPU and memory usage. After this, you can explain how container monitoring tools rely on cgroups underneath.
Understand what makes container image distribution so efficient: images are stored as layers and stacked at runtime using a union or overlay filesystem. You will pull an image, inspect its layers with docker image history, and watch how pulling a new version only downloads the changed layers instead of the whole image. After this, you can explain the overlay2 storage driver and why shipping a new app version moves megabytes, not hundreds of megabytes.
Learn the copy-on-write technology that lets many containers share one read-only image while each keeps its own writable layer. You will create a file inside one container, inspect the change with docker diff, and turn it into a new image with docker commit in a git-like workflow. After this, you can explain why launching a container from even a large image is near instant, and why containers save disk space instead of cloning the image every time.
Understand Docker's real job: it is a translator between you and the Linux kernel features that make containers possible, like namespaces, cgroups, and capabilities. You will trace the stack from LXC to libcontainer to runc, then containerd, and finally the Docker daemon that adds image building, volumes, and networking on top. After this, you can explain how the OCI standard and container runtimes fit together, and why Docker sits where it does in the ecosystem.
Get a clear answer to the question that worried many people when Kubernetes announced it was deprecating Docker. You will learn what the dockershim actually did, why the Container Runtime Interface (CRI) matters, and how containerd and CRI-O fit in. After this, you can explain why nothing changes for you as a developer, why platform engineers just swap the daemon for a CRI-compliant runtime, and why your OCI-compliant images keep running on Kubernetes.
Docker follows a client-server model: the Docker client talks to the Docker engine (daemon) running on a Docker host, locally or over a remote port. You'll see how images flow from a registry like Docker Hub down to the host, why Mac and Windows need a Linux VM or WSL2, and where an orchestration engine like Kubernetes fits once you move past a single host. After this you can picture the full container ecosystem and explain how each piece connects.
The container development workflow mirrors Git: you pull an image, run a container, use docker diff and docker commit to capture changes, then docker push a new image to the registry. You'll follow the full CI/CD path too, from a Dockerfile build to testing with docker run or Docker Compose, publishing to the registry, and deploying through Kubernetes YAML manifests with GitOps tools like Argo CD or Flux. After this you can map your own build, test, and deploy pipeline for containers.
Before the hands-on exercises, you need a working environment to run Docker containers and do your development. This lecture walks through your options: the Docker Toolbox, Docker Desktop for Mac or Windows (the recommended path), a cloud-based setup if you'd rather install nothing locally, and a packaged lab environment you can run anywhere. After this you'll know which setup fits your machine so you're ready to start running containers.
Docker for Mac/Windows is the recommended way of setting up the environment. It gives a appearance of running a native application and sits in the system toolbar. However, remember this will launch docker by default every time you start your system and keep running in the background. If you do not desire so, choose docker toolbox.
This is the introduction to the sample, muti tier, micro services based application that we would use throughout this training. This is a official application created by Docker itself and contains combination of technologies such as python, node.js, redis, postgres, java/dot net.
This section is where you start actually running containers and operating them day to day. You'll launch your first container, use the -i and -t options to work interactively, run containers in the background with -d, check and manage logs, get inside a container's shell with docker exec, expose your application with port mapping, and manage the container lifecycle using start, stop, and rm. After this you can run, troubleshoot, and control containers on your own with Docker.
Before launching a container, you'll validate your setup with docker version and docker system info, then watch the daemon live using docker system events. This lecture also breaks down how images and registries work: the four-part image name (registry, namespace, repository, tag), why an official image has no username, and why Docker Hub is always the default registry you can't change. After this you can read any image reference and know exactly where it comes from.
Here you run your first real container with docker run, using a tiny Alpine image and the uptime command, and watch every step on the daemon side through docker system events. You'll see the full launch sequence: pull the image, create the container, attach to the app, connect the network, start it, then die and disconnect when the command exits. You'll also list containers with docker ps, docker ps -l, and docker ps -a. After this you understand why a container lives only as long as its process.
This lecture shows how to launch an interactive container using docker run -it with a shell, so you can actually get inside and explore it. You'll poke around the namespaces with ps, ifconfig, and hostname to see what a container isolates, and compare kernel, CPU, and uptime against the host to learn what it shares instead. After this you understand exactly what is namespaced and what containers borrow straight from the underlying system.
The -d flag is the option you'll reach for most: it runs a container in detached mode so it keeps working in the background and hands your shell back. You'll see the difference from running attached, learn the graceful control-P control-Q detach that only works with -it, and why control-C kills a container that isn't interactive. You'll also name containers with --name. After this you can keep long-running applications alive in the background and attach or detach whenever you need.
When an app inside a container misbehaves, logs are your first stop. You'll use docker logs and docker logs -f to read and follow output, learn how the daemon captures logs by attaching to stdout and stderr, and find the JSON log file on the host with docker inspect. You'll also see how logging drivers forward logs to Splunk, Fluentd, or AWS, and why you should never write logs inside the container. After this you can debug containers through their logs and choose the right logging setup.
This lecture covers docker exec, the way you run ad-hoc commands or open a shell inside a container that's already running. You'll run a one-off command with docker exec, then use docker exec -it with sh or bash to get an SSH-like connection, and see why real SSH doesn't work when there's no sshd inside. You'll also confirm your changes on the writable layer with docker diff. After this you can jump into any running container to troubleshoot and modify it.
Run a container as a throwaway local dev environment, just like you would use a VM. You will launch an Ubuntu container with docker run, get inside it using docker exec, install a package such as Redis, and see those changes with docker diff. You will also learn that docker stop and docker start keep your work, so you can pick up right where you left off.
Reach a web app running inside a container from outside. You will map ports three ways: small -p with host and container port, capital -P to auto-pick a host port from the image's EXPOSE metadata, and -p with only the container port. You will see how Docker sets this up with iptables rules, and know which style to pick when you run more than one container.
Follow a container through its full life: create, start, stop, and remove. You will learn that docker run is really pull, create, and start rolled into one, and split those into docker container create and docker container start when you need to copy files before launch. You will also stop containers with SIGTERM, force-remove running ones, and clean up space with docker container prune and docker system prune.
Cap how much memory a container can use so one container cannot starve the rest. You will watch live usage with docker stats, then set a memory limit on a running container with docker update -m, no restart needed. You will understand how cgroups do the actual limiting and accounting underneath, and how to change these limits on the fly.
Control how CPU is shared between containers when they compete for it. You will use --cpus to pin a container to a fraction of a core, and --cpu-shares to set relative weight, where 1024 is the default. Running a stress test across a few containers, you will see how higher shares win a bigger slice of CPU when the host is busy.
This section is about packaging your application into a container image, the one skill every developer building containers needs. You will learn two ways to build images: the manual approach and the far more common Dockerfile approach. Along the way you will examine images, tag and push them to a registry, understand image layers, and work through the key Dockerfile instructions like RUN, COPY, CMD, and ENTRYPOINT.
See the two ways to turn source code into a container image, using the Spring Pet Clinic Maven app as the example. The imperative way means launching a container, copying code in, building it by hand, and committing the result. The declarative way writes those same steps as a Dockerfile. You will fork the app to get ready, and learn why the resulting image is OCI compliant and runs on any runtime.
Build a container image by hand, step by step, so you truly understand what a Dockerfile automates later. You will clone your fork, launch a Maven build container with port mapping, copy your code in with docker cp, run maven package to produce the jar, and test-run it in the browser. Then you will commit those changes into your own image with docker container commit and tag it for Docker Hub.
Take the same manual steps and turn them into a Dockerfile, then build the image automatically. You will read a real Dockerfile for the Spring Pet Clinic app and see how FROM, WORKDIR, COPY, RUN, CMD, and EXPOSE map to the steps you did by hand. You will run docker image build with a -t tag and the build context path, and learn the common errors when either is missing.
Look inside your images and ship them to a registry. You will use docker image history to compare the layers in your manual v1 against your Dockerfile-built v2, and see how the build cache skips work until code changes. Then you will move the latest tag with docker image tag, log in with docker login, and push everything to Docker Hub with docker image push.
Walk through the Docker image build process step by step and see exactly how each layer is born. You will learn that Docker launches an intermediate container for every instruction, commits the change into a new layer, then removes that container before moving on. By the end you can read docker image history and explain why FROM brings the base layers while every other instruction adds one layer of its own.
Start writing your own Dockerfiles the right way by studying real official images like MySQL and Tomcat on Docker Hub. You will learn the Dockerfile syntax, why instructions are written in capitals by convention, and why FROM must be the very first instruction. After this you can pick a base image using registry, namespace, repository, and tag, and understand how it seeds the first intermediate container in the build.
The RUN instruction is where you install packages, compile code, and build your application inside the intermediate container. You will learn why combining commands into one RUN with && keeps your image small and stays under the layer limit, and why deleting a downloaded file in a separate layer still leaves it bloating the image. You will also learn to order layers so the parts that change often sit near the bottom of the Dockerfile.
Learn how WORKDIR sets the working folder for every instruction that follows, so you never need CD in your RUN commands. You will see that COPY's source is relative to the build context while the destination is relative to WORKDIR, and why sending a huge build context to the Docker daemon slows builds and fills your disk. You will also learn when ADD's remote fetch and auto-extract help, and why COPY is the safer default. ENV is covered too.
EXPOSE and CMD are launch-time metadata, not build steps. You will learn how EXPOSE documents the port your application listens on and how docker run -P maps it to a host port automatically. You will also learn how CMD defines the default command that runs when a container starts, why it must run in the foreground instead of using systemctl or service, and why only the last CMD in the Dockerfile wins.
See how ENTRYPOINT and CMD work together at launch time. You will learn to use ENTRYPOINT for initialization steps, like the MySQL entrypoint script that sets the root password and creates databases at runtime instead of hardcoding secrets in the image, then hands control to CMD for the actual application. You will also learn why CMD is easy to override with docker run for debugging a container that keeps exiting.
Learn how VOLUME marks a path inside the container for persistent storage, so a database keeps its data even after the container is deleted. You will also learn why running containers as root is a security risk, and how the USER instruction drops privileges to a non-root user, as the official Jenkins image does. After this you can write safer Dockerfiles and know where to find the official Dockerfile reference for the rest.
Put your Dockerfile skills to the test by containerizing a real C application from source, the way you would on a real job. You will clone the code, test-build inside an Ubuntu container by installing the build tools and running make all, then turn that working process into a Dockerfile. By the end you will have built, tested, and published a working image to Docker Hub, going from source code to a running container on the given port.
This section takes your image building past the basics into advanced optimization every developer should know. You will get a preview of what is coming: multi-stage Dockerfiles, running tests inside the build, speeding up image builds, offline image distribution without a registry, and setting up your own private registry. After this intro you will know the roadmap for writing the smallest, most efficient images possible.
Learn why a single-stage image carries dead weight it does not need: source code, Maven, the JDK, and other build tools all shipped to production. You will see how a multi-stage Dockerfile uses multiple FROM blocks to compile in a build stage, then copy only the finished artifact into a tiny runtime image built on something like Alpine. After this you can write multi-stage Dockerfiles that produce clean, minimal images with just the application and its runtime.
Walk through a real multi-stage Dockerfile for a Maven-based Java web app, where one stage uses Maven and the JDK to build the war file and a second Tomcat stage only carries the artifact and runtime. After this you can read the two FROM blocks, see why the build tools and source code stay behind in an untagged image, and understand how COPY --from keeps your final image clean.
Your turn to refactor the single-stage Spring PetClinic Dockerfile into a multi-stage build. Right now the image is bloated at around 782 MB because it ships Maven, the JDK, source code, and every dependency. In this exercise you'll add a build stage plus a slim runtime stage on an Alpine Java base, aiming to cut the image to roughly one-fifth the size for both faster pulls and a smaller attack surface.
See the worked solution for converting the Spring PetClinic Dockerfile into a multi-stage build. You'll define a named build stage that runs mvn package, then a run stage on openjdk:8-alpine that copies only the jar with COPY --from=build. By the end you can tag and build the image, compare the 782 MB original against the ~154 MB result, and explain where that size saving comes from.
Learn a neat trick to fold testing into the same Dockerfile using a dedicated test stage built on top of your build stage. Because it only adds a CMD, you use docker build --target test to build an image that runs your unit tests when launched. After this you can wire separate build, test, and package steps into one Dockerfile and trigger just the test stage from a CI pipeline.
Find out why your build time drops from about 13 minutes to under 30 seconds when the base image already carries Maven and the project dependencies, versus pulling a plain Maven image that fetches everything fresh. You'll also see the --no-cache flag in action and weigh the options for caching dependencies, including bake them into a custom base image versus mounting a volume, to speed up your Docker image builds.
Learn how to move Docker images between machines without any registry using docker image save and load. You'll save an image as a tar archive, see why save/load keeps all the layers while container export/import flattens them, then delete and reload the same image to prove it works. After this you can ship images offline over a copy, an S3 bucket, or an air-gapped server.
Set up Harbor, an open-source self-hosted container registry, as an alternative to Docker Hub. You'll grab the online installer, edit harbor.yaml with your host's IP, and run install.sh, which brings up all of Harbor's components as containers through Docker Compose. By the end you can log into the Harbor UI as admin and have a private registry ready to receive your images.
Push images to your Harbor registry by retagging them with the registry URL, project, repository, and tag, no rebuild needed. You'll create a Harbor user and project, add the insecure-registry entry to daemon.json for plain HTTP, and docker login before pushing. After this you can publish to any external registry including GCR, ECR, or ACR, since the same tagging pattern applies.
What You Will Learn in this Section
Understand the evolving container development tools landscape and how it impacts modern DevOps workflows.
Learn the difference between Docker, runc, and containerd, and how they work together (or separately).
Discover whether and how runc can replace Docker in a containerized setup.
Get a clear understanding of how containerd works as a high-performance container runtime.
Explore Podman as a Docker alternative and learn how to run containers without a daemon.
Learn to run rootless containers with Podman for improved security and developer experience.
Use Buildah to build container images as a non-root user, aligning with container security best practices.
Learn how to build container images from scratch — starting with just a binary and dependencies.
Discover how to create custom base images with Buildah, giving you more control over your image layers.
Practice building multi-stage container images with Buildah to optimize for size and security.
In this lesson, we explore the modern container development landscape beyond Docker. You'll get a high-level overview of essential tools like runc, containerd, Podman, Buildah, and Skopeo, and understand how each fits into the container ecosystem. This sets the foundation for the rest of the course by showing how container tools have evolved and become more modular.
Ever wondered what powers Docker under the hood? In this lesson, we demystify runc, the lightweight runtime responsible for actually running containers. You'll learn how runc fits into the container stack and whether it's feasible to use it directly instead of Docker. We’ll also discuss its limitations and where it fits in real-world workflows.
Docker has transitioned to using containerd as its core runtime – but what is containerd exactly? This lesson breaks down containerd’s architecture and its role in managing container lifecycles. You'll learn about its components and how it serves as the bridge between high-level tools like Docker or Podman and low-level runtimes like runc.
Security-conscious environments prefer running containers as non-root users. In this lesson, you’ll learn how Podman enables rootless containers, why it's safer, and how to set it up on your system. We’ll run a few containers as a regular user and explore the benefits of this approach in development and production.
Buildah is a powerful image-building tool that doesn’t require root privileges or a daemon. In this lesson, you’ll learn how to use Buildah to build container images as a non-root user. We’ll explore how Buildah works with container registries, and how it offers more control compared to Docker’s build process.
This lesson shows you how to create your own custom base images using Buildah. You'll learn the steps to craft lightweight and purpose-built images from the ground up, giving you complete control over what's included — a great step toward optimizing performance and security.
Multi-stage builds help reduce image size and improve security. In this lesson, you'll learn how to perform multi-stage builds using Buildah. We'll walk through a practical example where we separate build and runtime environments, producing efficient images ideal for production deployment.
Build the Spring PetClinic image step by step with Buildah instead of a Dockerfile, running rootless as a normal user. You'll map each Dockerfile instruction to its Buildah equivalent, buildah from, config --workingdir, copy, run, and commit, across a build container and a package container, using copy --from to pull just the artifact. After this you can construct multi-stage images by hand and turn the steps into a repeatable build script.
Podman is gaining popularity as a drop-in Docker replacement. In this session, you’ll get a hands-on introduction to Podman, understand how it differs from Docker, and learn how it runs containers without requiring a background daemon. We’ll also touch on compatibility with existing Docker commands and scripts.
Get the map for this module on container networking and storage on a single Docker host. You'll cover the three network types, bridge, host, and none, network segmentation with custom bridges, and a handy technique for troubleshooting container network issues that carries over to Kubernetes. On the storage side you'll meet Docker volumes, bind mounts, and tmpfs, and when to reach for each to keep data like a database persistent.
Get the full picture of how Docker networking is built on Linux network namespaces and the veth pairs behind eth0. You will learn the three default networks — bridge, host, and none — and where Docker's Container Network Model (CNM) with libnetwork differs from the CNI standard Kubernetes uses. By the end you can list networks with docker network ls, or browse them visually in Portainer, and explain what each one does.
See the default bridge network in action as you launch containers and watch them get an IP on the 172.17.0.0/16 subnet and talk to each other. You will trace how eth0 inside a container maps to a veth pair on the host, all wired into the docker0 bridge. After this you can inspect the pairing with ip addr show and brctl show, and understand exactly how inter-container communication works on one host.
Isolate containers on the same host by putting them on separate bridge networks, so one group talks freely while staying cut off from another. You will create a network with docker network create, launch containers with the --network option, and watch pings succeed inside a group but fail across groups. You will also connect and disconnect a running container from a network on the fly using docker network connect and disconnect.
Work with Docker's other two default networks and know when each fits. The host network attaches your container straight to the host interfaces, so you skip port mapping but must watch for port conflicts — handy for ingress controllers and public-facing apps. The none network is a black hole with only loopback, giving you an isolated container for local-only processing. You will also meet overlay, macvlan, and ipvlan for wider use cases.
Learn a clean way to debug container network problems using netshoot, an image loaded with tools like tcpdump, nmap, dig, netstat, and iftop. The trick is attaching netshoot to another container's network namespace, so you can troubleshoot even stripped-down images that have no shell — like Portainer. You will also connect it to the host network with privileged access to run iptables and inspect the Docker host itself.
Understand how containers spread across many hosts talk to each other using the overlay network driver. You will see why overlay depends on a service discovery mechanism like Consul, etcd, or Zookeeper, and how Docker Swarm bundles all of that in so it creates the overlay network for you automatically. After this you can explain when multi-host networking is needed and how Swarm removes the old manual key-value setup.
Get clear on Docker's three storage types and when to reach for each. You will learn volumes (managed by Docker, the recommended default for persistent storage like a database), bind mounts (for mapping an existing host path, file, or socket into a container), and tmpfs (fast in-memory storage that is ephemeral and Linux-only). By the end you can pick the right option and know why volumes outlive the container's lifecycle.
Create Docker volumes hands-on and see the difference between an auto-generated volume and a named volume. Using a MySQL example, you will mount storage with -v at /var/lib/mysql, then inspect the container to find where Docker keeps it under /var/lib/docker/volumes. You will learn to list volumes with docker volume ls and understand why named volumes make sharing storage between containers easy.
Put bind mounts and tmpfs into practice. You will mount a host directory into a container as a bind mount, see that it is writeable both ways, then remount it read-only — a clean pattern for passing config files into a container. Then you will mount system memory as tmpfs for fast but ephemeral storage, and inspect the result with df -h and /etc/mtab to confirm the file system type.
Get a map of what is coming as you move from single containers to running a full microservices stack with Docker Compose. You will first wire services together the manual, imperative way using --link and see where it falls short, then switch to a declarative Compose spec, refactor from v1 to the v3 format, and use DNS-based service discovery. By the end of the module you will build images with Compose and spin up a whole development environment fast.
See why a real monitoring setup like Prometheus is never one container but a stack of services working together — Prometheus, Node Exporter, cAdvisor, Grafana, and Alertmanager. You will watch a full stack come up with a single 'docker compose up -d' and tear down just as fast with 'docker compose down'. After this you will know when Docker Compose is the quickest way to spin up a multi-service environment on one host.
Turn the single-container Spring PetClinic app into a real two-tier microservices setup by swapping the in-memory H2 database for a MySQL container. You will launch MySQL with 'docker run', set the spring.profiles.active flag to MySQL, and point the app at the database. By the end you can start a backend database container and connect a front-end app to it, and you will see the connection failure that sets up the next lesson.
See how Docker Compose turns imperative 'docker run' commands into a declarative YAML file. You will map each piece — the container name becomes a service, -p becomes ports, -e becomes an environment block, and the image carries over. After this you understand the difference between imperative and declarative container setups, and why Compose v3 gives you automatic DNS-based service discovery so you no longer need manual links.
Write your first docker-compose.yml from scratch — the v1 spec — for the PetClinic app and its MySQL database. You will convert a 'docker run' command into services with image, ports, links, and environment blocks, and learn why the file name matters and why YAML wants two-space indentation, never tabs. After this you can hand-write a working Compose file and read the Compose reference to find the options you need.
Get hands-on with the docker-compose command itself. You will validate a spec with 'docker compose config', bring the stack up with 'up -d', list services with 'ps', follow combined logs, and read the errors Compose throws on bad indentation. You will also see how the directory name becomes a stack prefix so you can run the same stack from multiple folders, then clean up with 'docker compose down'. After this you can drive a Compose stack through its full lifecycle.
Refactor a v1 Compose file into the modern v3 (3.8) spec. You will nest everything under a services block, add a version, define a bridge network, and use 'depends_on' to control startup order so the database comes up before the app. After this you can check Compose-to-Docker-Engine version compatibility, drop the now-unneeded links, and read any real-world v3 Compose file with networks and volumes.
Understand how service discovery actually works in Docker Compose v3 without any --link. You will exec into a running container, ping 'db' and 'app' by service name, and see them resolve to container IPs through Docker's built-in DNS at 127.0.0.11. After this you know why you keep a stable hostname in your connection string and let Compose resolve the changing IP for you, so the same stack runs anywhere unchanged.
See the clear split between a Dockerfile (build-time, makes the image) and Compose (launch-time, runs the stack), then wire them together. You will add a 'build' block with context and dockerfile to your Compose file, tag the image, and let 'docker compose build' rebuild only the service that changed. After this you can have Compose build your app image and pull off-the-shelf images in one flow, and know where to reach for build args, caches, and labels.
Use Docker Compose to push your app to a dev or CI environment with docker compose build followed by docker compose up -d. You'll see the two properties that make container deployments reliable: idempotence, where Compose skips work when nothing changed and redeploys only when the image changes, and immutability, where it throws away the old container and recreates a fresh one. After this you can wire Compose into a tool like Jenkins to deploy to a shared dev environment automatically.
Here is your hands-on project: containerize the Docker example voting app, a polyglot microservices stack with a Python vote UI, a Node.js result app, a Java worker, plus Redis and Postgres. You'll fork the repo, build Docker images for the three custom services, and write a docker-compose spec that brings the whole stack up. Redis and Postgres are off-the-shelf, so you just pull those. Try it yourself before watching the solution lectures that follow.
Before writing any Dockerfile, you'll build the Python vote app image the manual way so you understand every step. Fork and clone the repo, launch a python:2.7-alpine container, copy the source in with docker cp, then run pip install -r requirements.txt and start the Flask app under gunicorn on port 80. Once you see the voting page in your browser, you'll know exactly what needs to go into the Dockerfile next.
Turn the manual steps into a working Dockerfile for the Python vote app using FROM, WORKDIR, COPY, RUN, EXPOSE, and CMD. You'll build a tagged image with docker image build, inspect its layers with docker image history, and check how lean it is with the dive tool to read its efficiency score. By the end you can write a Dockerfile in a monorepo's service subdirectory and commit your first containerized microservice.
Do a manual build of the Java worker before writing its Dockerfile. Launch a maven-alpine container, copy the code into /code, and run mvn package to produce the fat jar in target. You'll see why the worker needs no port mapping (nothing connects to it) and why running java -jar just waits for Redis and DB until those services exist. This gives you the exact build steps to convert into a Dockerfile next.
Write the Dockerfile for the Java worker with FROM, WORKDIR, COPY, RUN mvn package, and a CMD that runs the jar, dropping EXPOSE since the worker takes no incoming traffic. You'll hit a real bug when copying files selectively and debug it by dropping into a shell, then check size and layers with dive. You'll also spot two things worth fixing: a 143 MB image carrying build tools and source, and a two-minute build, setting up the multi-stage and caching work that comes next.
Cut the worker's build time by caching Maven dependencies in a base image instead of downloading them on every build. You'll write a Dockerfile.base that copies pom.xml and runs mvn dependency:go-offline to pull dependencies and plugins, build it with docker image build -f, then swap it in as the FROM for the worker image. This drops the build from about two minutes to one, a real win when a CI job runs the build dozens of times a day.
Convert the worker into a multi-stage Dockerfile: a build stage that runs mvn package, then a slim JRE-on-alpine runtime stage that copies only the jar with COPY --from. This drops the image from about 150 MB to 90 MB and leaves a clean image holding just the app and its runtime, with no Maven or source code. You'll verify it runs, push it to Docker Hub with docker image push, and see how shared layers get mounted from the registry instead of re-uploaded.
Manually build the Node.js result app before writing its Dockerfile. Launch a node:8.9-alpine container with port 80 mapped, copy the code into /app, and run npm install to pull the packages listed in package.json. You'll work through real snags: moving node_modules to the right place, and setting the PORT environment variable to 80 so the app listens where you expect. Once the results page loads in the browser, you're ready to convert these steps into a Dockerfile.
Write the Dockerfile for the Node.js result app using node:8.9-alpine, splitting the setup into separate RUN instructions for the npm registry config and the dependency install. You'll add ENV PORT=80 so Node listens on the right port at runtime, then EXPOSE and CMD to launch it. After building, running, and pushing this image, all three microservices are containerized and you're set to write the docker-compose spec that launches the full stack.
Write a docker-compose.yaml that launches all five services of the voting app together: vote, result, worker, plus Redis and Postgres. You will set the compose version, define services with build context and Dockerfile, map ports only where needed, wire up depends_on, and put every service on a shared bridge network. By the end you will know why service names double as DNS hostnames for service discovery, and how correct YAML indentation keeps the spec valid.
Bring the whole microservices stack up with Docker Compose and validate it end to end. You will run docker compose config to check syntax, build the vote, worker, and result images, start everything with docker compose up -d, and watch depends_on sequence the startup. When Postgres fails to start, you will read docker compose logs, fix it by passing a password environment variable, and confirm votes flow from vote to Redis to worker to DB to result. You will also tear the stack down cleanly with docker compose down.
Master Production-Grade Container Platform Engineering [2025 Edition]
Be ready for enterprise container deployments with the most comprehensive Docker, Kubernetes & GitOps course on Udemy!
What Makes This Course Special:
- The ONLY course covering the complete container platform lifecycle - from local development to production deployment
- Real enterprise deployment patterns used at Fortune 500 companies
- Includes cutting-edge GitOps & Progressive Delivery practices
- Regular updates with latest features and best practices
- Taught by School of DevOps® - trusted by companies like Nasdaq, VW, and NetApp
Course Highlights:
- Modern Dev Workflows with Dev Containers
- Production-Grade Docker & Kubernetes
- GitOps with ArgoCD & Continuous Delivery
- Progressive Delivery (Blue/Green, Canary)
- AWS Cloud Integration & Best Practices
- Enterprise Security Patterns
- Complete CI/CD Pipeline Setup
- Production Monitoring & Analysis
What You'll Learn:
- Build secure, scalable container environments
- Deploy applications using enterprise patterns
- Implement automated GitOps workflows
- Master blue/green and canary deployments
- Set up complete CI/CD pipelines
- Monitor and analyze deployments
- Optimize for production workloads
Perfect For:
- DevOps Engineers & SREs
- Cloud Engineers
- Software Developers
- Platform Engineers
- IT Professionals
- Anyone wanting to master container platforms
Course Features:
- 30+ hours of practical content
- 200+ hands-on lectures
- Real-world projects
- Production-ready examples
- Comprehensive labs
- Regular updates
- Active instructor support
2025 Updates:
- Dev Containers workflows
- Latest Docker & Kubernetes features
- Enhanced AWS integration
- New GitOps patterns
- Advanced monitoring setup
- Security best practices
- Enterprise deployment strategies
Prerequisites:
- Basic understanding of terminal/command line
- No prior Docker or Kubernetes experience needed
- Basic web development knowledge helpful but not required
Join 10,000+ successful students who are already using these skills in production environments. Start your container platform journey today!
30-Day Money-Back Guarantee
Full Lifetime Access
Certificate of Completion
School of DevOps® - Trusted by Fortune 500 companies for container platform training