If Docker ever felt like a long list of commands with no mental model, this post is for you.
Here’s the thing: Docker isn’t complicated. It’s just layered.
Once you see how the layers fit together, everything clicks.
Let’s break it down from the ground up.
1️⃣ Running Containers with docker run
The most basic Docker command:
docker run redis
What actually happens?
Docker checks if the image exists locally
If not, it pulls it from Docker Hub
It creates a container
It runs the default command defined in the image
By default, Docker uses the latest tag.
To run a specific version:
docker run redis:4.0
Why this matters:
latest is not predictable
Production systems should always use explicit tags
2️⃣ Interactive vs Non-Interactive Containers
Containers run in non-interactive mode by default.
That’s why this behaves oddly:
docker run kodekloud/simple-prompt-docker
To accept user input, you need both flags:
docker run -it kodekloud/simple-prompt-docker
-i → keep STDIN open
-t → allocate a pseudo terminal
Miss one, and your app won’t behave like a real CLI program.
3️⃣ Port Mapping: Making Containers Accessible
Containers have their own internal network.
If your app listens on port 5000 inside the container, it’s invisible from outside unless you map it.
docker run -p 80:5000 kodekloud/webapp
Now traffic flows like this:
Host:80 → Container:5000
Run multiple instances easily:
docker run -p 8000:5000 app
docker run -p 8001:5000 app
Same app. Different host ports.
4️⃣ Data Persistence with Volumes
Containers are ephemeral.
Delete the container → data is gone.
Example problem:
docker run mysql
docker rm mysql
💥 All data lost.
Solution: volumes.
docker run -v /opt/datadir:/var/lib/mysql mysql
Now:
Data lives on the host
Containers can be recreated safely
5️⃣ Inspecting Containers Like a Pro
See running containers:
docker ps
Deep inspection:
docker inspect
This shows:
Entrypoint
Environment variables
Mounts
Network configuration
Runtime state
When something breaks, docker inspect usually tells you why.
6️⃣ Logs and Detached Mode
Run containers in the background:
docker run -d nginx
Check logs anytime:
docker logs <container_name>
Detached mode is essential for:
Web servers
Databases
CI tools like Jenkins
7️⃣ CMD vs ENTRYPOINT (This Confuses Everyone)
Short version:
CMD → default arguments
ENTRYPOINT → fixed executable
Best practice pattern:
FROM ubuntu
ENTRYPOINT ["sleep"]
CMD ["5"]
Usage:
docker run ubuntu-sleeper # sleep 5
docker run ubuntu-sleeper 10 # sleep 10
ENTRYPOINT stays fixed.
CMD stays flexible.
8️⃣ Building Your Own Docker Images
A Dockerfile is just documented automation.
Example:
FROM ubuntu
RUN apt-get update && apt-get install -y python python-pip
RUN pip install flask
COPY app.py /opt/app.py
ENTRYPOINT ["flask", "run", "--host=0.0.0.0", "--app", "/opt/app.py"]
Each instruction:
Creates a layer
Is cached
Rebuilt only when it changes
That’s why Docker builds are fast when written well.
9️⃣ Environment Variables (Config Without Code Changes)
Hardcoding config is fragile.
Better approach:
color = os.environ.get("APP_COLOR")
Run with:
docker run -e APP_COLOR=blue simple-webapp-color
Same image.
Different behavior.
Zero code changes.
🔟 Docker Compose: When One Container Isn’t Enough
Running multiple containers manually doesn’t scale.
Compose fixes that.
version: "3"
services:
redis:
image: redis
db:
image: postgres:9.4
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
vote:
image: voting-app
ports:
- "5000:80"
result:
image: result-app
ports:
- "5001:80"
worker:
image: worker-app
Start everything with:
docker-compose up
What Compose gives you:
Automatic networking
Service name DNS
One-command startup
Cleaner architecture
Final Takeaway
Docker isn’t about commands.
It’s about control.
Control over:
Environments
Dependencies
Networking
Data
Deployment behavior
Once that mental model clicks, Docker stops being scary and starts being obvious.
👋 If this helped:
Save it
Share it
Try the commands locally
That’s how Docker really sticks.
Top comments (0)