Toolmingo
Guides11 min read

What Is Docker? A Complete Beginner's Guide (2025)

Learn what Docker is, how containers work, the difference between containers and VMs, and how to use Docker in your development workflow — with practical examples and key commands.

Docker is an open-source platform that lets you package an application and everything it needs to run — code, runtime, libraries, environment variables — into a single portable unit called a container. You build it once; it runs the same way everywhere: your laptop, a colleague's machine, a test server, or production in the cloud.

If you've ever heard "works on my machine" and wished it worked on everyone's machine, Docker is the answer.


Docker in 30 seconds

Concept What it means
Image A read-only blueprint (snapshot) for a container
Container A running instance of an image
Dockerfile A script that builds an image step by step
Docker Hub Public registry of pre-built images
docker-compose Tool to run multiple containers together
Volume Persistent storage that survives container restarts
Network Virtual network connecting containers
# Pull an image from Docker Hub
docker pull nginx

# Run a container from that image
docker run -d -p 8080:80 nginx

# Visit http://localhost:8080 — Nginx is running!

Why does Docker exist?

Before Docker, deploying software meant:

  1. Writing an app that works on your local machine
  2. Shipping it to a server with a different OS, different library versions, different paths
  3. Spending hours (or days) making it work again

Docker solves this with containerization: isolate the app and its environment together so the environment travels with the code.

The "works on my machine" problem — solved

Developer laptop         CI server            Production
─────────────────────    ─────────────────    ─────────────────
Node.js 18               Node.js 16           Node.js 20
npm 9.x                  npm 8.x              npm 9.x
Ubuntu 22.04             CentOS 7             Amazon Linux 2

With Docker, all three environments run the exact same container image. No more version mismatches.


Containers vs Virtual Machines

People often confuse Docker containers with virtual machines (VMs). They solve similar problems but work very differently.

Feature Container Virtual Machine
What it virtualises OS process isolation Entire hardware
Includes OS kernel No — shares host kernel Yes — full OS inside
Startup time Milliseconds 30–60 seconds
Disk size Megabytes Gigabytes
Performance overhead Minimal (near-native) Higher (hypervisor layer)
Isolation level Process-level Full machine-level
Portability Excellent Good but image is huge
Best for Microservices, CI/CD, dev env Running different OS, legacy apps

Rule of thumb: Containers are for applications; VMs are for entire operating systems.

Virtual Machine stack:          Container stack:
┌─────────────────────┐         ┌────────┐ ┌────────┐ ┌────────┐
│     Application     │         │  App A │ │  App B │ │  App C │
├─────────────────────┤         ├────────┴─┴────────┴─┴────────┤
│  Guest OS (full!)   │         │         Docker Engine         │
├─────────────────────┤         ├──────────────────────────────┤
│     Hypervisor      │         │         Host OS Kernel        │
├─────────────────────┤         ├──────────────────────────────┤
│     Host Hardware   │         │          Host Hardware        │
└─────────────────────┘         └──────────────────────────────┘

Core Docker concepts

Images

A Docker image is a read-only template. Think of it like a class in OOP — it defines what a container looks like but doesn't run on its own.

Images are built in layers. Each instruction in a Dockerfile adds a layer:

Base layer:   ubuntu:22.04
Layer 2:      apt-get install python3
Layer 3:      pip install flask
Layer 4:      COPY app.py /app/
Layer 5:      CMD ["python3", "/app/app.py"]

Layers are cached and shared between images — if 10 projects all use ubuntu:22.04, that base layer is only stored once on disk.

Containers

A container is a running instance of an image — like an object instantiated from a class.

# Same image, three containers running simultaneously
docker run -d --name web1 nginx
docker run -d --name web2 nginx
docker run -d --name web3 nginx

Containers are:

  • Isolated — each has its own filesystem, network, and process space
  • Ephemeral by default — stopping a container discards its filesystem changes
  • Lightweight — you can run dozens on a single laptop

Dockerfile

A Dockerfile is a text file that defines how to build your image. It's the recipe.

# Start from official Node.js 20 image
FROM node:20-alpine

# Set working directory inside the container
WORKDIR /app

# Copy dependency files first (layer caching optimisation)
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy the rest of the source
COPY . .

# Expose port 3000
EXPOSE 3000

# Command to run when container starts
CMD ["node", "server.js"]

Build and run:

docker build -t my-app:1.0 .
docker run -d -p 3000:3000 my-app:1.0

Volumes

Containers are ephemeral — when a container stops, its data is gone. Volumes solve this by mounting persistent storage from the host into the container.

# Named volume (Docker manages the location)
docker run -d -v my-db-data:/var/lib/postgresql/data postgres:16

# Bind mount (specific host path → container path)
docker run -d -v ./src:/app/src node:20-alpine

Volumes are essential for:

  • Databases (PostgreSQL, MySQL, MongoDB)
  • User-uploaded files
  • Development: live-reloading code changes

Networks

Containers on the same Docker network can talk to each other by name:

# Create a network
docker network create myapp-net

# Both containers join the same network
docker run -d --network myapp-net --name db postgres:16
docker run -d --network myapp-net --name api my-api:1.0

# Inside the api container, the db is reachable at "db:5432"

Docker Compose

Running one container is easy. Running a full stack (app + database + cache + reverse proxy) with docker run gets tedious fast. Docker Compose solves this with a single YAML file.

# docker-compose.yml
version: "3.9"

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://postgres:password@db:5432/mydb
      REDIS_URL: redis://cache:6379
    depends_on:
      - db
      - cache
    volumes:
      - ./src:/app/src   # live reload in development

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - db_data:/var/lib/postgresql/data

  cache:
    image: redis:7-alpine

volumes:
  db_data:

Start the entire stack:

docker compose up -d        # Start all services in background
docker compose logs -f      # Follow logs
docker compose down         # Stop and remove containers
docker compose down -v      # Also remove volumes (wipes data)

Essential Docker commands

Command What it does
docker pull <image> Download image from registry
docker build -t name:tag . Build image from Dockerfile in current dir
docker run -d -p 8080:80 nginx Run container (detached, port mapping)
docker ps List running containers
docker ps -a List all containers (including stopped)
docker stop <id or name> Stop a container gracefully
docker rm <id or name> Delete a container
docker images List local images
docker rmi <image> Delete an image
docker exec -it <id> bash Open shell inside running container
docker logs -f <id> Follow container logs
docker inspect <id> Full JSON details of a container
docker system prune Remove unused containers, images, networks
docker volume ls List volumes

How Docker works under the hood

Docker uses Linux kernel features — you don't need to know this to use Docker, but it helps you understand why containers are so fast:

Kernel feature What it provides
Namespaces Isolation — each container sees its own PID, network, filesystem, hostname
cgroups Resource limits — restrict CPU, memory, I/O per container
Union filesystem (OverlayFS) Layer-based filesystem for images
seccomp Syscall filtering for security

On macOS and Windows, Docker runs a lightweight Linux VM (via HyperKit/WSL 2) because these kernel features are Linux-only.


Docker workflow in practice

A typical development→production workflow:

1. Write code
       │
       ▼
2. Write Dockerfile (once)
       │
       ▼
3. docker build → creates image
       │
       ▼
4. docker run → test locally
       │
       ▼
5. docker push → push to registry (Docker Hub, ECR, GCR)
       │
       ▼
6. Production server: docker pull + docker run
   (or Kubernetes/ECS/Cloud Run pulls and runs it)

Multi-stage builds (production optimisation)

Keep production images small by separating build and runtime stages:

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production — only the compiled output
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

The production image doesn't include dev dependencies, source files, or build tools — often 5–10× smaller.


When to use Docker

Use case Why Docker helps
Development environment Same env for entire team, no "works on my machine"
CI/CD pipelines Reproducible builds, isolated test environments
Microservices Each service in its own container, independent scaling
Running open-source tools docker run postgres — no install needed
Legacy app isolation Old PHP 5.6 app in a container, won't pollute host
Cloud deployment All major clouds run containers natively (ECS, GKE, ACI)
Onboarding New dev up and running in 5 minutes with docker compose up

Docker registries

A registry stores Docker images. When you docker pull, it downloads from a registry.

Registry Description
Docker Hub Default public registry (hub.docker.com)
AWS ECR Amazon's private container registry
Google Artifact Registry Google Cloud's registry
GitHub Container Registry ghcr.io, integrated with GitHub Actions
Azure Container Registry Microsoft's private registry
Self-hosted Run your own with registry:2 image
# Push to Docker Hub
docker login
docker tag my-app:1.0 username/my-app:1.0
docker push username/my-app:1.0

# Push to GitHub Container Registry
docker tag my-app:1.0 ghcr.io/username/my-app:1.0
docker push ghcr.io/username/my-app:1.0

Docker security basics

Best practice Why
Use official base images Scanned and maintained by vendors
Pin image versions (node:20.12 not node:latest) Reproducible builds
Run as non-root user Limit blast radius if container is compromised
Scan images (docker scout, Trivy, Snyk) Find known CVEs
Don't store secrets in Dockerfile Use env vars or secrets managers
Use multi-stage builds Smaller attack surface
Read-only filesystems where possible --read-only flag
# Add non-root user
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
USER app
WORKDIR /home/app
COPY --chown=app:app . .
CMD ["node", "server.js"]

Docker vs related tools

Tool What it is Relationship to Docker
Docker Container runtime + build tool The foundation
Kubernetes (K8s) Container orchestration at scale Runs Docker containers across a cluster
Docker Swarm Docker's own simpler orchestration Built into Docker, less popular than K8s
Podman Daemonless Docker alternative Drop-in replacement, rootless by default
containerd Low-level container runtime Docker uses it internally; K8s uses it directly
docker-compose Multi-container local dev Great for development, not for production scale
Helm Kubernetes package manager Deploys containerised apps to K8s
OCI Open Container Initiative standard Docker images follow OCI spec

Common mistakes

Mistake Fix
FROM ubuntu with full apt-get installs Use slim images (node:20-alpine) — 10× smaller
Copying everything including node_modules Add node_modules to .dockerignore
Storing secrets in Dockerfile or image Use --env, .env files (not committed), or secrets managers
Not using layer caching Copy package.json before source files
Running as root inside container Add USER nonroot
One giant monolith container One process per container
No health checks Add HEALTHCHECK instruction
docker run with no resource limits Add --memory and --cpus flags

Docker vs Kubernetes — when to use what

Situation Use
Local development Docker Compose
Single server, simple deployment Docker + docker-compose
Multiple servers, high availability Kubernetes
Managed simplicity AWS ECS, Google Cloud Run
Learning containers Docker first, then K8s

A common progression: Docker → Docker Compose → Kubernetes as your application scales.


Frequently asked questions

Is Docker free? Docker Desktop (the GUI app for Mac/Windows) requires a paid subscription for companies with more than 250 employees or $10M revenue. Docker Engine (the CLI) is always free and open source. For most developers and CI environments, Docker Engine is all you need.

Do I need Docker to use Kubernetes? No. Kubernetes can use any OCI-compatible container runtime (containerd, CRI-O). But the containers you run on Kubernetes are built with Docker (or Buildah/Podman), and Docker images are OCI-compatible, so they just work.

Is Docker the same as a container? No. A container is the concept (isolated process). Docker is a tool (one of several) for building and running containers. Other tools: Podman, containerd, LXC.

Can Docker run on Windows without WSL 2? Docker Desktop on Windows uses WSL 2 (Windows Subsystem for Linux) as its backend, which is required. On Windows Server, Docker can run Windows containers natively.

How is Docker different from a virtual environment (venv/conda)? A Python venv isolates Python packages only. Docker isolates everything — the OS libraries, the runtime version, system packages, network — not just Python. venv is fine for Python projects; Docker is for deploying services.

How much memory does Docker use? Each container uses only what its process needs. An Nginx container at idle might use 5–10 MB. A Node.js app might use 50–200 MB. Docker Engine itself uses ~200–400 MB. Docker Desktop on Mac/Windows runs a Linux VM that uses 2–4 GB by default (configurable).


Quick start: your first Docker project

# 1. Install Docker (docker.com/get-started)

# 2. Verify installation
docker --version
docker run hello-world

# 3. Run a database without installing it
docker run -d \
  --name my-postgres \
  -e POSTGRES_PASSWORD=mysecret \
  -e POSTGRES_DB=mydb \
  -p 5432:5432 \
  postgres:16-alpine

# 4. Connect to it (from host, if psql installed)
psql -h localhost -U postgres -d mydb

# 5. Run a Redis cache
docker run -d --name my-redis -p 6379:6379 redis:7-alpine

# 6. Stop and remove when done
docker stop my-postgres my-redis
docker rm my-postgres my-redis

With Docker, every tool in your stack is a one-liner away — no installation, no version conflicts, no cleanup headaches.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools