Covers: fundamentals β installation β images β containers β volumes β networking β Docker Compose.
Docker is a containerization platform β a tool used to create, manage, and run containers.
It is extremely useful for developers because it helps at every stage of the software lifecycle:
Develop β Package β Ship β Run
Key definition to memorize:
Docker provides the ability to run an application in an isolated environment called a container.
Because of Docker, the deployment and development process has become far more efficient and easy compared to before.
A developer builds an application using several tools and services (Node.js, a database, etc.). They hand the code + instructions to the testing team. The tester tries to run it⦠and it fails.
The developer's famous reply:
"But it works on MY machine!"
| Cause | Example |
|---|---|
| OS differences | Dev on macOS, Tester on Windows |
| OS settings | Different configurations |
| Missing libraries | A library installed on dev's machine only |
| Missing dependencies | Package not installed on tester's side |
| Missing files | A config file wasn't shared |
| Missing environment variables | .env not transferred |
| Version mismatch β | Dev tested on v1.2, tester has v1.0 |
With Docker, the developer:
- Builds the application
- Uses Docker to package everything needed to run it:
- Application code
- External services
- Libraries
- Dependencies
- Config files
- Delivers this single package to the testing team
Now the tester needs only ONE thing: Docker. They simply extract & run the package β and it works. β
BEFORE DOCKER AFTER DOCKER
βββββββββββββ ββββββββββββ
Dev machine β
works Dev machine β
works
β (code + instructions) β (one Docker image)
Test machine β FAILS Test machine β
works
β β
compatibility hell only Docker needed
Think of shipping containers:
- Goods are packed into a container and shipped from one place to another
- The dock (β Docker) manages these containers
- If goods need a cold environment, that container maintains a low temperature internally
- Each container is isolated β one container's conditions do NOT affect another's
Exactly the same idea:
A container is a way to package an application with all its necessary dependencies and configuration.
Properties of a container:
| Property | Meaning |
|---|---|
| β Isolated | Runs in its own environment; other containers can't see it |
| β Easily shared | Can be handed to anyone, anywhere |
| β Efficient | Makes deployment & development fast |
| β Self-contained | Everything needed to run the app lives inside |
Docker sits between the OS and your applications. Here's the layered view:
By running apps inside containers, we remove the app's direct dependency on the host operating system.
Result: Transfer the container to another team or another server β it still works. No compatibility issues.
Problem: You have two apps on one computer:
- App 1 needs Node.js v14
- App 2 needs Node.js v16
How do you run both on the same server?
| Option | Cost |
|---|---|
| β Create 2 Virtual Machines | Heavy, wasteful, slow |
| β Create 2 Containers | Lightweight, fast, isolated |
Since containers are isolated and have no connection to each other, App1's container runs Node 14 and App2's container runs Node 16 β on the same machine, at the same time. π
| Feature | π³ Docker Containers | π₯οΈ Virtual Machines |
|---|---|---|
| Impact on OS | Low | High |
| Speed | Very fast | Slow |
| Disk space usage | Low | High |
| Resource usage | Only what the app needs; rest stays free & available | Resources totally divided β lots of waste |
| Sharing / Rebuilding / Distribution | Easy | Really challenging |
| What it encapsulates | Just the app + its dependencies | The entire machine (incl. Guest OS) |
| Guest OS required? | β No | β Yes (per VM!) |
Verdict: If your goal is to encapsulate just the application, Docker containers are the better approach.
This single flow diagram explains the entire Docker lifecycle β memorize it:
| Component | Definition |
|---|---|
| π Dockerfile | A simple text file containing instructions to build an image |
| π¦ Docker Image | A single file with all the dependencies and libraries required to run the program. (This is the "package" the developer sends to the tester!) |
| βοΈ Docker Engine | The tool that runs the image |
| π’ Docker Container | The running instance / process created when you run an image |
| βοΈ Docker Registry | The central repository for storing and distributing images |
β Key insight: You can run the same image multiple times β creating multiple container instances.
| Term | Meaning | Example |
|---|---|---|
| Registry | The whole common place / platform | Docker Hub |
| Repository | One collection inside the registry holding different versions/tags of the SAME image | The node repo (containing node:18, node:20, node:21β¦) |
| Type | Who uses it | Why |
|---|---|---|
| Public (Docker Hub) | Everyone | 100,000+ container images already available (Node, MongoDB, Nginx, Python, MySQLβ¦) |
| Private | Companies / Organizations | Their own applications cannot be publicly exposed β so they keep a private registry within the company |
Requirements:
| Requirement | Minimum |
|---|---|
| WSL version | 1.1.3.0 or higher |
| Windows version | 21H2 or higher |
| Processor | 64-bit |
| RAM | At least 4 GB |
| Virtualization | Must be Enabled in BIOS |
How to check WSL version:
wsl --versionHow to check virtualization: Task Manager β CPU tab (left side) β look for "Virtualization: Enabled"
βΉοΈ On latest Windows versions this is enabled by default.
Steps:
- Download Docker Desktop for Windows
- Double-click the installer β check "Add shortcut to Desktop"
β οΈ Restart required after install- Accept the Service Agreement
- Choose Recommended settings β Finish
- Sign in to Docker Hub or click "Continue without signing in"
- Download the
.dmg - Drag & drop Docker into the Applications folder
- Open via
Cmd + Spaceβ search "Docker" - Accept Terms & Service Agreement
- Recommended settings β Finish
- Enter your system password (required for making system changes)
- Docker whale icon π³ appears in the top menu bar
- Shows status: "Docker Desktop is running"
- Has: Check for Updates, Settings, About Docker Desktop (see version)
Docker supports .deb and .rpm packages across: Ubuntu, Debian, CentOS, Fedora, RHEL.
β οΈ Important architecture note:
- Ubuntu β supports all architectures (most versatile)
- RHEL (Red Hat) β
β οΈ officially supports ONLYs390x(IBM Z architecture)!amd64/x86_64are NOT supported β Red Hat has its own containerization tool: Podman Workaround: Install the CentOS package on RHEL (CentOS is Red-Hat-based)
RHEL / CentOS install (recommended: repository method):
# 1. Check your architecture
uname -m
# 2. Install yum-utils
sudo yum install -y yum-utils
# 3. Add the Docker repository
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 4. Install Docker Engine (latest)
sudo yum install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# 5. Start the Docker service
sudo systemctl start docker
sudo systemctl enable docker # start on boot
# 6. Check status
sudo systemctl status docker # should say: active (running)docker --version # or docker -v
docker ps # list running containersπ΄ CRITICAL: Docker must be in a RUNNING state to use any command. On Windows/Mac, launch Docker Desktop first.
We need a sample app to practice on. The course uses a React (Node.js) web app.
π‘ Not a limitation! You can use Java, Python, Go β anything. You just need a simple application.
# Verify Node is installed
node -v # e.g. v20.x.x
# Create a React app (β οΈ project name MUST be lowercase!)
npx create-react-app test-app
# Run it
cd test-app
npm startOpens automatically at β http://localhost:3000 (React's default port)
Stop it with β Ctrl + C
| File / Folder | Purpose |
|---|---|
package.json |
All dependencies + project info |
src/App.js |
The main file β the web page you see |
node_modules/ |
β All the packages needed to run the project |
# Delete node_modules, then try:
npm start
# β ERROR: command not found
# Fix it:
npm install # β node_modules comes back!
npm start # β
worksWhy does this matter?
node_modules is HUGE. We do not ship it during deployment. Instead we ship only the main files and run npm install on the target to regenerate it.
π― This is exactly why our Dockerfile will contain
RUN npm install.
Dockerfile = a simple text file with instructions to build an image.
Create a file named exactly Dockerfile (no extension) in your project folder.
π‘ Tip: Install the Docker extension in VS Code β you get autocomplete + hints while writing.
# 1οΈβ£ BASE IMAGE β what our app needs to run
FROM node:20
# 2οΈβ£ WORKING DIRECTORY β create a folder INSIDE the container
WORKDIR /myapp
# 3οΈβ£ COPY β copy all files from current dir β working dir
COPY . .
# 4οΈβ£ RUN β executed at BUILD time (creating the image)
RUN npm install
# 5οΈβ£ EXPOSE β document the port (optional)
EXPOSE 3000
# 6οΈβ£ CMD β executed at RUN time (when the container starts)
CMD ["npm", "start"]| Instruction | What it does | Key notes |
|---|---|---|
FROM node:20 |
The base image | node = image name, 20 = version. If you omit the version β it uses latest. This image is pulled from Docker Hub. |
WORKDIR /myapp |
Creates a folder inside the (empty) container and sets it as the working directory | Visualize: the container is an empty isolated environment β we make a room inside it for our app |
COPY . . |
Copy everything from the current directory β into the working directory | First . = source (host), second . = destination (container). You could also write COPY . /myapp |
RUN npm install |
Runs at BUILD time | β Why? Because we deleted node_modules! The container needs it, so we regenerate it while building the image β the image becomes a complete package |
EXPOSE 3000 |
Documents which port the app listens on | Optional β it does NOT actually publish the port |
CMD ["npm", "start"] |
Runs at RUN time (container start) |
RUN βββββββββΊ Executes while BUILDING the image
(npm install β bake dependencies in)
CMD βββββββββΊ Executes when RUNNING the container
(npm start β actually launch the app)
Remember: We're only building an image right now β we don't want to run the app during the build. That's why
npm startgoes inCMD, notRUN.
docker build .The . means: "the Dockerfile is present in the current directory."
π Common silly mistake:
Dockerfile cannot be emptyβ You forgot to SAVE the Dockerfile! PressCtrl + Sand rebuild.
Docker will show each step:
- Pull the
node:20base image - Create the
/myappfolder - Copy files
- Run
npm install - β¦
Output: writing image sha256:... β a long Image ID.
docker image ls
# or
docker images| Column | Meaning |
|---|---|
| REPOSITORY | Image name |
| TAG | Version |
| IMAGE ID | Short unique hash |
| CREATED | When built |
| SIZE |
# Get the image ID first
docker image ls
# Run it
docker run <IMAGE_ID>Your console freezes / hangs. This is normal!
Our app isn't a small script that finishes and exits β it's a website that keeps running forever, waiting for requests. It's running in the foreground.
Why? Visualize what happened:
Your Laptop (Host Machine)
ββββββββββββββββββββββββββββββββββββββ
β β
β Browser βββββββ BLOCKED β
β β β
β ββββββββββββββββββΌββββββββββββββ β
β β CONTAINER (isolated!) β β
β β β β
β β App listening on :3000 β
β β
β β (accessible INSIDE only) β β
β ββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββ
The app IS running on port 3000 β but inside the container's isolated environment. Your host browser can't reach it. β‘οΈ We need PORT BINDING.
Open a second terminal:
docker ps # see running containers
docker stop <CONTAINER_NAME> # stop it
docker ps # verify β now emptyπ² Fun fact: Docker auto-assigns random names to containers (e.g.
dreamy_wiles). You'll see a new quirky name every time!
docker run -p 3000:3000 <IMAGE_ID>docker run -p <HOST_PORT>:<CONTAINER_PORT> <IMAGE>
β β
outside access inside the container
Now visit http://localhost:3000 β β
Your app appears!
Running a container normally hangs your console. You can't run other commands or manage multiple containers.
docker run -d -p 3000:3000 <IMAGE_ID>Result: Docker prints a process ID and frees your terminal immediately. π
docker ps # verify: "Up 16 seconds"docker run -d --rm -p 3000:3000 --name "my-web-app" <IMAGE_ID>Why bother?
- You remember it (vs. random
dreamy_wiles) - Managing it becomes easy β
docker stop my-web-app
Problem: Every stopped container still exists in the background. You must manually docker rm them. Tedious!
Solution:
docker run -d --rm -p 3000:3000 <IMAGE_ID>
--rm= "when this container stops, automatically remove it" β saves you an extra step.
docker stop <name>
docker ps -a # β
EMPTY β stopped AND removed in one step!docker ps # RUNNING containers only
docker ps -a # ALL containers (running + stopped + created)
docker stop <name|id> # stop a container
docker start <name|id> # restart a stopped container
docker rm <name1> <name2> # remove container(s) β can pass multiple!
docker logs <name> # π see WHY a container crashed
docker inspect <name> # full details (incl. IP address!)
β οΈ docker psvsdocker ps -a:psshows only running. If Docker Desktop shows many containers butdocker psshows one β usedocker ps -ato see them all (Created / Exited / Running states).
If you're on Windows/Mac, the GUI makes life easier:
- See all containers and their states
- Restart stopped containers with one click
- Delete containers easily
- View CPU usage, Memory usage β acts as a quick monitoring tool
Would you tell a teammate "use image 2f02e..."? Terrible. Let's give images meaningful names.
docker build -t my-web-app:01 .
β β β β
β name version β
β Dockerfile location
"-t" = TAGFormat: name:version β exactly like node:20 (name node, version 20)!
docker image ls
# REPOSITORY TAG IMAGE ID
# my-web-app 01 2f02e8b... β same ID, nice name β
docker rmi my-web-app:02 # rmi = ReMove Image| Command | Removes |
|---|---|
docker rm |
Container |
docker rmi |
Image |
π‘ With
rmiyou don't need the Image ID β just use the name:tag.
docker tag my-web-app:02 username/web-app-demo:02
# β OLD name β NEW nameScenario: You fixed a bug / added a feature / made a correction in your source code.
If only your SOURCE CODE changes β you do NOT need to change the Dockerfile. Just rebuild the image.
# 1. Edit your code (e.g. src/App.js)
# 2. SAVE it! β οΈ
# 3. Rebuild with a NEW version tag
docker build -t my-web-app:02 .
# 4. Check
docker image ls
# my-web-app 02 <NEW UNIQUE IMAGE ID> β different ID! β
# my-web-app 01 <old id>
# 5. Run the new version
docker run -d --rm --name my-web-app -p 3001:3000 my-web-app:02π‘ Notice: instead of the Image ID, we can now use the
name:tagβ much nicer!
# New version on port 3001
docker run -d --rm --name my-web-app -p 3001:3000 my-web-app:02
# OLD version on port 3002 (different port + different name!)
docker run -d --rm --name my-web-app-01 -p 3002:3000 my-web-app:01Now open two browser tabs:
localhost:3001β new content β¨localhost:3002β old content
β Compare / verify changes side-by-side. This is the beauty of containerization!
| Error | Cause | Fix |
|---|---|---|
port is already allocated |
Port 3000 already in use by another container | Use a different host port (-p 3001:3000) |
container name already in use |
You reused a --name |
Give a unique name |
docker run -d --rm -p 3000:3000 <img> # β
docker run -d --rm -p 3001:3000 <img> # β
docker run -d --rm -p 3002:3000 <img> # β
docker psAll three containers listen on port 3000 internally! How?!
π‘ Because they're ISOLATED. Each listens on 3000 inside its own container. Container A has nothing to do with Container B. They don't affect each other. Only the host ports (3000/3001/3002) must differ.
This is a kind of load balancing β and a great answer if asked in interviews! π―
So far we built custom images (our own). Now: pre-defined images.
"Build and Ship any Application Anywhere"
- 100,000+ container images already available
- Publicly available β you don't need to build them
- Popular companies/software publish their own official images
Common pre-defined images: node Β· python Β· mysql Β· postgres Β· mongodb Β· nginx Β· apache Β· redis
Each image (e.g. python) has many versions/tags: 3.13, 3.12, 3.11, 3.8β¦
docker pull python # no version β pulls "latest"
docker pull python:3.12 # specific version
docker image ls # verifydocker run python:latest
docker ps # ...nothing! It ran and exited immediately.Why? Python alone has nothing to do β no long-running process.
What is Nginx? A web server. It runs continuously in the background, waiting for incoming requests from browsers. When a browser requests a page, Nginx accepts the request and serves the web page.
π Nginx listens on port 80 by default (our React app used 3000).
docker pull nginx
docker run -p 8080:80 nginx:latestOpen http://localhost:8080 β "Welcome to nginx!" β
Watch the console β it keeps running continuously, logging every request.
Scenario: Your program takes user input (e.g. a Python script asking for two numbers).
# my_app.py
print("Program to sum two numbers")
num1 = int(input("Enter the first number: "))
num2 = int(input("Enter the second number: "))
print("Sum of two numbers is", num1 + num2)FROM python
WORKDIR /myapp
COPY my_app.py .
CMD ["python", "my_app.py"]docker build .
docker run <IMAGE_ID>
# Output:
# Program to sum two numbers
# Enter the first number: β π₯ program stops/fails here!Why? The container has no interactive terminal β it can't accept your keyboard input.
docker run -it <IMAGE_ID>π§ Memory trick:
-it= Interactive Terminal
Now:
Program to sum two numbers
Enter the first number: 10 β you CAN type! β
Enter the second number: 20
Sum of two numbers is 30
Once the program finishes β the container stops automatically.
Goal: Share our image with teammates / another server / the world.
| Feature | Free tier |
|---|---|
| Public repositories | βΎοΈ Unlimited β anyone in the world can download & use |
| Private repositories | 1 β only you + your authorized team members |
| Good for | Education, individual developers, open-source community |
1οΈβ£ Create a repository on Docker Hub
- Sign up β verify email
- Click Create a Repository
- Name:
web-app-demoΒ· Description:docker learning - Visibility: Public (nothing sensitive here)
Your image name becomes: <username>/web-app-demo
2οΈβ£ Login from terminal
docker login
# Enter Docker ID + password
# β "Login Succeeded" β
Login is needed because you're pushing into your space on Docker Hub.
3οΈβ£ Build with the CORRECT name
docker push <username>/web-app-demo:01
# β ERROR: "An image does not exist locally"
β οΈ Why the error? The image name must exactly match<username>/<repo-name>! Our local image is calledmy-web-appβ that's not the same thing.
Two ways to fix:
# Option A: Rebuild with the right name
docker build -t <username>/web-app-demo:01 .
# Option B: Rename the EXISTING image (faster!)
docker tag my-web-app:02 <username>/web-app-demo:024οΈβ£ Push!
docker push <username>/web-app-demo:01Refresh Docker Hub β your image appears under the Tags tab. π
When pushing v2 after v1, you'll see: Layer already exists.
Docker is smart β it doesn't re-store things that already exist. It links to existing layers instead. This preserves & optimizes storage space.
docker pull <username>/web-app-demo:02
docker images
docker run -p 3000:3000 <username>/web-app-demo:02Open localhost:3000 β your app runs on a completely different machine! π
β No Node.js install. No setup. No configuration. Just install Docker β pull β run. Deployment in minimal time with minimal effort. Compatibility issues β nearly zero.
Scenario: A Python program that saves usernames into a file.
# my_app.py
user_name = input("Enter your name to store in a file (or Enter to proceed): ")
if user_name:
file = open("user_info.txt", "a") # "a" = APPEND
file.write(user_name + "\n")
file.close()
show = input("Do you want to see all user names in the file? (y/n): ")
if show == "y":
file = open("user_info.txt", "r") # "r" = READ
for line in file:
print(line)Running it LOCALLY (physical machine):
| Run | Input | Output |
|---|---|---|
| 1st | Paul |
Paul |
| 2nd | Raju |
Paul, Raju β |
| 3rd | Shyam |
Paul, Raju, Shyam β |
Data persists! β
(The user_info.txt file survives between runs.)
docker build .
docker run -it --rm <IMAGE_ID>| Run | Input | Output |
|---|---|---|
| 1st | Shyam |
Shyam |
| 2nd | Raju |
Raju only! β Where did Shyam go?! |
Container starts β file created INSIDE container
Container stops β π₯ container removed (--rm)
π₯ THE FILE IS GONE TOO!
"No container remains, no file remains."
The file lives inside the container's isolated filesystem. When the container dies, the data dies with it.
docker run -it --rm -v my-volume:/myapp <IMAGE_ID>
β β β
β β βββ path INSIDE the container
β ββββββββββββββ volume name (your choice)
"-v" = VOLUME
β οΈ CRITICAL: The path after:must match the path inside the container where your file is created. Our Dockerfile hasWORKDIR /myappβ our Python program runs there β the file is generated there β so we mount/myapp.
| Run | Input | Output |
|---|---|---|
| 1st | Raju |
Raju |
| 2nd | Shyam |
Raju, Shyam β |
| 3rd | Baburao |
Raju, Shyam, Baburao β |
Data persists across container restarts! π
docker volume --help # π‘ see ALL options for any command!
docker volume ls # list volumes
docker volume inspect <name> # details: driver, created-at, scope, MOUNT POINT
docker volume rm <name> # delete a volumeExample docker volume ls output:
DRIVER VOLUME NAME
local my-volume β "local" = present locally, Docker handles it
Scenario: Your program depends on an external file that YOU want to edit from your physical machine.
# reads a list of servers from a file
file = open("servers.txt", "r")
for line in file:
print(line)servers.txt (on your physical machine):
server1
server2
server3
Once this is inside a container, you cannot edit that file β it's locked inside the container's isolated environment. Every code change would mean rebuilding the image. Painful.
Link (bind) a file/folder on your PHYSICAL machine β a file/folder INSIDE the container. Edit locally β the change is instantly visible in the container. π₯
docker run -it --rm -v ./servers.txt:/myapp/servers.txt <IMAGE_ID>
β β β
β β βββ path INSIDE container
β ββββββββββββββββββββ path on PHYSICAL machine
β (relative OR absolute)
"-v" again!π‘ Read it as:
HOST_PATH : CONTAINER_PATHYou can bind a specific file (as above) or an entire folder.
Run 1 β server1, server2, server3, server4
β (edit servers.txt locally, add "server5", SAVE)
Run 2 β server1 ... server4, server5 β
LIVE UPDATE β no rebuild!
- π Code depends on an external/config file β keep it editable on the host
- π¨βπ» During the DEVELOPMENT phase β you're constantly changing code.
Without bind mounts: every tiny change = rebuild the image. π© With bind mounts: build once, mount your local files β changes reflect instantly β verify β build the final image at the end. π
| πΎ Volume | π Bind Mount | |
|---|---|---|
| Syntax | -v my-volume:/myapp |
-v ./file.txt:/myapp/file.txt |
| Managed by | Docker | You (it's your physical filesystem) |
Shows in docker volume ls |
β Yes | β No (no volume is created!) |
| Use for | Persisting data the app generates | Live-editing files during development |
| Location | Docker's internal storage | Anywhere on your machine |
π Key check: After a bind mount, run
docker volume lsβ nothing there. Because we mounted a physical location, no Docker volume was needed!
Just like .gitignore β but for Docker.
Your Dockerfile has COPY . . β EVERY file in the project gets copied into the image, including:
Dockerfileitself (you need it, but not inside the image!).gitignore.git/folder- Other junk you don't want bloating your image
Create a file called .dockerignore in the same location where you build the image:
# Exact file names
Dockerfile
.gitignore
README.md
# Wildcard patterns
.git*
node_modules
*.log
Now docker build β these files will NOT go into your image. β
Your containerized app doesn't live alone. Three types of communication:
Example: A Python app fetching a random cat fact from an API.
import requests
url = "https://catfact.ninja/fact"
response = requests.get(url)
print(response.json()["fact"])β First attempt fails:
ModuleNotFoundError: No module named 'requests'
Why? requests is an external package β not part of Python by default. The container doesn't have it!
β
Fix β add a RUN step to the Dockerfile:
FROM python
WORKDIR /myapp
COPY api_demo.py .
RUN pip install requests # β install the external package
CMD ["python", "api_demo.py"]Rebuild β run β random cat fact prints! π± API communication works perfectly.
π Lesson learned: Whenever your Python code
imports an external package, you need an additionalRUN pip install <package>step in the Dockerfile.
Example: Python app connecting to MySQL running on your physical machine.
import mysql.connector
def create_connection():
return mysql.connector.connect(
host="localhost", # β β THIS IS THE PROBLEM
user="root",
password="root",
database="user_info"
)Dockerfile:
FROM python
WORKDIR /myapp
COPY sql_demo.py .
RUN pip install mysql-connector-python
CMD ["python", "sql_demo.py"]β Running it:
Can't connect to MySQL server on 'localhost'
Cannot assign requested address
Inside the container,
localhostmeans THE CONTAINER ITSELF β not your laptop! The container is an isolated environment with no relation to the host OS. How would it know what "localhost" means to you, or where your database lives?
host="host.docker.internal" # β
instead of "localhost"π
host.docker.internaltells Docker: "target the HOST MACHINE where Docker is installed."
Rebuild β run β β
Connected! Now you can INSERT and SELECT from the container, and see the changes live in your local MySQL Workbench. π₯
Scenario: Both the Python app AND MySQL run in separate containers.
# 1. Pull & run MySQL container FIRST
docker pull mysql
docker run -d --name mysql-db \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=user_info \
mysql
β οΈ Without-e MYSQL_ROOT_PASSWORD, the container immediately STOPS! Usedocker logs mysql-dbto find out why β "You need to specify one of the following environment variablesβ¦"
| Environment Variable | Purpose |
|---|---|
MYSQL_ROOT_PASSWORD |
Required. Sets the root password |
MYSQL_DATABASE |
Auto-creates a database when the image starts up |
# 2. Find the container's IP address π©
docker inspect mysql-db
# β Networks β IPAddress: 172.17.0.2
# 3. Hardcode that IP into your Python code
host = "172.17.0.2"
# 4. NOW build the Python image
docker build .To build the Python image, I must FIRST build and run MySQL, then look up its IP. Every. Single. Time.
That's backwards! Images should be ready to go β we should only need to change the command, not rebuild.
# 1. Create a network
docker network create my-net
# 2. Verify
docker network ls
# NAME DRIVER
# my-net bridge β "bridge" type network created β
# 3. Run MySQL ON that network
docker run -d --name mysql-db --network my-net \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=user_info \
mysql
# 4. In your Python code β use the CONTAINER NAME as host!
# host = "mysql-db"
# 5. Build & run Python ON THE SAME network
docker build .
docker run -it --rm --network my-net <IMAGE_ID>Because both containers are on the same network, Docker automatically resolves the container name β target IP for you.
You never need to look up an IP again! Just remember your container's name.
When connecting Python β MySQL you may hit:
RuntimeError: ... cryptography package is required ...
Why? It's needed for authentication & connection with the database.
Fix:
RUN pip install mysql-connector-python
RUN pip install cryptography # β add thisdocker stop mysql-db # container stops (but still EXISTS β "Exited" state)
docker start mysql-db # bring it back upβ The data is STILL THERE! As long as you don't remove (
docker rm) the container, your data is preserved.
Docker Compose is a config file (based on YAML) used to manage your containers.
Officially it says "to manage multiple containers" β but there's no such limitation: you can use it for a single container too. It just shines brightest with many containers.
Look at the command we needed for a single MySQL container:
docker run -d --name mysql-db --network my-net \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=user_info \
-v my-volume:/var/lib/mysql \
mysql:latestThat's a 3-4 line monster. And we haven't even added volumes and mounts for the other containers!
Why is this bad?
- β You must retype the same huge command repeatedly
- β Working in a team? Your teammate must type the entire thing too
- β Repetitive β error-prone β not an efficient way to work
β‘οΈ Docker Compose solves exactly this.
Create: docker-compose.yml (or .yaml)
π‘ Tip: Install a Docker Compose plugin/extension in your editor (VS Code / PyCharm) β you get autocomplete + auto-formatting. Life gets much easier.
services: # β REQUIRED β the main section
mysql-db: # service name (your choice)
image: mysql:latest # which image to use
container_name: mysql-db # optional (Docker auto-names otherwise)
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=user_info
β οΈ Syntax gotcha: environment variable values take NO double quotes! Check the docs βMYSQL_ROOT_PASSWORD=root, not"root".
docker compose up # build/pull + start everything
docker compose up -d # detached (background) mode
docker compose down # stop AND remove containersπ Location matters: Run the command from the folder where
docker-compose.ymllives.
- Pulls the image (if not present)
- Runs the container
- Applies all your environment variables, names, portsβ¦
Stopping mysql-db ...
Removing mysql-db ...
Removing network ...
π It stops AND removes the container β you don't need
--rmanymore!
β οΈ IMPORTANT: Docker Compose does NOT replace your Dockerfile! When you build custom images, you still need the Dockerfile. Compose just adds abuild:step.
services:
# βββββ SERVICE 1: MySQL (pre-defined image) βββββ
mysql-db:
image: mysql:latest
container_name: mysql-db
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=user_info
healthcheck: # β see below!
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
# βββββ SERVICE 2: Python (custom image) βββββ
my-python-app:
build: ./ # β path to the Dockerfile
container_name: my-py-app
stdin_open: true # β = the "-i" flag
tty: true # β = the "-t" flag
depends_on:
mysql-db:
condition: service_healthy # β wait until HEALTHY| Directive | Equivalent CLI flag | Purpose |
|---|---|---|
image: |
docker run <image> |
Use a pre-defined image |
build: ./ |
docker build . |
Build a custom image from a Dockerfile. Relative paths allowed! (./app/ if the Dockerfile is in a subfolder) |
container_name: |
--name |
Name the container |
environment: |
-e |
Set environment variables |
ports: |
-p |
Port binding (- 8080:3000) |
volumes: |
-v |
Volumes & bind mounts |
networks: |
--network |
Attach to a network |
stdin_open: true |
-i |
Keep STDIN open |
tty: true |
-t |
Allocate a terminal |
depends_on: |
(none!) | Start order + wait conditions |
healthcheck: |
(none!) | Check if a service is actually ready |
Symptom: You run docker compose up and the Python container crashes:
Can't connect to MySQL server on 'mysql-db'
β¦even though the config is perfect!
Compose starts BOTH containers together
β
βββΊ MySQL: starts... loading... loading... (SLOW! ~20s)
β
βββΊ Python: starts instantly β tries to connect β π₯ MySQL isn't ready yet!
MySQL takes time to come up. Python arrives in between and tries to connect β but MySQL isn't ready for connections yet.
depends_on:
- mysql-db # β only waits for the container to STARTStill fails! Why?
π "Container is UP" β "Ready for connections"!
depends_onalone only knows the container started. It has no idea whether MySQL is actually accepting connections.
mysql-db:
image: mysql:latest
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s # keep trying for 20 seconds
retries: 10 # retry 10 times if it fails
my-python-app:
build: ./
depends_on:
mysql-db:
condition: service_healthy # β wait for HEALTHY, not just "started"What healthcheck does:
"Declares a check that runs to determine whether or not the service containers are healthy."
It literally tries to connect via command line (mysqladmin ping). Once the connection succeeds β the health status flips to healthy β only then does Python start. β
Result: After ~20 seconds β MySQL becomes "ready for connections" β Python launches β connects successfully! π
Sometimes you want interactive mode for just one container:
# Start MySQL in the background
docker compose run -d mysql-db
# Now run the Python app interactively (foreground)
docker compose run my-python-appUse the service name from your YAML file. Now you get a working interactive terminal! β
Question: In our Compose file, our Python code says host = "mysql-db" (a container name). But we never created a network! How do they connect?!
All services inside a SINGLE
docker-compose.ymlfile automatically become part of ONE network. Docker Compose creates the network by itself and adds all services to it.β‘οΈ You don't need to create a network manually!
Proof:
docker network ls
# NAME DRIVER
# python-demo-project_default bridge β π Compose made this automatically!
# β folder name + "_default"Live proof: Delete that network β run docker compose up again β Compose recreates it automatically! β¨
services:
mysql-db:
image: mysql:latest
networks: # β attach the service
- my-network
my-python-app:
build: ./
networks: # β attach this one too
- my-network
networks: # β top-level section (same indent as "services")
my-network: # β define it hereπ Compose adds a prefix:
python-demo-project_my-network
docker compose down # stops + removes CONTAINERS only
# β οΈ networks & volumes STAY
docker compose down -v # β ALSO removes networks & volumes
# β "Removing network ..." β
β οΈ On the first run, Compose builds/pulls images. On subsequent runs, it reuses the old image β even if your code changed!
docker compose up --build # β force a rebuildservices:
my-web-app:
build: ./ # Dockerfile in same folder
container_name: my-web-app
ports:
- 8080:3000 # host:container (3000 = React default)docker compose up
# β builds the image, spins up the containerOpen localhost:8080 β β
App is live!
| β Benefit | Explanation |
|---|---|
| Config-file based | YAML syntax β simple and easy |
| Manages multiple containers | All in one file |
| No repetitive commands | Kills those giant docker run one-liners |
| Auto-network | All services land on a common network automatically |
| Auto-volume | Creates volumes by default |
| Auto-cleanup | down stops and removes containers (no --rm needed) |
| Team-friendly | Share one file β teammate runs docker compose up |
docker build . # build from Dockerfile in current dir
docker build -t name:tag . # build WITH a name & version
docker images # list images
docker image ls # (same thing)
docker pull <image> # download from Docker Hub
docker pull <image>:<version> # specific version
docker push <user>/<repo>:<tag> # upload to Docker Hub
docker tag <old> <new> # rename an image
docker rmi <image> # remove an image
docker login # authenticate to Docker Hubdocker run <image> # run (foreground)
docker run -d <image> # DETACHED (background)
docker run -it <image> # INTERACTIVE TERMINAL
docker run -p 3000:3000 <image> # PORT binding (host:container)
docker run --rm <image> # auto-REMOVE on stop
docker run --name my-app <image> # custom NAME
docker run --network my-net <image> # attach to a network
docker run -e KEY=value <image> # ENVIRONMENT variable
docker run -v vol:/path <image> # VOLUME
docker run -v ./f.txt:/app/f.txt <img> # BIND MOUNT
# Typical full command:
docker run -d --rm --name my-app -p 3000:3000 my-image:01docker ps # RUNNING containers
docker ps -a # ALL containers
docker stop <name> # stop
docker start <name> # restart a stopped container
docker rm <name1> <name2> # remove container(s)
docker logs <name> # π debug: see why it crashed
docker inspect <name> # full details (incl. IP)docker volume ls # list volumes
docker volume inspect <name> # details + mount point
docker volume rm <name> # delete
docker volume --help # π‘ all optionsdocker network create my-net # create
docker network ls # list
docker network rm <name|id> # remove
docker network --help # all optionsdocker compose up # start all services
docker compose up -d # detached
docker compose up --build # force rebuild
docker compose down # stop + remove containers
docker compose down -v # ALSO remove networks & volumes
docker compose run <service> # run ONE service
docker compose run -d <service> # ...in backgroundπ‘ PRO TIP: Stuck on any command? Just append
--help:docker volume --helpΒ·docker network --helpΒ·docker compose --help
Q1. What is Docker?
A containerization platform / tool used to create and manage containers. It provides the ability to run an application in an isolated environment called a container, and helps with developing, packaging, shipping, and running applications.
Q2. Why do we need Docker?
To solve compatibility issues β the classic "it works on my machine" problem. Differences in OS, settings, libraries, dependencies, missing files, missing env variables, and version mismatches break apps across machines. Docker packages the app with everything it needs so it runs identically anywhere.
Q3. Docker vs Virtual Machine?
| Docker | VM | |
|---|---|---|
| OS impact | Low | High |
| Speed | Very fast | Slow |
| Disk usage | Low | High |
| Resources | Only what's needed; rest free | Totally divided, lots wasted |
| Sharing | Easy | Challenging |
| Encapsulates | App only | Whole machine (+ Guest OS) |
Verdict: For encapsulating just an app, Docker containers are the better approach.
Q4. Dockerfile vs Image vs Container?
- Dockerfile β a simple text file with instructions to build an image
- Image β a single file with all dependencies + libraries needed to run the program
- Container β the running instance created when you run an image
Flow: Dockerfile β (build) β Image β (run) β Container
One image β many containers.
Q5. β How can 3 containers all listen on port 3000 simultaneously?
Because containers are ISOLATED. Each app listens on port 3000 inside its own container. Container A has no connection to Container B β they can't see each other and don't affect each other.
Only the host-side ports must be unique:
docker run -p 3000:3000 img # β
docker run -p 3001:3000 img # β
docker run -p 3002:3000 img # β
This is effectively a kind of load balancing.
Q6. RUN vs CMD?
RUNβ executes at BUILD time (while creating the image). E.g.RUN npm installCMDβ executes at RUN time (when the container starts). E.g.CMD ["npm", "start"]
We only want to build an image β not run the app during the build. Hence npm start belongs in CMD.
Q7. Why does my data disappear when the container stops?
Because the file lives inside the container's isolated filesystem. Container dies β filesystem dies with it.
Fix: Use a Docker Volume β a shared directory managed by Docker that lives outside the container:
docker run -v my-volume:/myapp <image>Q8. Volume vs Bind Mount?
| Volume | Bind Mount | |
|---|---|---|
| Syntax | -v my-vol:/myapp |
-v ./file.txt:/app/file.txt |
| Managed by | Docker | You (physical filesystem) |
Appears in docker volume ls? |
β Yes | β No |
| Use for | Persisting app-generated data | Live-editing during development |
Q9. Why can't my container connect to `localhost`?
Inside a container, localhost means the container itself β not your host machine! The container is isolated and has no relation to the host OS.
Fix: use host.docker.internal β it targets the host machine where Docker is installed.
Q10. β How do two containers talk to each other?
Put them on the same Docker network, then reference the other container by its NAME (not IP):
docker network create my-net
docker run -d --name mysql-db --network my-net mysql
docker run -it --network my-net my-python-apphost = "mysql-db" # β
container name β Docker resolves the IP automaticallyWithout a network: you'd have to docker inspect to find the IP, hardcode it, and rebuild every time. π©
Q11. What is Docker Compose and why use it?
A YAML-based config file to manage (multiple) containers. It eliminates giant repetitive docker run commands, is team-friendly (share one file), and automatically creates a common network for all its services.
Q12. β My Compose app fails: "Can't connect to MySQL" β why?
A race condition. Compose starts both containers together, but MySQL is slow to boot. Python starts instantly and tries to connect before MySQL is ready.
depends_on alone is NOT enough β it only waits for the container to start, not to be ready for connections.
Fix: combine healthcheck + depends_on: condition: service_healthy:
mysql-db:
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
my-python-app:
depends_on:
mysql-db:
condition: service_healthy # β wait for HEALTHYQ13. Do Compose services need a manual network?
No! All services in a single docker-compose.yml automatically join one network that Compose creates itself (named <folder>_default). That's why containers can reference each other by name without any networks: block.
Q14. Registry vs Repository?
- Registry = the whole platform/common place (e.g. Docker Hub)
- Repository = one collection inside it holding different versions/tags of the same image (e.g. the
noderepo withnode:18,node:20,node:21)
Q15. What does `--rm` do?
Automatically removes the container when it stops. Without it, stopped containers linger in the background (visible via docker ps -a) and you must manually docker rm them.
ββββββββββββββββ
β Your Code β
ββββββββ¬ββββββββ
β write instructions
βΌ
ββββββββββββββββ
β Dockerfile β FROM Β· WORKDIR Β· COPY Β· RUN Β· EXPOSE Β· CMD
ββββββββ¬ββββββββ
β docker build -t app:01 .
βΌ
ββββββββββββββββ docker push
β Image β βββββββββββββββββββββββΊ βοΈ Docker Hub
ββββββββ¬ββββββββ βββββββββββββββββββββββ β
β docker pull β
β docker run -d --rm -p 3000:3000 β
βΌ βΌ
ββββββββββββββββ βββββββββββββββββββ
β Container β β Any machine, β
β (isolated) β β anywhere π β
ββββββββ¬ββββββββ βββββββββββββββββββ
β
ββββΊ πΎ Volumes β persist data
ββββΊ π Bind Mounts β live-edit files
ββββΊ π Networks β container β container
ββββΊ πΌ Compose β manage them ALL in one YAML
Quick revision order: Sections 6 (components) β 9 (Dockerfile) β 25 (cheat sheet) β 26 (interview Q&A)