Docker 101


Why Docker Exists
Every machine has:
CPU
RAM
Operating System (OS)
When you install software like an OS, then a Node app or a Java app on top, it works fine…
but if your app isn’t using 100% CPU, the rest goes to waste.
This inefficiency led to hypervisors — software to manage and partition computer resources.
With a hypervisor, you can split your machine into Virtual Machines (VMs), each with its own OS.
But sometimes you need something lighter than a VM.
That’s where containers come in — faster, smaller, and no need to install a whole OS.
One of the most popular container engines is Docker.
Virtual Machine vs Docker Engine
Feature | Virtual Machine | Docker Engine |
OS | Full OS inside each VM | Shares host OS |
Size | Heavy (GBs) | Light (MBs) |
Startup Time | Minutes | Seconds |
Performance | Slower | Faster |
Why Developers Should Use Containers
Works the same on any system (no "works on my machine" issue)
Easy to share and deploy
Saves system resources
Even non-hardcore developers benefit from using Docker
Key Definitions
Docker Image
A Docker Image is a lightweight, standalone, executable package containing:
Code
Runtime
Libraries
Environment variables
Configuration files
Properties:
Layered – built in multiple layers
Immutable – cannot be changed once built
Portable – runs anywhere with Docker
Docker Container
A container is a runtime instance of a Docker image.
You can run as many containers as you want from any image.
Properties:
Isolation – runs in its own environment
Ephemeral – can be deleted and recreated anytime
Portable – works anywhere with Docker installed
Some Common Docker CLI Commands
docker pull <image_name> # Download an image
docker run <image_name> # Run a container
docker ps # List running containers
docker ps -a # List all containers
docker stop <container_id> # Stop a container
docker rm <container_id> # Remove a container
docker images # List downloaded images
docker rmi <image_id> # Remove an image
docker exec -it <container_id> /bin/bash # Run commands inside container
Other useful:
docker run -d <image> # Run in detached mode
docker run --name my-container <image> # Run with custom name
docker run -p 8080:80 <image> # Map ports (host:container)
docker run -v $(pwd):/app <image> # Mount volume
docker logs <container_name> # Show logs
docker kill <container> # Kill container
docker stop $(docker ps -a) # Stop all containers
docker start <container> # Start stopped container
docker rm -f <container> # Force remove container
docker container prune # Remove all stopped containers
docker cp <container>:/path/to/file ./ # Copy file from container
Dockerfile Basics
RUN → Executes commands at build time (inside image creation)
CMD → Runs the default command at runtime (when container starts)
Example 1: Containerizing a Vite React App
Steps:
npm create vite@latest react-app
cd react-app
npm install
cd src
touch Dockerfile
Dockerfile:
FROM node:22-alpine # Base Node.js image
WORKDIR /app # Set working directory
COPY package*.json ./ # Copy package files
RUN npm install # Install dependencies
COPY . . # Copy all project files
EXPOSE 5173 # Open port
CMD ["npm", "run", "dev"] # Start dev server
Build and Run:
docker build -t vite-app . # "." means current directory (Dockerfile location)
docker run -p 5173:5173 vite-app
Fix for Vite not showing in browser:
In vite.config.js
:
export default defineConfig({
server: {
host: "0.0.0.0",
port: 5173
},
plugins: [react()],
});
Rebuild the image:
docker build -t vite-app:2 . # Versioning example
Example 2: Containerizing a Node.js App
npm init -y
touch index.js
npm install express
index.js:
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send("Hello from NodeJS");
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Dockerfile:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ENV PORT=3000
CMD ["node", "index.js"]
Run:
docker run -p 4000:3000 -e PORT=3000 --rm express-app
# -p : Maps host port 4000 → container port 3000
# -e : Sets environment variable
# --rm : Removes container after it stops
Or use .env
file:
docker run -p 4000:3000 --env-file .env --rm express-app
Multi-Stage Build
What it is:
Build the app in one stage
Copy only needed files to final stage
Benefit: Smaller, faster images
Example:
# --- Stage 1 --- Build Stage
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# --- Stage 2 --- Production Stage
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app /app
EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]
Explanation:
AS builder
→ Names the first stageCOPY --from=builder
→ Copies files from first stageOnly needed files go into the final image → smaller size
That’s all for today’s Docker basics!
I hope these notes help you get started and give you the confidence to containerize your own apps.
If you have any questions or tips of your own, drop them in the comments — I’d love to hear from you.
Thanks for reading, and happy coding! 🚀
Subscribe to my newsletter
Read articles from Taha Iftikhar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
