Docker-Complete-Guide
Docker-Complete-Guide
Docker
If you can draw the lifecycle diagram (Dockerfile → Image → Container) from memory, you already understand most of Docker.
Table of Contents
Part Topic
5 Containerizing FastAPI
You reply:
Instead of sending instructions for building the environment, send the whole environment.
This is not a coincidence: Docker's logo is a whale carrying shipping containers. Before standardised shipping containers, every ship
was loaded differently and cargo handling was chaos. One standard box changed global trade. Docker did the same thing for software.
# terminal 1
sudo systemctl start postgresql
# terminal 2
source .venv/bin/activate && uvicorn [Link]:app --reload
# terminal 3
npm run dev
After Docker:
docker compose up
That is it. PostgreSQL, FastAPI, and [Link] all start, already configured, already connected.
Developer's laptop
↓
Docker Image ← the exact same artifact, byte for byte
↓
┌────┴─────┬──────────┬────────────┐
▼ ▼ ▼ ▼
Testing Staging Production Your teammate
(AWS) (Azure) (GCP) (their laptop)
The image that passed your tests is literally the image running in production. No "the staging server has a different OpenSSL version"
surprises.
Now scale that up. Imagine Google with thousands of servers. Installing Ubuntu, Python, Node, Java, and libraries manually on each one is
impossible. Instead: build one image, deploy it to server 1, server 2, ... server 10,000. Identical environment everywhere.
Benefit Meaning
Isolation Project A can use Python 3.9 and Project B Python 3.12, on one laptop, with no conflict
Portability Runs on Ubuntu, macOS, Windows, AWS, Azure, GCP without changes
Speed A container starts in ~1 second; onboarding a new developer takes one command instead of a day
Q2. What problem does Docker solve? Environment inconsistency — the "works on my machine" problem — along with dependency
conflicts between projects, slow onboarding, and differences between development and production environments.
Q3. Why do companies deploy images rather than source code? Because the image is a fixed, tested artifact. Deploying source code
means rebuilding the environment on each server, which can introduce differences; deploying an image guarantees that what was tested is
exactly what runs.
The key line: a VM ships an entire guest operating system. A container shares the host's kernel and ships only the application plus
its libraries.
Isolation strength Stronger (full separation) Strong, but shares the kernel
One-line answer:
"A VM virtualises hardware and runs a complete guest OS, so it is heavy and slow to boot. A container virtualises at the OS level,
sharing the host kernel through namespaces and cgroups, so it is lightweight and starts almost instantly. Containers give you isolation
at a fraction of the cost."
Two Linux kernel features do the work. Knowing their names impresses interviewers:
Namespaces Isolate what a process can see — its own process list, network interfaces, filesystem, hostname
cgroups (control groups) Isolate what a process can use — CPU, memory, and I/O limits
A container is really just a normal Linux process that has been given its own private view of the system. It is not a tiny virtual
machine. That is why it starts as fast as any other process.
Component Role
Docker Daemon ( dockerd ) The service that does all the real work
containerd / runc The low-level runtime that actually creates the container process
This is why Docker fails with "Cannot connect to the Docker daemon" — the CLI is fine, but the background service is not running. The
fix is sudo systemctl start docker .
Q1. Docker vs Virtual Machine? A VM virtualises hardware and runs its own guest OS, making it heavy (GBs, minutes to boot). A
container virtualises the OS, sharing the host kernel, making it light (MBs, near-instant start). VMs give stronger isolation; containers give
far better density and speed.
Q2. How does a container achieve isolation? Through Linux namespaces, which give the process its own view of the filesystem,
network, and process tree, and cgroups, which limit its CPU, memory, and I/O usage.
Q3. What is the Docker daemon? The background service ( dockerd ) that builds images, runs containers, and manages networks and
volumes. The CLI is only a client that sends it API requests.
Install from Docker's official repository, not the outdated [Link] package in Ubuntu's default repos. This is how it is done
professionally, and it gets you current versions plus Compose v2.
docker --version
docker compose version
Step 2 — Update
These let Ubuntu securely fetch and verify packages from Docker.
Why a GPG key? It cryptographically proves the packages really came from Docker and were not tampered with in transit. This is the
same trust model as a JWT signature.
Step 6 — Permissions
echo \
"deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/[Link]] \
[Link] \
$(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/[Link].d/[Link] > /dev/null
This tells Ubuntu: "from now on, get Docker directly from Docker."
Step 9 — Install
docker-compose-plugin docker compose (v2, a subcommand — not the old docker-compose script)
Step 11 — Verify
⚠ Security note worth knowing for interviews: membership of the docker group is effectively root access, because you can
mount the host filesystem into a container. It is fine on your own development laptop; on a shared production server you would use
rootless Docker or restrict access.
Compose is here from the start because your project will end up looking like this:
Docker Compose
│
┌───────────────┼────────────────┐
▼ ▼ ▼
FastAPI PostgreSQL [Link]
Container Container Container
If you understand this Part, you understand 70% of Docker. Every Docker interview starts here.
Docker Hub
│
docker pull (automatic)
│
▼
Image on your disk
│
create container
│
▼
Container
│
execute
│
▼
"Hello from Docker!"
│
▼
Container exits
PYTHON DOCKER
------ ------
Class ←→ Image
Object ←→ Container
An image is a read-only blueprint: a packaged filesystem containing an OS layer, a runtime, your dependencies, your code, and the
command to start it.
Analogy: an image is [Link] . You have downloaded it. It is not installed, it is not running — it is just sitting there.
Immutable — an image never changes. To change something, you build a new image.
Layered — it is built from stacked read-only layers (more on this in Part 4).
Analogy: the Word installer is the image; Microsoft Word open on your screen is the container.
IMAGE CONTAINER
Python 3.12 A live FastAPI server
FastAPI ──run──▶ listening on port 8000
Your code serving real requests
Start command
Exactly like:
student1 = Student()
student2 = Student()
student3 = Student()
This is the foundation of scaling. When traffic grows, you do not rewrite anything — you run more containers from the same image behind
a load balancer. Kubernetes automates precisely this.
When Docker printed Pulling from library/hello-world , it downloaded from Docker Hub.
Docker Hub is a registry: a server that hosts images. It holds official images for python , postgres , redis , node , nginx , and millions of
community images — plus, soon, yours (Part 8).
postgres : 18
│ │
│ └── tag (the version)
└────────── repository name
vikaskumar8048 / second-brain-backend : v1
│ │ │
username image name tag
⚠ Never use :latest in production. It is not "the newest" in any guaranteed sense — it is just the default tag name. If it changes
under you, your deployment silently changes too. Pin explicit versions: postgres:18 , python:3.12-slim , your-app:v1.2.0 .
A container lives exactly as long as its main process. When the process ends, the container stops. This is the most common source of "my
container keeps exiting" confusion.
Command Shows
docker exec -it <id> bash is your most valuable debugging tool. It puts you inside the container so you can check whether your
files are really there, whether the env vars are set, and whether the app can reach the database.
Docker Hub
│
docker pull
│
▼
IMAGE
│
docker run
│
▼
CONTAINER
│
▼
Running Application
3.11 Exercise
docker images
docker ps
docker ps -a
Observe the difference between an image, a running container, and a stopped container. If you can explain those three outputs, you
have the core concept.
Q1. Difference between an image and a container? An image is a read-only blueprint containing the application, its dependencies, and
its start command. A container is a running instance of that image. One image can produce many containers — exactly like a class and its
objects.
Q3. Why did docker ps show nothing after running hello-world? Because a container only lives as long as its main process. hello-
world printed its message and exited, so the container stopped. docker ps -a still shows it.
Q4. Can one image run multiple containers? Yes — that is how horizontal scaling works. Multiple identical containers run from one
image behind a load balancer.
Q5. Are containers stateless? The container's writable layer is deleted with the container, so anything written inside is lost. Durable
state must go into a volume or an external database. That is why containers are treated as disposable.
Making Maggi:
If everyone follows the same recipe, everyone gets the same Maggi.
A Dockerfile is a recipe for building an image. Same recipe → same image → same behaviour, on every machine.
A Dockerfile never runs. It only builds. This confuses beginners constantly — write it on a sticky note.
cd ~/Downloads/second-brain/backend
touch Dockerfile
FROM python:3.12-slim
Start from an existing image that already has Linux + Python 3.12 installed.
python:3.12-alpine ~50 MB Smallest, but uses musl libc — some Python wheels fail to build
Every Dockerfile starts with FROM . ( FROM scratch means a completely empty base.)
WORKDIR /app
Equivalent to cd /app , and it creates the directory if missing. Every instruction after this runs inside /app .
COPY [Link] .
Copies a file from your laptop into the image. The . is the destination — /app , because of WORKDIR .
RUN executes a command while the image is being built, and the result is baked permanently into a layer.
★ RUN vs CMD — the classic interview question. RUN happens at build time (installing packages). CMD happens at run time
(starting the server). RUN pip install is done once and stored; CMD uvicorn executes each time a container starts.
--no-cache-dir tells pip not to keep its download cache, which would otherwise add ~50 MB of dead weight to the image.
COPY . .
Copy everything from the build context into /app . Comes after the install step, deliberately — see caching below.
EXPOSE 8000
Documentation only. It declares "this application listens on 8000". It does not publish the port to your laptop; that happens with -p at
run time.
⚠ --host [Link] is mandatory, and everyone gets this wrong once. [Link] means "only accept connections from inside this
machine" — and inside a container, that means only from inside the container. Your laptop's browser would never reach it. [Link]
means "accept on all interfaces". If your containerized API is unreachable, check this first.
Also use the JSON array form ( ["uvicorn", "[Link]:app"] ), not the shell string form. The array form runs your process as PID 1
directly, so docker stop delivers the shutdown signal to it properly.
CMD vs ENTRYPOINT
CMD ENTRYPOINT
Overridden by docker run <image> other-cmd ? ✔ Yes, easily ✘ No — arguments are appended instead
Use CMD for normal applications. Use ENTRYPOINT when the image is one specific tool.
The rule: when a layer changes, that layer and every layer above it must be rebuilt. Cached layers below are reused instantly.
Golden rule of Dockerfiles: put the things that change least at the top, and the things that change most at the bottom.
Your code changes fifty times a day; your dependencies change once a week.
This is a genuinely strong interview answer — most candidates can write a Dockerfile, few can explain why the lines are in that order.
4.7 .dockerignore ★
.venv/
__pycache__/
*.pyc
.git/
.env
*.md
tests/
.pytest_cache/
1. Speed — the entire build context is sent to the Docker daemon first; without this, you upload your 400 MB .venv on every build
2. Size — COPY . . would otherwise bake junk into the image
3. Security — it stops .env and .git (which contains your whole history) from being copied into an image you might publish publicly
cd ~/Downloads/second-brain/backend
docker build -t second-brain-backend .
Piece Meaning
. The build context — the current directory, where the Dockerfile lives
That trailing . is not decoration. It tells Docker which folder to send as the build context. Forgetting it is the most common build error.
Verify:
docker images
Useful variants:
Q1. What is a Dockerfile? A text file of instructions that Docker follows to build an image — the base image, dependencies, files to copy,
and the start command.
Q2. RUN vs CMD ? RUN executes during the build and its result is stored in a layer; CMD defines the command executed when a container
starts from the image.
Q3. CMD vs ENTRYPOINT ? CMD provides a default command that is easily overridden at docker run ; ENTRYPOINT fixes the executable, and
any run arguments are appended to it.
Q4. What does EXPOSE do? It documents which port the application listens on. It does not publish anything — actual publishing requires -
p host:container at run time.
Q5. Why copy [Link] before the rest of the code? To exploit layer caching. Dependencies change rarely; code changes
constantly. This ordering means a code edit does not invalidate the expensive pip install layer.
Q6. How would you reduce image size? Use a slim base image, add a .dockerignore , combine RUN commands, use --no-cache-dir
with pip, and use a multi-stage build to leave build tools out of the final image.
Q7. What is the build context? The directory sent to the Docker daemon when building — the . at the end of docker build . Everything
in it (minus .dockerignore entries) is uploaded, which is why the ignore file matters for build speed.
-p 8000:8000
│ │
│ └── port INSIDE the container
└─────── port on YOUR laptop
Your browser
localhost:8000
│
▼
┌───────────────────┐
│ Your laptop │
│ port 8000 │
└───────────────────┘
│ Docker forwards
▼
┌───────────────────┐
│ Container │
│ port 8000 │ ← uvicorn listening on [Link]:8000
└───────────────────┘
A container's network is isolated by default. Without -p , nothing on your laptop can reach it — the app runs perfectly and is completely
unreachable.
docker run \
-d \ # detached — run in the background
--name backend \ # a readable name instead of "nice_hopper"
-p 8000:8000 \ # publish the port
-e DATABASE_URL=postgresql://... \ # pass an environment variable
--restart unless-stopped \ # restart automatically after a crash/reboot
second-brain-backend
-p Publish a port
-v Mount a volume
source .venv/bin/activate
uvicorn [Link]:app --reload
The application is no longer using your host Python installation. It uses the Python runtime packaged inside the image. Your laptop
could have no Python at all and it would still run.
This is Docker's central benefit in one sentence: everyone runs the exact same environment.
Run docker logs <id> . Usually a Python traceback — a missing module, or a wrong path in [Link]:app . Remember: the container dies
with its main process.
FROM python:3.12-slim
# do not write .pyc files; do not buffer stdout (so logs appear immediately)
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY [Link] .
RUN pip install --no-cache-dir -r [Link]
COPY . .
EXPOSE 8000
Addition Why
PYTHONUNBUFFERED=1 Without it, Python buffers output and docker logs appears empty during a crash
USER appuser If the app is compromised, the attacker is not root inside the container. Default-root is one of the most common
container security findings
No --reload Reload is a development feature; it watches the filesystem and wastes resources in production
Right now:
Terminal 1 → PostgreSQL
Terminal 2 → Backend
Terminal 3 → Frontend
Three terminals. Three commands, each with a long list of flags. Three sets of configuration to keep in your head, and a specific order to
start them in.
Docker Compose says: put it all in one file, then start everything together.
docker compose up
Without a conductor:
Docker Compose
│
┌────────────┼────────────┐
▼ ▼ ▼
Database Backend Frontend
One command. Everything starts in the right order, on a shared network, with the right configuration.
Notice: FastAPI will no longer connect to localhost . It connects to database — the service name. Compose gives every service a DNS
name on its private network.
cd ~/Downloads/second-brain
touch [Link]
services:
database:
image: postgres:18
container_name: second-brain-db
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: second_brain
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5
backend:
build: ./backend
container_name: second-brain-backend
depends_on:
database:
condition: service_healthy
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@database:5432/second_brain
restart: unless-stopped
volumes:
postgres_data:
POSTGRES_PASSWORD=your_strong_password
Compose reads .env from the project root automatically and substitutes ${POSTGRES_PASSWORD} . This keeps the password out of the file
you commit — the original tutorial hard-codes it, which is fine for learning and dangerous in a repository.
Note on version: — older tutorials start the file with version: "3.9" . That key is now obsolete and Compose will warn you about it.
Modern Compose files simply begin with services: .
services:
"Which containers should Docker create?" Each key underneath ( database , backend ) becomes one container and one hostname.
image: vs build: ★
database:
image: postgres:18 # DOWNLOAD a ready-made image from Docker Hub
backend:
build: ./backend # BUILD from ./backend/Dockerfile — YOUR image
image: build:
Rule: one Dockerfile = one application. Your backend and frontend each get one. Redis and Postgres need none — they already
exist on Docker Hub.
environment:
The official postgres image reads POSTGRES_USER , POSTGRES_PASSWORD , and POSTGRES_DB on first start and creates that user and database
for you. No manual CREATE DATABASE — that is why the Postgres setup you did by hand in Phase 2 is now one line of YAML.
DATABASE_URL: postgresql://postgres:...@database:5432/second_brain
▲
NOT localhost
Why? Because inside a container, localhost means that container itself. The backend container asking for localhost:5432 is asking itself
for a database it does not have.
Compose creates a private network and registers each service name as a DNS hostname:
This is one of Compose's best features, and one of its most-asked interview questions.
depends_on:
⚠ A crucial subtlety interviewers probe: plain depends_on waits for the container to start, not for PostgreSQL to be ready to accept
connections. Postgres takes a few seconds to initialise, so your backend can start, fail to connect, and exit. The fix is the healthcheck
plus condition: service_healthy shown above — or retry logic in your application.
ports:
ports:
- "8000:8000" # laptop:container
Same meaning as -p . Note that ports is only needed for access from your laptop. Containers talk to each other over the internal
network regardless. In production you would often remove 5432:5432 so the database is not reachable from outside at all.
volumes:
volumes:
- postgres_data:/var/lib/postgresql/data
/var/lib/postgresql/data is where PostgreSQL stores its files inside the container. Mapping it to a named volume moves the real storage
outside the container's lifecycle.
The bottom-level volumes: block declares the named volume so Docker creates and manages it.
restart:
restart: unless-stopped
6.6 Running It
All automatically. Open [Link] — your API, talking to a PostgreSQL container, with no PostgreSQL installed on your
laptop.
⚠ down vs down -v . down keeps your named volumes, so your database survives. down -v deletes them. Type -v by accident and
your data is gone.
frontend:
build: ./frontend
container_name: second-brain-frontend
depends_on:
- backend
ports:
- "3000:3000"
environment:
NEXT_PUBLIC_API_URL: [Link]
Careful with that last line. Server-side code in the frontend container would use [Link] (the internal DNS name). But
NEXT_PUBLIC_ variables run in the user's browser, which is outside the Docker network and has no idea what backend means. The
browser must use [Link] . Getting this backwards is one of the most common full-stack Docker bugs.
Q1. What is Docker Compose? A tool for defining and running multi-container applications from a single YAML file, handling build order,
networking, volumes, and environment configuration with one command.
Q2. Dockerfile vs [Link]? A Dockerfile describes how to build one image. A Compose file describes how to run several
containers together, including which images they use, how they network, and what they persist.
Q3. How do containers find each other in Compose? Compose creates a private bridge network and registers each service name as a
DNS hostname, so the backend connects to database:5432 rather than localhost:5432 .
Q4. Why not localhost inside a container? Because localhost refers to the container itself, not the host machine or a sibling
container. Each container has its own network namespace.
Q6. What is the difference between docker compose down and down -v ? down removes containers and the network but preserves
named volumes; -v also deletes the volumes and therefore the data.
7.1 Networks
Driver Behaviour
bridge The default — a private network on the host; containers reach each other by IP, and by name if on a user-defined bridge
host The container shares the host's network stack directly (no isolation, no port mapping)
Compose automatically creates a user-defined bridge for your project, which is what enables DNS by service name.
docker network ls
docker network inspect second-brain_default
This is exactly what Compose does for you — which is a good way to explain Compose in an interview: it is a declarative wrapper over
the docker run , docker network , and docker volume commands you would otherwise type by hand.
A container's filesystem is ephemeral. Everything written inside its writable layer disappears when the container is removed.
# 2. BIND MOUNT — maps a folder on your laptop. Best for live-reloading code in dev.
-v ./backend:/app
Best for Database data in any environment Source code during development
Now editing a file on your laptop instantly reloads the server inside the container — you get Docker's consistency without rebuilding the
image for every change.
Use this in a [Link] for development only. Production should use the baked-in code from the image, with no bind
mount and no --reload .
Volume Commands
docker volume ls
docker volume inspect second-brain_postgres_data
docker volume rm <name>
docker volume prune # delete all unused volumes ⚠
Docker quietly consumes tens of gigabytes. Learn these before your disk fills up:
Write code
↓
Build a Docker image
↓
Push the image to a registry
↓
A teammate / a server / Kubernetes
↓
docker pull your-image
↓
docker run
Once your image is on Docker Hub, anyone can run your entire backend with no Python, no pip install, no dependency conflicts.
Everything is already inside the image.
docker login
Username: your-username
Password: <use an Access Token, not your password>
Login Succeeded
Generate an Access Token under Account Settings → Security. Same principle as GitHub's Personal Access Token: a revocable
credential instead of your real password.
docker images
docker tag does not copy anything. It adds a second name pointing at the same image id — like a symlink.
Step 5 — Push
Docker uploads layer by layer, and skips layers the registry already has.
Step 6 — Verify on your Docker Hub profile. You will see both repositories.
For that to pull instead of build, swap build: for image: in the Compose file:
services:
backend:
image: your-username/second-brain-backend:v1 # was: build: ./backend
frontend:
image: your-username/second-brain-frontend:v1 # was: build: ./frontend
database:
image: postgres:18
Tagging with the Git commit SHA is what serious teams do. It makes every deployed image traceable back to the exact line of code
that produced it — which is exactly what you need at 3 a.m. when production breaks.
second-brain/
│
├── backend/
│ ├── Dockerfile ← builds the FastAPI image
│ ├── .dockerignore
│ ├── [Link]
│ └── app/
│
├── frontend/
│ ├── Dockerfile ← builds the [Link] image
│ └── ...
│
├── [Link] ← runs everything together
├── .env ← secrets (gitignored)
└── .gitignore
3. Compose combines applications. Each service becomes a container; Compose wires them together.
5. Volumes make data survive. Containers are disposable; volumes are not.
6. Build ≠ Run.
Needed when Code or Dockerfile changed Every time you start the app
Image
├──▶ Container 1
├──▶ Container 2
└──▶ Container 3
Every push produces a new, tested, versioned image. Nobody SSHes into a server to run git pull and pip install ever again.
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 5
template:
spec:
containers:
- name: backend
image: your-username/second-brain-backend:v1
Notice: there is no Python here. No [Link] , no build step. Just an image name and replicas: 5 .
Kubernetes pulls that image and runs five identical containers, restarts them if they crash, and scales them up or down. That is only
possible because the image is a complete, self-contained, immutable artifact.
You do not need these today, but knowing them separates a candidate who used Docker from one who understands it.
The problem: compilers and build tools end up inside your final image, bloating it and widening the attack surface.
The solution: build in one stage, copy only the result into a clean second stage.
Only what stage 2 explicitly copies survives. Typical result for a Node or Go application: 1 GB → 50 MB.
10.2 Healthchecks
Docker now reports the container as healthy or unhealthy rather than merely "running". Compose can wait on it, and orchestrators can
restart on it.
Remember the /health endpoint you built in the FastAPI guide? This is what it was for.
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
Without limits, one runaway container can consume the entire host. This is cgroups , exposed as configuration.
Practice Why
Run as a non-root user ( USER appuser ) Limits the blast radius of a compromise
Never bake secrets into images Anyone can extract image layers
Scan images ( docker scout cves <image> ) Finds known CVEs in your dependencies
You will learn these in context — Redis when you add caching, Kafka when you add streaming, and the rest when you deploy to AWS.
Learning tools in the situation that needs them beats memorising features in isolation.
Cannot connect to the Docker Daemon not running sudo systemctl start docker
daemon
permission denied ... [Link] User not in the docker group sudo usermod -aG docker $USER && newgrp
docker
Container exits immediately The main process crashed or finished docker logs <id> and read the traceback
localhost:8000 refuses to connect Missing -p , or bound to [Link] Add -p 8000:8000 ; use --host [Link]
port is already allocated Something else uses that port Change the host port, or stop the other
process
Backend cannot reach the database Used localhost instead of the service name Use database:5432
connection refused on first Postgres not ready yet Add a healthcheck + condition:
Compose start service_healthy
Code changes do not appear The image still has the old code docker compose up --build , or bind-mount in
dev
COPY failed: file not found Path is outside the build context, or excluded by Fix the path or the ignore file
.dockerignore
Build is slow every single time Bad layer ordering Copy [Link] and install before
COPY . .
Data lost after compose down Used -v , or never had a volume Define a named volume; avoid down -v
Disk full Accumulated images and volumes docker system df then docker system prune
no space left on device during Same Prune, and use a slimmer base image
build
"Docker packages an application together with its runtime, libraries, and configuration into an image — an immutable, layered
blueprint built from a Dockerfile. Running that image creates a container, which is really just a host process isolated using Linux
namespaces and cgroups, so it starts in under a second and shares the host kernel instead of shipping a whole guest OS like a VM. The
relationship is the same as a class and its objects: one image, many containers, which is how you scale horizontally. Real projects have
several services, so Docker Compose declares them in one YAML file — it builds or pulls each image, creates a private network where
services reach each other by service name rather than localhost , attaches volumes so database data survives container deletion,
and starts everything with docker compose up . Images are pushed to a registry like Docker Hub, and that image becomes the
deployment artifact: CI builds it on every push, and Kubernetes pulls and runs it, because Kubernetes understands images, not source
code. The core benefit is that the exact artifact tested on my laptop is the one running in production."
Fundamentals
5. Image vs container?
6. Can one image run many containers?
7. What is Docker Hub / a registry?
8. What are image layers, and why does ordering matter?
9. Why should you avoid the latest tag?
10. Are containers stateless? Where does state go?
Dockerfile
18. What is Docker Compose and how does it differ from a Dockerfile?
19. How do containers discover each other in Compose?
20. Why can't a container use localhost to reach another container?
21. Does depends_on wait for readiness? How do you actually wait?
22. Named volume vs bind mount — when do you use each?
23. What does -p 8000:8000 mean, and which side is which?
24. Why must uvicorn bind to [Link] inside a container?
Production
25. How do secrets get into a container? (Not baked into the image.)
26. What is a healthcheck for?
27. Describe the CI/CD flow from git push to a running container.
28. Why is Docker a prerequisite for Kubernetes?
✔ Linux
✔ Git & GitHub
✔ FastAPI
✔ PostgreSQL
✔ Docker ← you are here
↓
Redis (Dockerized — caching, sessions, rate limiting)
↓
Kafka (Dockerized)
↓
AWS (deploy the image)
↓
Kubernetes (orchestrate many copies of the image)
Notice that Docker never really "ends". Every technology from here on arrives as a container — which is exactly why learning it
incrementally, in the context where it is used, beats memorising features in isolation.