Docker Explained: How It Works and Why We Use It in Real Projects
When I started working on backend and full-stack projects, one problem I faced was:
“It works on my laptop, but why does it not work on another laptop?”
Maybe the Node.js version is different.
Maybe Python is not installed.
Maybe MongoDB is configured differently.
Maybe some dependency is missing.
This is where I came across Docker.
Docker helps us package an application along with everything it needs to run, so we can run the same application in different environments.
In this post, I’ll explain Docker in simple language, how it works, where we can use it in projects, and a real-world example.
What is Docker?
Docker is a platform that allows us to run applications inside containers.
A container contains the application and the dependencies required to run it.
For example, suppose I have a Node.js backend.
My backend needs:
- Node.js
- npm packages
- Environment configuration
- Application code
Instead of manually installing everything on another computer, I can create a Docker image containing these requirements.
Then anyone can run that image using Docker.
So, in simple words:
Docker makes the application environment consistent.
The main idea is:
My laptop → Docker → Same environment → Your laptop/server
Why do we need Docker?
Let's take a simple example.
Suppose I build a Node.js application using:
Node.js 20
MongoDB
Express.js
Mongoose
It works perfectly on my laptop.
Now I give the project to another developer.
They might have:
Node.js 18
Different MongoDB configuration
Different operating system
Different package versions
Now the application may not work properly.
This creates the famous developer problem:
“But it works on my machine!” 😅
Docker helps solve this problem by creating a similar environment wherever the application runs.
How does Docker actually work?
There are a few important terms we should understand.
1. Dockerfile
A Dockerfile is like a set of instructions for Docker.
It tells Docker:
- Which base environment to use
- Which dependencies to install
- Which files to copy
- Which command to run
For example, a simple Node.js Dockerfile can look like:
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Let's understand it step by step.
FROM
FROM node:20
This tells Docker to start with a Node.js 20 environment.
WORKDIR
WORKDIR /app
This creates/uses /app as the working directory inside the container.
COPY
COPY package*.json ./
This copies our package files into the container.
RUN
RUN npm install
This installs all the dependencies.
COPY
COPY . .
Now our application code is copied into the container.
EXPOSE
EXPOSE 3000
This tells Docker that our application uses port 3000.
CMD
CMD ["npm", "start"]
This is the command that starts our application.
2. Docker Image
After creating the Dockerfile, we can build a Docker image.
You can think of an image as a blueprint/template for our application.
For example:
Dockerfile
↓
Docker Image
↓
Docker Container
The image contains everything required to create a container.
3. Docker Container
A container is the running instance of an image.
For example:
Node.js Application Image
↓
Container 1
We can also create multiple containers from the same image.
Docker Image
/ | \
↓ ↓ ↓
Container Container Container
Each container can run the same application in an isolated environment.
Image vs Container
This was slightly confusing for me initially.
A simple way to remember it is:
Image = Blueprint
Container = Running application created from that blueprint
Think about a house.
The building plan is like the Docker image.
The actual house built from that plan is like the Docker container.
What happens when we run a Docker container?
Suppose we run:
docker run my-app
Docker takes the my-app image and creates a container from it.
Then the application starts inside that container.
The basic flow is:
Dockerfile
↓
Build
↓
Docker Image
↓
Run
↓
Docker Container
↓
Application Running
Docker Architecture
Docker mainly works using a client-server architecture.
The important parts are:
Docker Client
↓
Docker Daemon
↓
Docker Images
↓
Docker Containers
When we run commands such as:
docker build
docker run
docker ps
docker stop
the Docker client communicates with the Docker daemon.
The daemon is responsible for creating and managing containers, images, networks, and other Docker resources.
Docker vs Virtual Machine
One common question is:
“Is Docker the same as a Virtual Machine?”
No.
Both can isolate applications, but they work differently.
A Virtual Machine usually includes:
Application
Libraries
Guest OS
Virtual Hardware
Host OS
Docker containers share the host operating system's kernel, so they generally need fewer resources than full virtual machines.
A simplified comparison:
Virtual Machine
Application
↓
Libraries
↓
Guest OS
↓
Hypervisor
↓
Host OS
Docker:
Application
↓
Libraries
↓
Container
↓
Docker
↓
Host OS
Because containers are lightweight, we can start them quickly and run many of them on the same machine.
Where can we use Docker in a project?
Docker becomes especially useful when our project has multiple services.
For example, imagine a MERN application:
React Frontend
↓
Node.js Backend
↓
MongoDB
Instead of installing everything directly on a server, we can run different services in containers.
For example:
Docker
│
├── Frontend Container
│
├── Backend Container
│
└── Database Container
This gives us a cleaner and more consistent development environment.
Docker Compose
When our project has multiple containers, managing them individually can become difficult.
For this, Docker Compose is useful.
For example:
Frontend
Backend
MongoDB
Redis
We can define these services in a docker-compose.yml file.
A simplified example:
services:
backend:
build: ./backend
ports:
- "5000:5000"
frontend:
build: ./frontend
ports:
- "3000:3000"
mongodb:
image: mongo
ports:
- "27017:27017"
Now we can start the whole application using:
docker compose up
Instead of manually starting every service.
Real-World Scenario
Let's take a real example.
Imagine an e-commerce company.
The application has:
Frontend
Backend API
Payment Service
Product Service
Database
Redis Cache
Without containers, setting up this environment on a new server can take a lot of time.
Developers may need to install:
- Node.js
- MongoDB/PostgreSQL
- Redis
- Different dependencies
- Correct versions
- Environment configurations
With Docker, each service can have its own container.
For example:
Docker
│
┌────────────┼────────────┐
↓ ↓ ↓
Frontend Backend Redis
Container Container Container
│
↓
Database
Container
Now the development, testing, and deployment environments can be much more consistent.
Docker in CI/CD
Docker is also very useful in CI/CD.
Suppose a developer pushes code to GitHub.
A CI/CD pipeline can:
Developer
↓
Git Push
↓
GitHub
↓
CI/CD Pipeline
↓
Build Docker Image
↓
Run Tests
↓
Deploy
↓
Production
This means the same containerized application can move through different stages of development and deployment.
For example:
Development
↓
Testing
↓
Staging
↓
Production
The goal is to reduce environment-related problems between these stages.
Docker in My Own Projects
For the type of full-stack projects I work on, Docker can be useful when the project contains multiple technologies.
For example, suppose I have a backend built with:
Node.js
Express.js
MongoDB
I can use Docker to create a consistent environment for the backend and database.
If later I deploy the project on a cloud server, I don't have to manually recreate the complete environment from scratch.
I can build the image and run the container on the server.
Some Docker Commands I Started With
These are some basic commands that are useful when learning Docker.
Check Docker version:
docker --version
Build an image:
docker build -t my-app .
Run a container:
docker run my-app
See running containers:
docker ps
See all containers:
docker ps -a
Stop a container:
docker stop <container_id>
Remove a container:
docker rm <container_id>
See available images:
docker images
Remove an image:
docker rmi <image_id>
Start Docker Compose:
docker compose up
Stop Docker Compose:
docker compose down
What Problem Does Docker Actually Solve?
For me, the easiest way to understand Docker is not to remember definitions.
Think about the problem first.
Without Docker:
"My application works on my computer."
But on another computer:
Different OS
Different Node version
Missing dependency
Different database setup
Different configuration
With Docker:
Application
+
Dependencies
+
Required environment
↓
Docker
↓
Same environment
That's the main reason Docker is useful.
Docker is Not Only for Deployment
Initially, I thought Docker was mainly something used by DevOps engineers for deployment.
But Docker can be useful during development too.
It can help developers:
- Set up projects quickly
- Keep environments consistent
- Run databases locally
- Run multiple services
- Test applications in isolated environments
- Work with different versions of tools
- Prepare applications for deployment
So Docker is useful for both developers and DevOps teams.
What I Learned From Docker
The biggest thing I understood is that Docker is not just about running commands.
The actual idea is:
Package the application and its environment so it can run consistently in different places.
Once I understood the difference between:
Dockerfile
↓
Image
↓
Container
Docker became much easier to understand.
And when we add Docker Compose, we can manage multiple containers together.
Final Thoughts
Docker is one of those technologies that looks complicated when you first see terms like:
- Images
- Containers
- Dockerfile
- Volumes
- Networks
- Compose
- Docker Engine
But the basic concept is actually simple.
Docker packages applications into containers so that they can run in a consistent and isolated environment.
For a beginner like me, I think the best way to learn Docker is not by memorizing all the commands.
Start with one small project.
Create a Dockerfile.
Build an image.
Run a container.
Then try Docker Compose with a backend and database.
Once you actually see the application running inside a container, the concept becomes much clearer.
That's how I'm currently understanding Docker, and I think it's a useful technology to learn if you're working with Full Stack Development, Backend, Cloud, or DevOps.