What Happens When You Type a URL and Press Enter
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →Docker sounds complicated until you see the problem it solves. Images, containers, Dockerfiles and Compose explained simply, with a working Node.js example you can adapt to any stack.
"It works on my machine" is the most famous sentence in software for a reason. Your laptop has Node 20, a teammate has Node 18, the server has something else, and a library behaves slightly differently on each. Docker exists to make that sentence obsolete.
This guide skips the theory overload and focuses on what you actually need to start.
An app depends on more than its code: a language runtime, system libraries, environment variables, specific versions of everything. Docker packages all of that into a single unit that runs the same way on any machine with Docker installed.
You also get a bonus that's arguably even more useful day to day: you can run databases and services locally without installing them.
docker run -d --name pg -e POSTGRES_PASSWORD=secret -p 5432:5432 postgres:16
One command, and you have Postgres running. Delete the container when you're done and your system stays clean.
Containers aren't virtual machines. They share the host's operating system kernel, so they start in seconds and use far less memory than a VM.
Here's a solid starting point for a Node.js app:
FROM node:20-alpine
WORKDIR /app
# Copy dependency files first, so this layer is cached
COPY package*.json ./
RUN npm ci --omit=dev
# Then copy the rest of the source
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]
Build and run it:
docker build -t my-app .
docker run -p 3000:3000 --env-file .env my-app
Visit http://localhost:3000. That's it: your app, in a container.
Docker builds images in layers, one per instruction, and caches each layer. If a layer hasn't changed, Docker reuses it.
That's why we copy package.json and install dependencies before copying the source code. Change a line of your app and Docker reuses the cached dependency layer, so the rebuild takes seconds instead of minutes. Copy everything first and every code change reinstalls all your packages.
Like .gitignore, but for the build context:
node_modules
.git
.env
dist
*.log
This keeps images small, builds fast, and stops your local .env and node_modules from sneaking into the image. Secrets should be passed at runtime, never baked into images.
Real apps have more than one piece. Compose lets you define and run them together:
# docker-compose.yml
services:
api:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/app
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
docker compose up
Notice the database host is db, the service name. Containers in the same Compose project can reach each other by name. The pgdata volume keeps database data around when containers are removed. Without a volume, data disappears with the container.
New teammate onboarding becomes: clone the repo, run docker compose up.
| Command | What it does |
|---|---|
docker ps |
List running containers |
docker ps -a |
Include stopped ones |
docker logs -f <name> |
Follow a container's logs |
docker exec -it <name> sh |
Open a shell inside a container |
docker compose up -d |
Start services in the background |
docker compose down |
Stop and remove services (volumes kept) |
docker images |
List local images |
docker system prune |
Clean up unused data (read the prompt first) |
latest tags. node:latest changes under you. Pin versions like node:20-alpine for reproducible builds..dockerignore, and multi-stage builds for compiled apps (build in one stage, copy only the output into a small final stage).0.0.0.0, or it won't be reachable from outside the container.Not always. Platforms like Vercel and Netlify build your code without Docker. But a Dockerfile makes your app portable: it can run on container platforms, a VPS, Kubernetes or a free tier somewhere else when plans change. (Free hosting options.)
Even if you only ever use Docker for local databases, it's worth learning.
A VM virtualises an entire computer with its own operating system. A container shares the host's kernel and isolates only the app and its dependencies, so it's much lighter and faster to start.
The basics, meaning running images, writing a simple Dockerfile and using Compose, take an afternoon. Advanced topics like networking, multi-stage builds and orchestration can come later, as you need them.
It's especially useful for running databases, caches and other services without installing them, and for matching production environments. Many developers run their app code directly and only containerise the services around it.
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →There are dozens of HTTP status codes, but about fifteen cover nearly everything. What each one means, when to return it, and the mix-ups (401 vs 403, 301 vs 302) that confuse everyone.
Read →A, AAAA, CNAME, MX, TXT and NS records in plain English, why DNS changes seem slow, and the troubleshooting commands that tell you what is really going on.
Read →