Docker 101

Taha IftikharTaha Iftikhar
5 min read

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

FeatureVirtual MachineDocker Engine
OSFull OS inside each VMShares host OS
SizeHeavy (GBs)Light (MBs)
Startup TimeMinutesSeconds
PerformanceSlowerFaster


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 stage

  • COPY --from=builder → Copies files from first stage

  • Only 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! 🚀

0
Subscribe to my newsletter

Read articles from Taha Iftikhar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Taha Iftikhar
Taha Iftikhar