Skip to main content
Software Engineering for Machine Learning

Docker and Kubernetes for ML Deployment

Published: 2026-08-01
Level: postgraduate
Audience: Postgraduate students in Machine Learning

Prerequisite Knowledge

This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.

Previously Covered in This Subject

  • Containerization with Docker — covered in Lecture 12
  • Git and Docker overview — covered in Lecture 11
  • Deploying a trained model as part of the ML pipeline — covered in Lecture 1–2
  • Dependency management and packaging — covered in Lecture 12

# Docker and Kubernetes for ML Deployment

13.1 Software Deployment Evolution

Hook: A machine-learning model that never leaves your laptop has delivered zero value. Deployment — making an application available for use in a production environment — is the step that turns a trained model into a service that users can actually call. And how we deploy has changed dramatically across three distinct eras: physical servers, virtual machines, and containers.

Software deployment is the process of making an application available for use in a production environment. Unless a machine-learning model is deployed, end users receive no benefit from it. Across the industry today — for both traditional software engineering applications and machine-learning systems — the de facto standard for deployment relies on two technologies: Docker for containerization and Kubernetes for orchestration. To understand why these tools exist, it helps to see the problems that each earlier era left unsolved.

13.1.1 Traditional Deployment

In the 1970s and 1980s, organisations invested in physical servers. The model was straightforward: buy hardware, install an operating system (Windows, Linux, or another OS), write the application, and host it directly on that physical machine. Every application ran on its own dedicated server.

The one-application-per-server rule carried a visible cost. A typical web or database server spends most of its time idle — CPU utilisation of 5–15% was common — because workloads rarely sit at their peak. Yet the hardware was bought for the peak: you paid for a machine sized for the worst moment, and it sat mostly unused. Scaling meant buying another physical box, which took weeks (ordering, shipping, racking, installing an OS) and usually meant provisioning for future peak demand rather than current needs.

The era's advantage was simplicity and predictability: one server, one OS, one application, and a very clear mental model of where everything runs. Its disadvantages were cost, wasted hardware, slow scaling, and poor resource utilisation.

13.1.2 Virtualized Deployment

When cloud computing became popular in the early 2000s, virtual machines (VMs) changed the equation. A hypervisor — a software layer installed on top of the host operating system — lets you create multiple isolated virtual machines on a single physical system. Each VM gets its own guest operating system, which can differ from the host. For example, a Windows 11 host can run Ubuntu and Linux guest VMs simultaneously through a hypervisor such as VirtualBox, VMware, or Hyper-V.

Think of the hypervisor as a property manager for a building. The building (the physical server) is one piece of hardware. The property manager divides it into flats (VMs). Each flat has its own locks, plumbing, and electricity — its own guest OS and applications — so tenants cannot walk into each other's flats even though they share one building. If one tenant crashes their application, installs software, or reboots their OS, the other tenants are not disturbed.

In the cloud world, virtual machines are a core service. On AWS they are called EC2 instances. When launching an EC2 instance, the system asks you to choose an Amazon Machine Image (AMI) — essentially the guest operating system you want. On top of that guest OS, you install your runtime and your application.

The key advantage: one physical machine hosts many VMs, so hardware is better utilised. The key disadvantage: every VM carries a full guest operating system, which is heavy on resources. Each guest OS needs its own CPU, RAM, disk, and licences — a Linux guest can consume 500 MB to 1 GB of RAM before your application even starts. Spin up fifty VMs and you are running fifty full operating systems, most of them doing nothing.

13.1.3 Container-Based Deployment

Containers eliminate the guest operating system entirely. Whatever the host OS is, that is what the container uses. You install a container engine — the most popular being Docker — on the host OS, and then you run your applications inside containers on top of that engine. No guest OS layer sits between the engine and the application. This makes containers significantly lighter than VMs.

A container is not a new machine; it is an isolated process (or set of processes) that shares the host OS kernel. Because there is no guest OS to boot, containers start in seconds rather than minutes, and dozens can run on hardware that would strain under a handful of VMs. You get the isolation benefit that motivated VMs — separated applications with their own files and libraries — without paying for a full OS per application.

Real-world: On AWS, the VM counterpart is EC2; the container counterpart is AWS Lambda. When you create a Lambda function, the system asks for your runtime (Python, Java, .NET, Ruby, etc.) but does not ask for an operating system — it takes a default Linux image automatically. Behind the scenes, Lambda runs on containers.

13.1.4 VMs versus Containers — Summary

Aspect Virtual Machine Container
Guest OS Required (can differ from host) Not needed; uses host OS
Software layer Hypervisor Container engine (Docker)
Resource weight Heavy (full OS per VM) Light (shares host OS kernel)
Startup time Minutes Seconds
Isolation Strong (hardware-level) Process-level
Portability Tied to guest OS choice Runs on any system with the container engine

Visual: Picture a vertical stack for each option. The VM stack runs: hardware → hypervisor → guest OS → application. The container stack runs: hardware → host OS → container engine → application. The container stack has one fewer layer — and that missing layer is exactly the weight, boot time, and cost that containers save. A VM is a "computer inside a computer"; a container is a "process with its own isolated view of the world."

Scope: This comparison assumes a single host machine and a standard server workload. In practice the two technologies coexist everywhere: AWS Lambda (containers) and EC2 (VMs) run side by side, and most production Kubernetes clusters themselves run on VMs in the cloud. The "either/or" framing is a teaching model — the real question in industry is which layer you manage, not which technology wins.

Visual takeaway: The single landmark on both stacks is the layer between hardware and application. Where the VM inserts a hypervisor plus a guest OS, the container inserts only an engine. One-sentence takeaway: containers remove the duplicated operating system, and everything else follows — speed, weight, portability.

Pitfalls:

  • Treating containers as "lightweight VMs." Containers do not provide hardware-level isolation; they share the host kernel, so a compromised container is a smaller blast radius than a VM, but not zero.
  • Assuming "no guest OS" means "no OS at all." A container still needs a kernel, so a Linux-built image does not run natively on a Windows host without a Linux virtual machine underneath (Docker Desktop does this automatically).
  • Judging the eras only by their disadvantages. VMs are still everywhere because hardware-level isolation and full control of the operating system are genuinely required in many regulated and security-sensitive settings.

Q: Hypervisor versus Docker — are they the same or different? A: Both are software applications, but they serve very different purposes. A hypervisor enables virtual machines by providing a virtualisation layer between hardware and guest operating systems. Docker is a container engine that runs applications directly on the host OS without needing a guest operating system. They are different tools for different levels of isolation — the hypervisor isolates whole machines, the container engine isolates processes.

Recap + Bridge: Deployment moved from physical servers (simple but wasteful) to VMs (efficient but heavy) to containers (light, fast, portable). Containers fix the two problems that matter most when shipping software — weight and environment differences — which is exactly the problem 13.2 attacks: what do you put inside the container, and why does environment parity matter? Exam note: The three eras of deployment (physical → VM → container) and the differences and advantages of each are a frequent exam topic. The comparison table above is the compact revision form.

Real-world: The eras describe layers you still choose between today. Engineers pick EC2 (VMs) when they need full OS control, Lambda (containers under the hood) when they want zero server management, and EKS or ECS (containers at scale) when they need orchestration. The container era's core insight — package everything, ship once, run anywhere — is the theme of the rest of this lecture.

13.2 Containerization Fundamentals

Hook: "It works on my machine." Every developer has heard this — and it is almost never the developer's machine that is wrong. The production environment differs from the development environment, and those differences cause bugs that are nearly impossible to reproduce locally. Containers exist to make this phrase obsolete.

The word "container" in software comes from manufacturing and logistics. A physical shipping container is a standard-sized metal box. You can put anything inside it — vehicles, furniture, refrigerators — and then load it onto a truck or ship. The container gets sealed and shipped from point A to point B. What is inside does not matter; the container format is universal. A software container works the same way: you bundle everything your application needs into a single package and ship it between environments.

13.2.1 Environment Parity Problem

A common pain point in software engineering is the inability to replicate a production issue in the development environment. A critical bug appears in production but when the developer tries to reproduce it locally, the issue does not manifest. One major reason is environment discrepancies — different library versions, OS patches, configuration settings, or runtime versions. Containers solve this by packaging the entire environment so that code, binaries, runtime, libraries, and configuration are identical across all environments.

Intuition — the singer analogy: A singer who performs at different venues never knows what the sound system will be like: one hall has a bad microphone, another has no monitors, a third has a noisy amplifier. The singer who carries their own mic and speakers avoids all venue vagaries — the same sound everywhere. A container is the singer's own equipment: it carries the runtime, the libraries, and the configuration, so the application performs identically in development, staging, and production.

Intuition — the shipping-container analogy: Before standard shipping containers, cargo was loaded piece by piece, and every port handled it differently. Standard containers made shipping universal: seal it once, and any truck, ship, or crane in the world handles it. A software container is the same idea: bundle the code, runtime, libraries, and configuration into one sealed package, and any machine with a container engine can handle it.

The two analogies reinforce each other. The singer analogy explains why containers exist (environment parity — eliminating venue vagaries). The shipping-container analogy explains how they achieve it (standard package format that any host can carry). Both break down only at the edge: a physical container is fully sealed and tamper-proof, while a software container still depends on the host OS kernel — you cannot ship a Windows container to a Linux-only host.

Q: I cannot replicate a production issue in my development environment. What could be wrong? A: The environment is likely not identical. Whatever environment you have in production has some discrepancies with your development setup — a different library version, a patched OS, different configuration, a different runtime. Containers solve this by making the code, binaries, runtime, and configuration identical across all environments. Instead of debugging "the difference between environments," you debug the one fixed, identical environment you shipped.

13.2.2 What Goes Inside a Container

For a software engineering application, a container bundles:

  • Source code — the application itself
  • Runtime — Python 3.10/3.11, Java JVM, .NET CLR
  • System libraries — JAR files, DLLs, pip packages
  • Configuration settings — database connection strings, server ports

For a machine-learning application, the container additionally includes:

  • The trained model — a pickle or ONNX file
  • Inference code — a FastAPI or Flask server
  • ML dependencies — scikit-learn, NumPy, pandas, joblib
  • Configuration files — YAML or JSON with model parameters, feature names, thresholds

Everything and anything needed for the software to run is inside the container.

Pitfalls:

  • Leaving configuration outside the container. A container that must be told the database URL at runtime, but has no documented way to receive it, recreates the environment-parity problem you were solving.
  • Forgetting the model file. An ML container that copies code but not the trained model will start, accept requests, and fail at the first prediction.
  • Shipping secrets inside the image. Connection strings with passwords baked into the container leak to anyone who pulls the image; credentials belong in runtime-injected configuration, not the image.

Recap + Bridge: A container is a standard package that carries the full environment — code, runtime, libraries, configuration, and (for ML) the trained model — so that every environment is identical. With the why established, 13.3 introduces the engine that builds and runs these packages: Docker, its three-tier architecture, and where images come from. Exam note: The environment-parity motivation (why containers exist) and the container's contents (what goes inside, especially the ML-specific additions) are examinable; the singer and shipping-container analogies are the professor's preferred mental pictures.

Real-world: Every major cloud provider builds on this idea: AWS Lambda, Azure Functions, and Google Cloud Run all accept a container and run it for you, and Kubernetes (13.11) orchestrates thousands of containers in production. The container is the unit of shipping for the entire industry — for ML systems, it is the unit that carries a model into production intact, which the reference book (Deploying a Model) describes as the standard way to package a model inference service for deployment.

13.3 Docker

Hook: One image, any machine. A Docker image built once can run on any machine that has Docker installed — a laptop running Windows, a Linux server in a data centre, or a cloud VM — without any changes. How does one technology achieve that, and where does the image actually come from when you run a command?

Docker is the most popular container engine. It is a piece of software you install on any operating system — Windows, Mac, or Linux. Once Docker is installed on a machine, that machine becomes a Docker host. On that host, you can pull images from a registry, build your own images, and run containers.

The key property: a Docker image can run on any machine that has Docker installed, regardless of the underlying operating system. As long as Docker is there, your containerised application will run.

13.3.1 Docker Architecture

Docker uses a three-tier architecture: Client, Docker Host (Docker Daemon), and Registry.

Client. This is where you type commands. The Docker client runs in your terminal or command prompt. Commands like docker build, docker pull, and docker run are all client commands. The client sends these commands to the Docker daemon.

Docker Host (Docker Daemon). Any system where Docker is installed becomes a Docker host. The Docker daemon runs on this host and manages two core objects: images and containers. When the client sends a command, the daemon executes it — building images, starting containers, stopping containers, and so on. The client is only a messenger; all the real work happens in the daemon.

Registry. The registry stores Docker images. Docker Hub is the default public registry, analogous to GitHub for source code. Just as GitHub lets you git push and git pull code, Docker Hub lets you docker push and docker pull images. Other registries exist: AWS has Elastic Container Registry (ECR), Azure has Azure Container Registry, and Google Cloud has its own container registry.

13.3.2 Command Flow Through the Architecture

When you run docker pull ubuntu from the client, the command flows: client → Docker daemon → checks local images → if not found, connects to Docker Hub → downloads image → stores on Docker host. When you run docker run ubuntu, the daemon creates a container from the image and starts it. If the image is not local, it pulls first.

Worked example — docker pull ubuntu step by step:

  1. You type docker pull ubuntu in the terminal. The client (a CLI program) is the only component you touch.
  2. The client forwards the request to the Docker daemon on the same machine over a local socket.
  3. The daemon checks its local image store: is there an image named ubuntu?
  4. Not found → the daemon contacts Docker Hub (the registry) and downloads the ubuntu image (tag defaults to latest).
  5. The daemon stores the image on the Docker host and reports the digest and download size back through the client to your terminal.

Sense-check: the client never touches the network to Hub; the daemon does. This is why the three-tier division matters — the client is a dumb messenger, and all image management lives in the daemon. Docker Desktop's UI is a different client talking to the same daemon.

Exam note: Understanding the flow from client → daemon → registry is fundamental. Expect conceptual questions on this three-tier architecture — including which component does what during docker pull, docker build, and docker run.

13.3.3 System Images versus Custom Images

Docker Hub contains two kinds of images. System images (official images) are pre-built and freely available for virtually any software: operating systems (Ubuntu, Alpine), databases (PostgreSQL, MongoDB, MySQL), web servers (Nginx, Apache), programming language runtimes (Python, Java, Node.js), and ML frameworks. Custom images are images you build yourself on top of system images. You push them to Docker Hub under your account (e.g., 6945/iris-model:latest). Anyone can pull and run them.

A custom image internally uses one or more system images as its foundation. When you write FROM python:3.11 in a Dockerfile, you are pulling the official Python 3.11 system image and layering your application on top.

Intuition — Docker Hub is to images what GitHub is to source code. docker pull behaves like git pull (download a project to work with), docker push behaves like git push (upload your work so others can use it), and repositories behave the same way — public by default, private on request. If you have used GitHub, you already understand the registry's mental model.

Real-world: Docker Hub is free for public images, just like GitHub. You log in through GitHub or Gmail credentials. Account IDs are numeric (e.g., 6945), and images are pushed under <account-id>/<image-name>:<tag>.

Q: When building a custom image, does it internally copy and combine multiple system images? A: Yes. You pull the operating system, Python runtime, MongoDB, or whatever system images you need, and layer them into your custom image. They do not conflict with each other — it is like installing an OS first, then Python, then libraries, all in sequence inside a black box. Each layer adds on top of the previous one, and Docker tracks the layers so they can be reused and cached.

Pitfalls:

  • Forgetting the tag. docker pull ubuntu silently means ubuntu:latest; pinning a version (ubuntu:22.04) makes builds reproducible, which matters when the next lecture's pipeline rebuilds the same image.
  • Confusing the client and the daemon. When something fails, remember the error usually comes from the daemon (e.g., "daemon not running") or the registry (e.g., "pull access denied"), not from your terminal.
  • Believing an image is fully self-contained on the host OS level. The image packages the application layer stack; the kernel is still the host's, as established in 13.1.

Recap + Bridge: Docker is a three-tier system: client (your commands), daemon (does the work, manages images and containers), and registry (the image store, Docker Hub by default). System images come pre-built; custom images stack on top of them. With the engine understood, 13.4 answers the most frequently tested question in the lecture: what exactly is an image, what is a container, and where does data survive? Exam note: The three-tier architecture (client → daemon → registry) and what happens during docker pull, docker build, and docker run are explicit exam topics.

13.4 Docker Objects: Image, Container, Volume

Hook: What is the difference between a blueprint and a building? Between a class and an object? Between a recipe and a cooked dish? Docker's three core objects — image, container, volume — are built on exactly these distinctions, and the image-versus-container one is among the most frequently tested ideas in the course.

Docker has three core objects that every practitioner must understand.

13.4.1 Image

An image is a static, read-only blueprint that contains everything needed to create a running container: the base OS or runtime, application code, libraries, configuration, and instructions for what to execute. Think of an image as a class in object-oriented programming — it defines the structure and behaviour but is not itself running. When you run docker build, Docker executes the Dockerfile instructions layer by layer and produces an image. Docker caches these layers for performance.

13.4.2 Container

A container is a running instance of an image. When you execute docker run <image-name>, Docker creates a container from the image and starts it. The relationship between image and container is the same as between a class and an object in OOP: one image can produce many containers, each running independently.

Intuition — class-to-object: A Python class defines attributes and methods but occupies no independent memory until instantiated. Objects created from the same class are separate: changing one object does not change the class or the other objects. The image is the class (definition, static, shared); each docker run instantiates a separate object (a container with its own filesystem layer and process). Build once, run many.

When you run docker run -it ubuntu:latest (the -it flag means interactive terminal), Docker creates a container and drops you into a shell. You can run Linux commands, install software, and create files. However, any changes you make inside a container are ephemeral — if you exit and restart, everything is gone.

Q: When we create a Docker image, do we specifically mention which OS to use? A: You can start with an OS image like Ubuntu, but it is not a prerequisite. You can directly start with a runtime image like FROM python:3.11. Docker will use the host OS behind the scenes.

Q: Are the applications inside a Docker container dependent on the OS, like in a virtual machine? A: No. Applications inside a container are independent of the OS. The container shares the host OS kernel, and the applications run within the container's isolated environment. This is fundamentally different from a VM, where each application runs on its own guest OS.

13.4.3 Volume

A volume provides persistent storage for containers. Since container filesystems are ephemeral — everything inside disappears when the container stops — volumes let you store data outside the container lifecycle. When a container restarts, the volume is mounted back, and the data is available again.

Think of the container's writable layer as a whiteboard that gets wiped whenever the container stops, and the volume as a notebook that sits beside the whiteboard: you copy important things into the notebook, and when the whiteboard is wiped and replaced (a new container from the same image), the notebook is simply placed beside the new board.

Real-world: In the BITS virtual lab environment, each student gets a containerised workspace. When a student saves a Python file and exits, the file persists because the data is stored in an AWS EBS volume. When the student logs in again, the volume is mounted, and the saved file is still there. This is volume persistence in production: container is ephemeral, volume is durable.

Pitfalls:

  • Writing important data only to the container filesystem. When the container is removed, the data goes with it — no warnings, no recycle bin.
  • Assuming containers from the same image share state. They are independent instances; state sharing requires an explicit volume (or a database) attached to all of them.
  • Confusing the image's read-only layers with the container's writable layer. docker commit exists, but the idiomatic way to change an image is to edit the Dockerfile (13.7) and rebuild, not to modify a running container.

Recap + Bridge: Image = static blueprint (the class); container = running instance (the object); volume = durable storage outside the container's short life. One image produces many containers, and without a volume their data vanishes on exit. With these three objects defined, 13.5 runs them through the lecture's live Ubuntu demonstration — pull, run, install, exit, and watch the data disappear. Exam note: Image-versus-container (static blueprint versus running instance) is frequently tested, as is the rule that volumes provide persistence while container data is ephemeral.

13.5 Docker Commands and Workflow

Hook: Watch a full live demonstration in text form: pull Ubuntu, install a text editor inside the container, write a file, exit — and then discover everything you did is gone. This one demo explains why Docker works the way it does and why volumes exist.

13.5.1 Essential Commands

The three most important commands map to the three-tier architecture:

  • docker pull <image>:<tag> — download an image from a registry
  • docker build -t <name>:<tag> . — build a custom image from a Dockerfile
  • docker run [options] <image>:<tag> — create and start a container from an image

Additional commands: docker login (authenticate with registry), docker tag (create tag for pushing), docker push (upload image), docker ps (list running containers), docker stop (stop container), docker rm (remove container).

13.5.2 Demonstrated Workflow: Pulling and Running Ubuntu

The following sequence was demonstrated live:

Worked example — the Ubuntu demo, step by step:

  1. docker pull ubuntu:latest — pulls the official Ubuntu image from Docker Hub. The image was not local, so the daemon connected to Hub and downloaded it (as traced in 13.3.2).
  2. docker run -it ubuntu:latest — the -it flag keeps the container running and drops you into a bash shell. Running ls shows the standard root directory (/bin, /etc, /home, /root, /usr, ...); pwd shows /root. You are inside a fresh Ubuntu filesystem, not your laptop's.
  3. Inside the container: apt update updates the package list, apt install vim installs the Vim editor, vi one.txt creates and edits a file. This works — you are in a real Ubuntu environment with root privileges.
  4. exit — leaves the container. The container stops.
  5. If you restart the container, vim is no longer installed and one.txt does not exist. Everything was ephemeral. To persist data, you would need a volume (13.4.3).

Sense-check: the container started from a clean image, so every change lived in the container's writable layer. Stopping the container discarded that layer — exactly the class-versus-object behaviour of 13.4: instantiating a fresh container from the same image gives you the same fresh Ubuntu, with none of the previous instance's modifications.

Why this matters — professor's warning: Container data is ephemeral. Installing software and creating files inside a container disappears on exit unless volumes are used. This is the single most common misunderstanding after the lecture: students assume a container remembers what they did. It does not — the image is the memory, and the image never changed during the demo.

Q: How do we run various applications inside Docker — how do we place Python model files and run them? A: The Dockerfile handles this: copy the model file, copy the inference code, install dependencies, set the CMD to run the server. This will be demonstrated next with the Iris ML application (13.9). The Ubuntu demo used manual commands; the Dockerfile (13.7) automates the same pattern so it is reproducible.

Pitfalls:

  • Expecting a stopped container to still be running its services. docker ps lists running containers only; stopped containers are invisible to it unless you use docker ps -a.
  • Running docker rm on a container whose data you wanted. Removing the container permanently destroys its writable layer — that is what volumes are for.
  • Confusing docker run (creates a new container) with restarting an existing one (docker start <id>). Each docker run from the same image starts fresh.

Recap + Bridge: pull gets an image, run instantiates it, changes inside are ephemeral, volumes persist. The demo showed the manual workflow; 13.6 adds the one flag that makes containerised web applications usable (port mapping), and 13.7 replaces manual commands with a reproducible Dockerfile. Exam note: Expect to reproduce the flow of the three core commands (pull, build, run) and to state what happens to container data after exit.

13.6 Port Mapping

Hook: Your application is running inside the container — why can't you open it in your browser? Because the container's network is its own world. Port mapping is the bridge between the container's world and yours.

When you run a web application inside a container (say, on port 8000), you need to map the container's port to a port on your local machine. Without this mapping, the application is not accessible from your browser.

The -p flag handles this: docker run -p 8000:8000 <image>:<tag>. The format is -p <host-port>:<container-port>. If another application is already using port 8000 on your host, you will get a conflict — use a different host port (e.g., -p 9000:8000).

Common port conventions: web applications use port 80, 8080, or 8000; PostgreSQL uses port 5432; MySQL uses port 3306. Any unused port can be used.

Intuition: Think of an apartment building's intercom system. Each flat (container) has its own doorbell channel (container port, e.g., 8000). To reach a flat from outside, the visitor uses a shared directory at the building entrance (host port) that the receptionist rewires to the correct flat's doorbell. The syntax -p 9000:8000 says: "outside callers dial 9000; forward those to the flat's 8000." Without the forwarding entry, outside callers cannot reach the flat at all.

Worked example — exposing a containerised REST app:

  1. The container runs a REST API listening on port 8000 inside the container's own network namespace.
  2. Without -p: the API runs, but http://localhost:8000 on your machine hits nothing — your host has no service listening on 8000, and the container's 8000 is unreachable from outside.
  3. docker run -p 8000:8000 <image>:<tag> — host port 8000 forwards to container port 8000.
  4. Now http://localhost:8000 reaches the API, and the browser receives responses.
  5. Conflict case: if another application already uses port 8000 on your host, Docker refuses (the host port is taken). Fix: docker run -p 9000:8000 <image>:<tag>, then browse to http://localhost:9000.

Sense-check: the mapping is <host-port>:<container-port>. You only need the host side to be free; the container side stays whatever the app listens on, which is why -p 9000:8000 works when -p 8000:8000 fails.

13.6.1 Port Forwarding versus Kubernetes Services

Port mapping as demonstrated is called port forwarding — from the container port to the local port. In a Kubernetes deployment, port mapping is typically handled through a service definition (a YAML file) rather than direct port forwarding. This distinction will be covered in the next lecture.

Q: We are mapping port 8000 from the container to our local machine. Is that correct? A: Yes, exactly. This is called port forwarding. In Kubernetes, this is handled through services instead of direct port forwarding — the service is the cluster-level abstraction that decides which pod receives the traffic.

Q: If we have the same port numbers, will we get a conflict? A: Yes. If another application uses port 8000, you will get a conflict. Use a different host port (e.g., -p 9000:8000). As long as the port is unused, you can use it. The container's internal port does not need to change — only the host-side mapping does.

Pitfalls:

  • Reading -p backwards. The order is always <host>:<container>. -p 8000:9000 exposes a different mapping than -p 9000:8000, and only the first port is what you type into the browser.
  • Forgetting 0.0.0.0 inside the app. A server that listens only on 127.0.0.1 inside the container may reject external connections; the lecture's CMD uses --host 0.0.0.0 so the server accepts traffic arriving through the port mapping.
  • Mapping one container port to two host ports, or two containers to the same host port — the host side must be unique; the container side need not be.

Recap + Bridge: -p <host-port>:<container-port> forwards traffic from your machine into the container; without it, containerised web apps are unreachable; host-side conflicts are fixed by choosing another host port. Kubernetes replaces this ad-hoc forwarding with declarative Service objects — the preview comes in 13.12. Exam note: Port mapping with -p host:container is necessary to access containerised applications externally — expect the syntax and the conflict-handling rule.

13.7 Dockerfile and Building Custom Images

Hook: The Ubuntu demo in 13.5 was all manual: type commands, install software, hope you remember the order. A Dockerfile turns that fragile session into a repeatable recipe that anyone can rebuild — and it is the single file that unlocks every image you will ever build.

13.7.1 What is a Dockerfile

A Dockerfile is a plain text file containing step-by-step instructions for building a Docker image. It is not a script for your local machine — every instruction executes inside the container being built. The Dockerfile tells Docker: start with this base image, copy these files, install these dependencies, set this working directory, and run this command when the container starts. When you run docker build, Docker reads the Dockerfile from top to bottom, executes each instruction as a layer, and produces an image.

Intuition — the recipe: A Dockerfile is a cooking recipe. The recipe never cooks anything by itself; it describes the dish. A person following it in their own kitchen (the daemon) produces the same dish (image) every time, provided the ingredients (base image, files, dependencies) are the same. Crucially, the recipe is not executed on your laptop's kitchen counter — it is executed inside a fresh kitchen (the container being built), which is why COPY and RUN see the container's filesystem, not yours.

13.7.2 Dockerfile for a Python REST Application

A Python FastAPI/REST application typically has this Dockerfile structure:

FROM python:3.9
WORKDIR /code
COPY requirements.txt /code/requirements.txt
RUN pip install -r /code/requirements.txt
COPY . /code
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Line by line: FROM python:3.9 sets the base image (you do not need to specify an OS; the Python image includes one). WORKDIR /code creates and sets the working directory inside the container. COPY requirements.txt copies the requirements file. RUN pip install installs dependencies inside the container. COPY . /code copies all source code. EXPOSE 8000 declares the port. CMD sets the startup command (Uvicorn server).

The order of instructions matters. The logical sequence is: set base → set working directory → copy and install dependencies → copy source code → declare port → set startup command. Dependencies must be installed before copying source code so that Docker can cache the dependency layer — if only source code changes, dependencies are not re-installed.

Q: Why do we copy requirements.txt before the source code? A: To install dependencies first. Docker caches each layer. By copying requirements.txt first and installing dependencies, Docker caches that layer. If you later change only source code, Docker reuses the cached dependency layer — this makes builds faster. Copying source code first would invalidate the cache on every code change and force a full re-install of all dependencies.

Worked example — how the cache makes builds faster:

Build 1: the daemon executes all seven instructions — pulls python:3.9, creates /code, copies requirements.txt, runs pip install (the longest step), copies the source, declares the port, sets the command. Total time: several minutes, most of it in pip install.

Now you edit one line of app.py and rebuild.

Build 2: the daemon compares instructions one by one. FROM, WORKDIR, and COPY requirements.txt are unchanged, so their layers are reused from cache. RUN pip install has the same inputs (same requirements.txt), so the dependency layer is reused — no re-downloading of packages. Only COPY . /code runs again, because the source changed. Build time drops to seconds.

Sense-check: if you had placed COPY . /code before RUN pip install, any source edit would change the context of everything below it, and the dependency install would rerun every time — the exact slowness the ordering rule avoids.

13.7.3 Available Dockerfile Keywords

Dockerfiles support many keywords: FROM, WORKDIR, COPY, RUN, EXPOSE, CMD, ENV, ARG, ENTRYPOINT, ADD, VOLUME, USER, LABEL, HEALTHCHECK, SHELL, and others. The subset shown above covers the majority of real-world use cases.

13.7.4 Custom Image as Base

Instead of starting FROM python:3.11, you can start FROM <account>/<custom-image>:<tag>. This lets you layer one custom image on top of another. If your organisation maintains a base image with pre-installed security tools and monitoring agents, every team starts FROM that base image and adds their application-specific layers on top.

Pitfalls:

  • Ordering the copy so that cache is broken on every change (see the worked example above) — the professor's explicit instruction-order rule.
  • Running CMD with the wrong JSON syntax. CMD ["uvicorn", ...] is the exec form; CMD uvicorn ... is the shell form. Both exist, but mixing them up changes how signals and environment are handled.
  • Forgetting that COPY only copies from the build context. A file outside the directory where docker build . runs is not available inside the build unless the context includes it.

Recap + Bridge: A Dockerfile is a recipe executed inside the container being built: base image, working directory, dependencies first (for caching), source, port, command. Dockerfiles are also how the professor now runs training sessions — no more installing software on thirty machines; students just pull an image and run. With the recipe ready, 13.8 runs the full cycle: build, run, tag, push. Exam note: Dockerfile instruction order matters: base image → working directory → copy dependencies → install dependencies → copy source → expose port → set command — and the caching rationale behind it.

Real-world — professor's experience: Dockerising applications transformed training sessions. Previously, every participant's machine needed the software installed individually — a day lost to environment setup. Now the professor hands out a Docker image; participants pull and run it, and everyone starts from the identical environment in minutes. The same pattern is what makes ML demos reproducible in industry: the image carries the model, the dependencies, and the server, so "it works on my machine" disappears from the vocabulary.

13.8 Build, Run, Push — End-to-End Workflow

Hook: The three-tier architecture of 13.3 finally runs end to end: build the image locally, run it, and push it to a registry where the whole world can pull it. This is the exact workflow every CI/CD pipeline in industry automates.

13.8.1 Building the REST Application Image

After creating the Dockerfile, the build command is:

docker build -t rest-app:latest .

-t rest-app:latest names the image; the . tells Docker to look for the Dockerfile in the current directory. Docker executes the Dockerfile layer by layer: FROM (pull Python image, cached if present), WORKDIR (create directory), COPY requirements, RUN pip install (longest step on first build), COPY source, EXPOSE port, CMD startup. After the build, the image appears in Docker Desktop under "Images." No container is created yet — the image is static.

13.8.2 Running the Container

docker run -p 8000:8000 rest-app:latest

This creates a container from the image, maps port 8000, and starts the Uvicorn server. The application is now accessible at http://localhost:8000.

13.8.3 Pushing to Docker Hub

To share the image or deploy it to a server:

  1. docker login — authenticate with Docker Hub
  2. docker tag rest-app:latest 6945/rest-app:latest — create a tag with your Docker Hub account ID
  3. docker push 6945/rest-app:latest — upload the image to Docker Hub

After pushing, anyone can docker pull 6945/rest-app:latest and run the application.

Docker Hub visibility: Like GitHub, Docker Hub supports public and private repositories. Cloud registries (AWS ECR, Azure Container Registry) similarly support public and private images.

Worked example — full lifecycle trace:

  1. docker build -t rest-app:latest . — the daemon executes the Dockerfile instructions and produces the image. The image is now listed under "Images" in Docker Desktop. Nothing is running.
  2. docker run -p 8000:8000 rest-app:latest — a container is instantiated from the image, port 8000 is mapped, and Uvicorn starts serving. http://localhost:8000 responds. The container's process list shows uvicorn; the image is untouched.
  3. docker login — the client authenticates with Docker Hub using your account (GitHub or Gmail credentials).
  4. docker tag rest-app:latest 6945/rest-app:latest — a new tag 6945/rest-app:latest points at the same image. Tagging never copies data; it adds a name.
  5. docker push 6945/rest-app:latest — the daemon uploads the image layers to Docker Hub under your numeric account ID.
  6. Anywhere in the world: docker pull 6945/rest-app:latest retrieves the identical image.

Sense-check: build created a static artifact, run instantiated it, tag renamed it, push published it — each command mapped to one tier of the architecture: build (daemon + local storage), run (daemon + local storage), push (daemon → registry).

Q: Can we make Docker Hub images accessible to specific users only instead of public? A: Yes, Docker Hub supports private repositories, just like GitHub. Cloud registries also support public and private images. You control visibility through repository settings.

Q: What web servers can we use besides Uvicorn? A: Nginx, Tomcat for Java, IIS for .NET, and many others. For Python applications, Uvicorn is used approximately 90% of the time.

Pitfalls:

  • Trying to docker push rest-app:latest without a tag that includes your account ID — Docker Hub rejects the upload because rest-app is not namespaced to your account.
  • Forgetting docker login before pushing — the daemon will refuse the upload with an authentication error.
  • Pushing with the tag latest and losing track of versions. The lecture's pipeline (13.10) tags images with versions (e.g., image:1.0) precisely so deployments can be rolled back.

Recap + Bridge: Build creates the static image, run instantiates it with port mapping, tag renames it for your account, and push publishes it to the registry — the full three-tier cycle. This manual cycle is exactly what the CI/CD pipeline of 13.10 automates, and it is the same cycle used for the ML application in 13.9. Exam note: The end-to-end sequence — build, run, tag, push — and the role of each command in the three-tier architecture are examinable.

13.9 Containerizing a Machine-Learning Application

Hook: Everything so far was a REST demo. Now the same pattern ships something real: a trained Iris classifier behind a FastAPI server, in a container, answering prediction requests from your browser. This is the moment the lecture connects models to users.

13.9.1 Application Structure

A typical ML application for containerisation contains: train.py (training script), model.pkl (trained model file), app.py (inference server with FastAPI), preprocess.py (feature preprocessing), postprocess.py (mapping predictions to labels), requirements.txt (Python dependencies), config.yaml (configuration), and Dockerfile.

Why this structure: The reference text (Deploying a Model) describes model inference as a simple side-effect-free function — features in, prediction out — wrapped in an HTTP service. The container makes that service portable: the model file is loaded once at startup, and every prediction request reuses it. Preprocessing and postprocessing are separate modules because the same feature encoding must be used in training and inference; packaging them together prevents the two from drifting apart.

13.9.2 Iris Classification Example

The demonstrated ML application uses the Iris dataset with a Random Forest classifier. The training script loads the Iris data, trains the model, and saves it as model.pkl. The inference server loads model.pkl at startup and exposes GET / (health check) and POST /predict (accepts four features: sepal length, sepal width, petal length, petal width). Post-processing maps prediction integers to class names: 0 → Setosa, 1 → Versicolor, 2 → Virginica.

13.9.3 Dockerfile for the ML Application

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN pip install -r /app/requirements.txt
COPY . /app
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

The structure is identical to the REST application. The requirements.txt contains fastapi, uvicorn, scikit-learn, numpy, joblib, pydantic. Build and run:

docker build -t iris-model:latest .
docker run -p 8000:8000 iris-model:latest

After the container starts, a POST request to http://localhost:8000/predict with the four Iris features returns the prediction (e.g., prediction: 0, label: Setosa).

Worked example — a prediction round trip:

  1. Build: docker build -t iris-model:latest . — the daemon runs the Dockerfile; pip install fetches scikit-learn, NumPy, joblib, FastAPI, and Uvicorn into the image. The model file is copied in with COPY . /app.
  2. Run: docker run -p 8000:8000 iris-model:latest — Uvicorn starts, and the app loads model.pkl once at startup.
  3. Health check: GET http://localhost:8000/ returns a 200 response confirming the server is alive.
  4. Predict: POST http://localhost:8000/predict with the JSON body {"sepal_length": 5.1, "sepal_width": 3.5, "petal_length": 1.4, "petal_width": 0.2}.
  5. The server preprocesses the four features, passes the vector to the Random Forest model, receives prediction integer 0, and the postprocessor maps it to the label.
  6. Response: prediction: 0, label: Setosa — matching the well-known first row of the Iris dataset, which is a Setosa.

Sense-check: the same features that produced a Setosa during training produce a Setosa in the container — identical code, model, and dependencies, which is exactly the environment parity of 13.2 working for a model.

Real-world: Any application built during this course — basic ML models, agentic AI applications, RAG applications — can be dockerised using the same pattern. Create a Dockerfile, set up the environment, give the instructions, and set the startup command. This is the standard way that model inference services are packaged for deployment in industry: model file, inference code, dependencies, and configuration sealed into one portable image.

13.10 CI/CD Pipeline with Docker and Kubernetes

Hook: Manual docker push is fine for a demo. In industry, no one runs those commands by hand — a pipeline does it automatically every time code changes. This is the production lifecycle that connects the developer's commit to a running pod in a Kubernetes cluster.

In industry, the end-to-end deployment lifecycle follows this pipeline:

  1. Source code — developers write code and commit to a Git repository.
  2. CI pipeline (GitHub Actions, Jenkins) — automated build, test, and Docker image creation. If tests pass, the image is pushed to a container registry (Docker Hub, AWS ECR, Azure Container Registry).
  3. Container registry — the built image is stored with a version tag (e.g., image:1.0).
  4. CD pipeline (Argo CD, Flux CD, Jenkins CD) — pulls the image and deploys to a Kubernetes cluster using a deployment manifest (YAML file specifying replicas, image version, resource limits).
  5. Kubernetes cluster — runs the containers. If the image is updated (e.g., image:1.0image:1.1), the CD pipeline rolling-updates the pods.

Intuition — the assembly line: Think of a car factory. Developers are the design team producing blueprints (code + Dockerfile). The CI pipeline is the automated assembly line that turns a blueprint revision into a finished, tested car (image) and parks it in the lot (registry). The CD pipeline is the dealer network that takes cars from the lot and puts them on the road (deploys to the cluster). No one runs down the assembly line by hand — the pipeline reacts automatically to each new blueprint.

13.10.1 Scaling in Production

Different microservices scale independently based on demand. Consider an OTT platform: the recommendation microservice may need only one replica when nobody is browsing, while the play microservice may need 1000 replicas when millions are streaming. Kubernetes handles this through its scaling mechanisms (Horizontal Pod Autoscaler, discussed in the next lecture).

Q: In an enterprise scenario, developers create Dockerfiles and ops deploys them. How does this work end-to-end? A: Developers write code and the Dockerfile, commit to Git. A CI pipeline (GitHub Actions, Jenkins) builds the Docker image and pushes it to a registry. The ops team uses a CD tool (Argo CD, Jenkins CD) to pull the image and deploy to Kubernetes using a YAML deployment manifest specifying replicas, image version, and resource limits.

Pitfalls:

  • Deploying an image that CI never built. The pipeline's value is that the deployed artifact is exactly the one that passed tests — hand-built images bypass that guarantee.
  • Versionless deployments. If every build is tagged latest, rolling back from image:1.1 to image:1.0 is impossible; version tags in the registry are what make CD meaningful.
  • Treating CI and CD as one thing. CI ends at the registry (build + test + push); CD begins at the cluster (pull + deploy). The tools differ: GitHub Actions or Jenkins for CI, Argo CD or Flux CD for CD.

Recap + Bridge: The production lifecycle is source → CI build → registry → CD deploy → Kubernetes cluster, with independent scaling per microservice. The pipeline's destination — the Kubernetes cluster — is the final piece, and 13.11 introduces it: what Kubernetes is, where it came from, and why Docker alone is not enough at scale. Exam note: The CI/CD pipeline flow (source code → CI build → Docker image → registry → CD deploy → Kubernetes pods) is a common exam topic.

13.11 Introduction to Kubernetes

Hook: Docker handles one machine. What runs your application when it must serve a million users on a thousand machines — and restart, scale, and update without downtime? That is the problem Kubernetes was built to solve.

13.11.1 Why Kubernetes

Docker alone is sufficient for running an application on a single machine. But for large-scale deployment — serving millions or billions of users across distributed infrastructure — you need container orchestration. Kubernetes (K8s) is that orchestration platform. Originally developed by Google Labs and donated to the Cloud Native Computing Foundation (CNCF) in 2015–2016, it was the first graduated project of CNCF.

Intuition — the orchestra conductor: Docker is the musician: each container plays one part perfectly on its own. Kubernetes is the conductor: it decides who plays when, who rests (scales down), who joins when the audience grows (scales up), and it steps in when a musician drops out (restarts a crashed pod). Docker alone has no conductor — every container just plays by itself on its own machine.

Real-world: Almost every major application today — including ChatGPT — runs on Kubernetes. It provides high availability (no downtime), scalability (handle traffic spikes), high performance, and disaster recovery (backup and restore).

13.11.2 CNCF Graduated Projects

CNCF has 38 graduated projects and 38 incubating projects, all open source. Notable graduated projects: Kubernetes (container orchestration), Argo CD (GitOps CD), Flux CD (alternative CD), Helm (Kubernetes package manager), Prometheus (monitoring), Envoy (service proxy), Istio (service mesh), Kubeflow (ML workflows), OpenTelemetry (observability), etcd (distributed key-value store), gRPC (RPC framework). Incubating projects include Chaos Mesh, Litmus, OpenFeature, Thanos, Volcano.

Real-world: Helm, Prometheus, Argo CD, and Istio are among the most widely adopted CNCF tools. Several appear again in this course: Argo CD and Flux CD in the CI/CD pipeline (13.10), Prometheus for monitoring in production, and etcd as the Kubernetes control plane's state store (13.12).

Pitfalls:

  • Conflating Docker and Kubernetes. Docker packages and runs containers; Kubernetes orchestrates them across machines. They are complementary layers, not competitors.
  • Assuming "graduated" means "owned by one company." CNCF projects are open source and vendor-neutral — which is why EKS, GKE, and AKS can all run the same Kubernetes.
  • Expecting Kubernetes to manage VMs. Kubernetes orchestrates containers; the machines (EC2 instances, laptops) run a container engine and are registered as nodes.

Recap + Bridge: Docker is single-machine; Kubernetes is the orchestrator that makes containers work at scale — born at Google Labs, now the CNCF's first graduated project. The ecosystem around it (Argo CD, Helm, Prometheus) is large but coherent. 13.12 opens the hood: how a cluster is organised, what the master and worker nodes do, and what a pod really is.

13.12 Kubernetes Architecture

Hook: One machine cannot handle the work of a million users — so who decides which machine runs what? Kubernetes answers with a two-tier structure: a control plane that thinks, and worker nodes that do.

13.12.1 Master-Slave Pattern

Kubernetes follows a master-slave (control plane – worker node) architecture — the same pattern used by Apache Hadoop, OpenStack, and many job schedulers. A single machine cannot handle all the work; distributed compute across many machines is necessary.

Master node (control plane) receives all input and directs work to worker nodes. It contains:

  • API Server — the front door for all kubectl commands
  • Scheduler — decides which worker node runs a new pod
  • Controller Manager — ensures desired state matches actual state
  • etcd — stores cluster state

Worker nodes are machines (physical or virtual — EC2 instances, laptops with Minikube, bare-metal servers). Each contains:

  • Docker engine — every worker node must have Docker installed
  • Pod — smallest deployable unit
  • Kubelet — agent communicating with master node
  • Kube Proxy — networking and traffic routing

If you have 1000 worker nodes, all 1000 must have Docker installed. The Docker engine lives exclusively in worker nodes, not in the master node.

Intuition — the head office and branch offices: The master node is the company's head office: it receives every order (API server), assigns work to branches (scheduler), verifies each branch reports what was promised (controller manager), and keeps the ledger of everything (etcd). Worker nodes are the branch offices: they employ the actual workers (containers in pods), run the local receptionist that talks to head office (kubelet), and handle local deliveries (kube proxy). Branches do the work; head office never does branch work itself.

Why master-slave is everywhere: The pattern is not unique to Kubernetes — Hadoop, OpenStack, and job schedulers all use it, because a single machine cannot handle all the work. The division of labour is inevitable at scale: someone must coordinate, and someone must execute.

13.12.2 Pod

A pod is the smallest deployable unit in Kubernetes. You do not run containers directly; you run pods that contain containers. A pod can contain one container (most common — 1:1 mapping) or multiple tightly-coupled containers sharing the same network namespace and storage volumes.

Exam note: Know that a pod is the smallest deployable unit, not a container.

13.12.3 Managed Kubernetes Services

Cloud Provider Service Abbreviation
Amazon Web Services Elastic Kubernetes Service EKS
Google Cloud Google Kubernetes Engine GKE
Microsoft Azure Azure Kubernetes Service AKS

With managed services, the cloud provider manages the master node; you manage worker nodes. Version upgrades are user-driven — the ops team decides when to upgrade.

The alternative — Amazon ECS: AWS offers Elastic Container Service (ECS), its own orchestration (not Kubernetes-based). For ECS, upgrades are automatic because AWS fully controls the platform. Trade-off: EKS gives portability across clouds; ECS locks you into AWS.

Intuition — EKS vs ECS is like EC2 vs Lambda: With Lambda and ECS, AWS manages the servers (and upgrades) automatically — you do not touch the platform. With EC2 and EKS, you manage and control the servers, including deciding when to upgrade. The trade-off is control versus convenience, repeated at every layer of AWS.

Q: When CNCF releases a new Kubernetes version, will it automatically upgrade on managed services like EKS? A: No. For managed Kubernetes, upgrades are user-driven. For AWS ECS (not Kubernetes-based), upgrades are automatic because AWS fully controls the platform. ECS is like Lambda (AWS manages everything); EKS is like EC2 (you manage upgrades).

13.12.4 Kubernetes Features

  • High availability — no downtime. If a pod crashes, Kubernetes restarts it automatically.
  • Scalability — handle traffic spikes by adding more pod replicas.
  • High performance — efficient resource utilisation across the cluster.
  • Disaster recovery — backup and restore capabilities.

Pitfalls:

  • Saying "we deploy a container" when the unit is the pod. Kubernetes schedules pods; containers are only what pods contain.
  • Expecting the master node to run containers. The Docker engine lives exclusively in worker nodes — the control plane coordinates, it does not execute workloads.
  • Assuming all 1000 nodes share one Docker installation. Docker is per-node: every worker node runs its own engine.

Recap + Bridge: Kubernetes is master-slave: the control plane (API server, scheduler, controller manager, etcd) coordinates, worker nodes (Docker, pods, kubelet, kube proxy) execute, and pods — not containers — are the smallest deployable unit. Managed services (EKS, GKE, AKS) run the control plane for you but leave upgrades to you. 13.13 shows the one command-line tool that talks to the whole cluster. Exam note: Know the master-slave architecture — master components (API server, scheduler, controller manager, etcd) and worker components (Docker, pod, kubelet, kube proxy) — that a pod is the smallest deployable unit, and the managed-versus-self-managed version upgrade behaviour.

13.13 kubectl

Hook: The master node's API server is the front door — and kubectl is the key. One tool controls any Kubernetes cluster, whether it is running on your laptop or across three clouds.

kubectl is the command-line tool for communicating with the Kubernetes master node. All Kubernetes commands start with kubectl: kubectl get pods (list pods), kubectl apply -f deployment.yaml (deploy application), kubectl scale deployment my-app --replicas=5 (scale replicas). The commands are identical whether running on Minikube (local), EKS (AWS), GKE (Google), or AKS (Azure).

Intuition — kubectl is the remote control: The API server is the television; kubectl is the remote control. The same remote works on any TV of the same brand family — Minikube, EKS, GKE, or AKS all speak the same API, so the commands you learn on your laptop transfer to production unchanged. This uniformity is why the course's cluster demos run identically in the self-study material (13.14).

Command What it does
kubectl get pods List pods running in the cluster
kubectl apply -f deployment.yaml Deploy (or update) the resources described in the YAML manifest
kubectl scale deployment my-app --replicas=5 Change the number of pod replicas for my-app
kubectl get nodes List the cluster's worker nodes
kubectl delete -f deployment.yaml Remove the deployed resources

13.13.1 Minikube

Minikube is a tool for running a single-node Kubernetes cluster on your laptop — one master node and one worker node, enough to learn and experiment. For production, use managed services (EKS, GKE, AKS) with many worker nodes.

Pitfalls:

  • Expecting different syntax per cloud. The same kubectl commands work on Minikube, EKS, GKE, and AKS; only the cluster configuration (and how you authenticate) differs.
  • Scaling to more replicas than the cluster has capacity for — kubectl scale asks Kubernetes to run N pods; if nodes run out of resources, pods wait in a pending state.
  • Using kubectl against the wrong context. With several clusters configured, commands silently target the active context — a classic source of "where did my deployment go?"

Recap + Bridge: kubectl is the single command-line interface to the Kubernetes API server — list pods, apply manifests, scale replicas — and it behaves identically on Minikube and all managed services. The lecture closes with the self-study assignment in 13.14, which runs this entire pipeline live with GitHub Actions and Argo CD.

13.14 Self-Study Assignment

Hook: Everything in this lecture — build, push, deploy — comes together in one 45-minute end-to-end demo that you should watch before the next class. It is the same pipeline that industry runs, live.

Before the next class, students are expected to watch a 45-minute end-to-end CI/CD demo covering the entire lifecycle: source code → GitHub Actions CI → Docker build → push to registry → Argo CD CD → deploy to Kubernetes. The demo uses the same REST application shown in class.

13.14.1 Homework Instructions

Access the demo using your BITS e-mail ID through Microsoft Teams. This will make the next lecture on Kubernetes deployment significantly easier to follow.

Why this matters: The demo connects every piece of the lecture in one sequence: the Docker commands of 13.5 and 13.8, the CI/CD flow of 13.10, the kubectl commands of 13.13, and the Kubernetes deployment of 13.12. Watching it once makes the next lecture's deeper coverage of services, scaling, and deployment strategies much easier to absorb.

Exam note: The self-study material is not examinable content by itself, but it demonstrates the exact end-to-end flow — source → CI → registry → CD → Kubernetes — that is a common exam question. If you have not watched the demo, at minimum be able to recite this flow and name the tools at each stage (GitHub Actions, Docker, registry, Argo CD, Kubernetes).

Exam Guidance Summary

  • The three eras of deployment (physical → VM → container): know the differences and advantages of each.
  • Docker's three-tier architecture (client → daemon → registry): understand what happens during docker pull, docker build, docker run.
  • The difference between an image (static blueprint) and a container (running instance) is frequently tested.
  • Dockerfile instruction order matters: base image → working directory → copy dependencies → install dependencies → copy source → expose port → set command.
  • Port mapping (-p host:container) is necessary to access containerised applications externally.
  • Volumes provide persistence; without them, container data is ephemeral.
  • Kubernetes master-slave architecture: master node (API server, scheduler, controller manager, etcd) and worker nodes (Docker engine, pod, kubelet, kube proxy).
  • A pod is the smallest deployable unit in Kubernetes, not a container.
  • Managed Kubernetes services (EKS, GKE, AKS) versus self-managed: know version upgrade behaviour.
  • CNCF is the open-source foundation that maintains Kubernetes.
  • CI/CD pipeline: source code → CI build → Docker image → registry → CD deploy → Kubernetes pods.

Key Industry Applications

  • AWS Lambda — container-based serverless functions; no OS management required
  • AWS EC2 — virtual machine instances; full OS control
  • AWS EKS — managed Kubernetes service on AWS
  • AWS ECS — Elastic Container Service, AWS's own orchestration (alternative to Kubernetes)
  • AWS ECR — Elastic Container Registry (alternative to Docker Hub)
  • Docker Hub — the most popular public container registry (free for public images)
  • Azure Container Registry / AKS — Microsoft's container registry and managed Kubernetes
  • Google Container Registry / GKE — Google's equivalents
  • GitHub Actions — CI/CD pipeline tool demonstrated in the self-study material
  • Argo CD — GitOps-based continuous delivery, a CNCF graduated project
  • Helm — Kubernetes package manager
  • Minikube — local single-node Kubernetes cluster for development and learning
  • BITS Virtual Lab — uses containerised images on AWS VMs with EBS volumes for persistence
  • OTT platforms (Amazon Prime, Netflix) — use Kubernetes for microservice scaling
  • ChatGPT — runs on Kubernetes infrastructure

SEML Lecture 13 notes

Software Engineering for Machine Learning· postgraduate· 2026-08-01

Sections Breakdown

113.1 Software Deployment Evolution

Deployment has moved through three eras — physical servers, hypervisor-based VMs, and container-based deployment — with containers eliminating the guest OS to become the industry standard for shipping software.

213.2 Containerization Fundamentals

Containers solve the environment-parity problem by bundling code, runtime, libraries, configuration, and (for ML) the trained model into one standard package that runs identically everywhere.

313.3 Docker

Docker is a three-tier container engine — client sends commands, daemon manages images and containers, registry stores images — and images are either pre-built system images or custom images layered on top.

413.4 Docker Objects: Image, Container, Volume

Images are static read-only blueprints (like OOP classes), containers are running instances (like objects), and volumes provide persistent storage outside the ephemeral container lifecycle.

513.5 Docker Commands and Workflow

The three core commands (pull, build, run) map to the three-tier architecture; the live Ubuntu demo shows that all container changes are ephemeral and vanish on exit without a volume.

613.6 Port Mapping

The -p flag maps a host port to a container port (host:container), making containerised web applications reachable; conflicts on the host port are resolved by choosing another host-side port.

713.7 Dockerfile and Building Custom Images

A Dockerfile is a recipe executed inside the container being built; instruction order (dependencies before source) exploits layer caching, and custom images can build on other custom images.

813.8 Build, Run, Push — End-to-End Workflow

The full lifecycle: docker build creates a static image, docker run instantiates it with port mapping, docker tag namespaces it to the account, and docker push publishes it to Docker Hub.

913.9 Containerizing a Machine-Learning Application

An ML app (Iris + Random Forest + FastAPI) is containerised with the same Dockerfile pattern as a REST app — the trained model, inference code, and dependencies are sealed into one image, and POST /predict returns label 0 (Setosa).

1013.10 CI/CD Pipeline with Docker and Kubernetes

The industry lifecycle: developers commit code, CI (GitHub Actions/Jenkins) builds and pushes a versioned image to a registry, CD (Argo CD/Flux CD) deploys it to Kubernetes via a YAML manifest, and microservices scale independently.

1113.11 Introduction to Kubernetes

Kubernetes is the container orchestration platform (originally Google Labs, first CNCF graduated project) that runs containers at scale with high availability, scaling, performance, and disaster recovery.

1213.12 Kubernetes Architecture

Kubernetes uses a master-slave architecture: the control plane (API server, scheduler, controller manager, etcd) coordinates worker nodes (Docker, pod, kubelet, kube proxy); pods are the smallest deployable unit, and managed services keep upgrades user-driven.

1313.13 kubectl

kubectl is the single CLI for the Kubernetes API server — get pods, apply YAML manifests, scale replicas — with identical commands across Minikube, EKS, GKE, and AKS.

1413.14 Self-Study Assignment

Students watch a 45-minute end-to-end CI/CD demo (source → GitHub Actions → Docker build → registry → Argo CD → Kubernetes) accessed through Microsoft Teams with their BITS e-mail ID.

15Exam Guidance Summary

Consolidated exam guidance: three deployment eras, Docker three-tier architecture, image vs container, Dockerfile order, port mapping, volumes, Kubernetes master-slave components, pod as smallest unit, managed-service upgrade behaviour, and the CI/CD flow.

16Key Industry Applications

Named industry applications: AWS services (Lambda, EC2, EKS, ECS, ECR), registries (Docker Hub, ACR, GCR), CI/CD (GitHub Actions, Argo CD, Helm), Minikube, BITS Virtual Lab, and Kubernetes at OTT platforms and ChatGPT.

Postgraduate students in Machine Learning

Exam Revision Notes

Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.

Software Deployment Evolution

Must-know: The three eras of deployment (physical, VM, container) and the advantages/differences of each; the container stack removes the guest OS layer that VMs require.

⚠️ Top pitfall: Confusing hypervisor (virtualizes whole machines with guest OSes) with a container engine (runs processes on the host OS kernel); treating containers as lightweight VMs when they share the host kernel.

Self-check: In the VM stack, which software layer sits between hardware and the guest OS? (Answer: the hypervisor.)

Connects to: 13.2

Containerization Fundamentals

Must-know: Containers exist to guarantee environment parity: code, binaries, runtime, libraries, and configuration are identical across all environments, so production bugs reproduce locally.

⚠️ Top pitfall: Assuming a container is fully sealed and OS-independent; it still depends on the host OS kernel, and ML containers must include the trained model or they fail at first prediction.

Self-check: Name the four things a container bundles for a software engineering application. (Answer: source code, runtime, system libraries, configuration.)

Connects to: 13.1, 13.3

Docker

Must-know: Docker's three-tier architecture: client sends commands, the daemon executes them and manages images/containers, and the registry stores images; during docker pull the daemon (not the client) contacts the registry.

⚠️ Top pitfall: Confusing client and daemon roles — the daemon does all real work and talks to the registry; the client is only a messenger.

Self-check: When you run docker pull ubuntu, which component checks the local image store first? (Answer: the Docker daemon.)

Connects to: 13.2, 13.4

Docker Objects: Image, Container, Volume

Must-know: An image is a static read-only blueprint; a container is a running instance of it; one image produces many containers; volumes give persistence while container data is ephemeral.

⚠️ Top pitfall: Assuming containers from the same image share state, or storing data only on the container filesystem — it disappears when the container is removed.

Self-check: Is mentioning an OS required when defining an image? (Answer: No — you can start directly from a runtime image like FROM python:3.11.)

Connects to: 13.3, 13.5

Docker Commands and Workflow

Must-know: docker pull downloads an image, docker run instantiates a container from it, and every change made inside a container disappears on exit unless a volume is attached.

⚠️ Top pitfall: Assuming a container remembers installed software and files after exit — the container's writable layer is discarded when the container stops.

Self-check: After running docker run -it ubuntu:latest, installing vim, creating one.txt, and exiting — what happens if you restart the container? (Answer: vim is gone and one.txt does not exist.)

Connects to: 13.4, 13.6

Port Mapping

Must-know: Port mapping syntax -p <host-port>:<container-port> forwards host traffic into the container; without it the app is unreachable, and a busy host port must be replaced (e.g., -p 9000:8000).

⚠️ Top pitfall: Reading -p backwards (host:container) or assuming both sides must match; only the host port must be free and unique.

Self-check: What does -p 9000:8000 do, and which URL do you open in the browser? (Answer: forwards host 9000 to container 8000; open http://localhost:9000.)

Connects to: 13.5, 13.12

Dockerfile and Building Custom Images

Must-know: Dockerfile instruction order: FROM base → WORKDIR → COPY requirements → RUN install → COPY source → EXPOSE → CMD; dependencies-first ordering lets Docker cache the dependency layer.

⚠️ Top pitfall: Copying source before installing dependencies, which invalidates the cache and forces dependency re-installation on every source change.

Self-check: Why is requirements.txt copied before the source code? (Answer: so the dependency install layer is cached and reused when only source changes.)

Connects to: 13.5, 13.8

Build, Run, Push — End-to-End Workflow

Must-know: End-to-end workflow: docker build -t name:tag . → docker run -p 8000:8000 → docker login → docker tag account/name:tag → docker push; each step maps to a tier of the architecture.

⚠️ Top pitfall: Pushing without a tag namespaced to the account ID, or without docker login — the registry rejects both.

Self-check: What does docker tag do before a push? (Answer: namespaces/renames the image, e.g., rest-app:latest → 6945/rest-app:latest, without copying data.)

Connects to: 13.3, 13.7, 13.9

Containerizing a Machine-Learning Application

Must-know: The ML container pattern: COPY the model and inference code, install ML dependencies, CMD starts the server; POST /predict with four Iris features returns prediction 0 → Setosa.

⚠️ Top pitfall: Forgetting to copy the trained model file into the image — the server starts but fails at the first prediction.

Self-check: What does the postprocessing step do in the Iris app? (Answer: maps prediction integers to class names — 0 → Setosa, 1 → Versicolor, 2 → Virginica.)

Connects to: 13.8, 13.10

CI/CD Pipeline with Docker and Kubernetes

Must-know: CI/CD flow: source code → CI build (GitHub Actions/Jenkins) → Docker image → registry (versioned) → CD deploy (Argo CD/Flux CD) → Kubernetes pods; scaling is per-microservice.

⚠️ Top pitfall: Deploying images that CI never built, or tagging everything 'latest' so rollbacks become impossible.

Self-check: Where does CI end and CD begin? (Answer: CI ends at the registry after build+test+push; CD pulls the image and deploys to the cluster.)

Connects to: 13.8, 13.11

Introduction to Kubernetes

Must-know: Kubernetes (K8s) is container orchestration for scale — developed by Google Labs, donated to CNCF, first graduated CNCF project; Docker alone is sufficient for a single machine only.

⚠️ Top pitfall: Conflating Docker (runs containers on one machine) with Kubernetes (orchestrates containers across many machines).

Self-check: Which foundation maintains Kubernetes, and which tool runs on every machine in a Kubernetes cluster? (Answer: CNCF; a container engine like Docker.)

Connects to: 13.10, 13.12

Kubernetes Architecture

Must-know: Master-slave architecture: master = API server, scheduler, controller manager, etcd; worker = Docker engine, pod, kubelet, kube proxy. Pod is the smallest deployable unit, not a container.

⚠️ Top pitfall: Calling the pod a container — pods are the unit Kubernetes schedules; or expecting version upgrades to be automatic on managed Kubernetes (they are user-driven on EKS).

Self-check: Which components live in the master node, and which in worker nodes? (Answer: master = API server/scheduler/controller manager/etcd; worker = Docker/pod/kubelet/kube proxy.)

Connects to: 13.11, 13.13

kubectl

Must-know: kubectl communicates with the master node's API server; the same commands work on Minikube, EKS, GKE, and AKS.

⚠️ Top pitfall: Expecting different syntax per cloud provider — commands are identical, only authentication/context differs.

Self-check: Which command deploys the resources described in deployment.yaml? (Answer: kubectl apply -f deployment.yaml.)

Connects to: 13.12

Self-Study Assignment

Must-know: The self-study demo walks the full lifecycle: source code → GitHub Actions CI → Docker build → push to registry → Argo CD CD → deploy to Kubernetes.

⚠️ Top pitfall: Skipping the demo and missing the connected view of the pipeline — it makes the next lecture's Kubernetes deployment topics much harder to follow.

Self-check: Where is the demo accessed? (Answer: Microsoft Teams, using the BITS e-mail ID.)

Connects to: 13.10, 13.13

Was this lecture useful?

Loading comments…