Prabal Tripathi
Lets Make Docker's Duck
Just wanted to put some sort of clickbait title—sorry guys, I respect Docker! And today, you are going to understand why I respect what I respect.
Why Docker?
Docker serves as the underlying layer for container orchestration, running applications and scripts in isolated environments, and executing projects and auxiliary services in one go without any hassle.
What is Containerization?
Containerization is a term that existed before Docker. It has been used widely in the industry to package an application and all its dependencies to distribute it, deploy it, and run it independently of the OS it is being run on. It requires a container runtime on that machine, and currently, the most prominent one is Docker.
Containers are a lifesaver when dependencies grow over time, setup issues arise, and different developers have different environments. It is always better to package/containerize your application.
A simple command to run a Mongo container is:
docker run -p 27017:27017 mongo
The Docker Architecture
Let's break down things from the command itself. Whenever you run a Docker command on your computer, it involves three main parts:
- The CLI
- Docker Daemon (Docker Engine)
- Docker Registry
What I typed above is the Docker CLI. It communicates with the Docker Daemon using the HTTP protocol and Unix sockets, telling it to pull an image and start running it. (The CLI is not the only way to talk to a Docker engine; you can also hit the Docker REST API).
All the heavy lifting and operations are taken care of by the Daemon. The Docker Registry is the place where these images reside. Just like npm is a registry for JS libraries, DockerHub (among others) is a library for Docker images!
Understanding Port Forwarding (-p)
What does the -p flag do? This is the port forwarding flag.
- On the left, we define the machine’s port.
- On the right, we define the container’s port (the isolated environment, completely separated from the host machine).
So, docker run -p 27017:27017 mongo pulls the Mongo image from the registry (if not present locally) and exposes its internal port 27017 to our host machine's port 27017. Now, you can access the Mongo database instance at localhost:27017. You can spin up multiple isolated Mongo containers like this—you just have to forward requests to different host machine ports!
Fun Fact & History: Docker is a YC-backed company started in 2014 by the founders of Dotcloud. Dotcloud was doing well, but they wanted to do something massive in the software industry. That is where Solomon Hykes planned to start Docker. Soon, Docker became mainstream and the industry leader for container runtimes.
Another fun fact: Docker is natively built for Linux. On Mac and Windows (via Docker Desktop), it actually spins up a lightweight Linux VM under the hood, inside which Docker runs!
Essential Docker Commands & Concepts
(Insert command cheat sheet image here)
Images vs. Containers
An app packaged with all its dependencies is an Image. An image in execution is a Container. For example, the previous command spun up a Docker container of the Mongo image.
Key Commands to Know:
docker images: Lets you see all the images stored locally on your machine.docker ps: Lets you see all the active, running containers.docker run: Spins up a container from an image.docker build: Helps you build your own custom Docker image.docker kill <container_id/name>: Kills/stops a running container.docker exec -it <container_id> /bin/bash: If you want to run anything inside the Docker container, you use this to "enter" it and get shell access!
The Detached Flag (-d): Apart from -p, you will often see -d. This runs the container in the background (detached mode). It will just return the container ID and leave your terminal free.
(Insert Normal Run vs. Detached Run image here)
Building Your Own Docker Image
Building your own Docker image sounds fun, so how do we do it? Let's understand the Dockerfile. This is a file written with basic instructions to build an image.
For example, if you have a simple Node server, how would you explain setting it up to a friend?
- Bhai node install karle ➔
FROM node:22 - Directory banale app naam rakhle ➔
WORKDIR /app - Mera project usme copy paste karle ➔
COPY . . - npm install run karde ➔
RUN npm install - Expose karde machine ka port 3000 ➔
EXPOSE 3000 - Aur run karde: node index.js ➔
CMD ["node", "index.js"]
Just like .gitignore, you probably want to ignore the node_modules folder during the build. You can put that in a .dockerignore file.
Once your Dockerfile is ready in your root directory, run:
docker build -t node-server .
(Note: -t tags the image with a name of your choice).
Every image is built on top of a base image (like node in our example). Often, alpine versions of base images are chosen because they are incredibly lightweight.
The commands written with RUN execute while the image is being built. The command written in CMD executes when the image is run (i.e., when someone types docker run).
To run your newly built image:
docker run -p 3000:3000 node-server
(Pro-tip: You can pass environment variables while running your image using the -e flag. In the Dockerfile, variables are declared with ENV and used with a $ sign).
Want to share your image? Push it to Docker Hub!
docker push your_username/your_reponame:tagname
The Magic of Docker Layers
Layers are a fundamental part of Docker's architecture, making it efficient, fast, and portable. A Docker image is essentially a series of layers, each representing a set of differences from the previous one.
When we wrote our Dockerfile, we were actually defining different layers:
- Base Layer: The starting point (e.g., Ubuntu, Alpine).
- Instruction Layers: Each command (
RUN,COPY) creates a new layer by modifying the filesystem. - Reusable & Shareable: Layers are cached! If multiple images share common instructions, they reuse the same layers, saving storage and speeding up builds.
- Immutable: Once created, a layer cannot be changed. Any modification simply creates a new layer on top.
(Insert Layers Architecture image here)
Why does this matter?
If you change your code, but not your dependencies, Docker reuses the cached npm install layer! Since dependencies don't change often, this caching mechanism saves a massive amount of build time.
Networks and Volumes
When you have multiple containers (e.g., a Node backend and a Mongo database), two concepts become vital: Networks and Volumes.
(Insert Networks and Volumes diagram here)
Volumes: Persisting Data
If you restart a Mongo Docker container, your data disappears. Containers are transitory by default. To persist data across restarts, we use volumes:
docker run -v volume_database:/data/db -p 27017:27017 mongo
Networks: Container Communication
By default, Docker containers are isolated and cannot talk to each other. localhost inside a container refers to its own network, not your host machine's network.
To let them communicate, we create a custom network:
docker run -d -p 3000:3000 --name backend --network my_custom_network image_tag
docker run -d -v volume_database:/data/db --name mongo --network my_custom_network -p 27017:27017 mongo
Types of Networks:
- Bridge: The default network driver. It provides a private internal network where containers on the same bridge can communicate.
- Host: Removes network isolation entirely, using the host machine's networking directly.
Docker Compose
Managing multiple docker run commands with long flags gets tedious. Docker Compose is a tool designed to define and run multi-container applications. You use a single docker-compose.yml file to configure your services, networks, and volumes. Then, with one command, you spin up the entire stack!
(Insert Docker Compose configuration image here)
The Secret of Alpine Images
Earlier, we mentioned Alpine images. When you pull an image, you aren't just getting your app; you're downloading an entire file system.
(Insert Alpine File System image here)
Alpine is a Linux distribution that is exceptionally lightweight. It achieves this by using libraries like busybox for core utilities (instead of GNU used in Ubuntu/Debian) and apk as a C-based package manager (instead of the Python-based apt). This makes Alpine the perfect, minimal base for containerized apps, especially in embedded systems or where every megabyte counts.
What's Next?
Now it's your turn. Go build a full-stack project, write a Dockerfile, use Docker Compose to orchestrate it, and push it to Docker Hub. Try deploying it to a VM! Build a proper understanding of what's happening under the hood.
Hope you learned something new today!
— Prabal