I avoided Docker for years. "Containerization" sounded like something principal engineers with beards did. I was fine with "it works on my machine" as a development philosophy.
Then I joined a team where nothing worked on anyone's machine. Different Node versions. Different database configurations. Different everything. I spent more time debugging environment issues than writing code.
Docker fixed that. And here's the secret: you don't need to understand orchestration, networking, or half the Docker hype. You need to know maybe 10 commands and how to write a Dockerfile.
This is the Docker basics guide I wish I had. No fluff. No "let's deploy a microservices architecture." Just what you need to containerize your apps and stop the environment madness.
What Docker Actually Is (Simple Version)
Docker is a way to package your application with everything it needs to run: code, runtime, system tools, libraries. That package is called a container.
Analogy time: Think of shipping containers. Before them, loading ships was chaos. Every item was different. Every port handled things differently. Then came standardized containers. Same size, same fittings, handled the same way everywhere.
Docker does that for software. Your app goes in a container. The container runs the same way on your laptop, your teammate's machine, and production. No more "but it works on my machine."
Core Concepts (Only What Matters)
Docker has a million concepts. You need to know four.
Dockerfile
A text file with instructions for building your container image. Think of it as a recipe:
# Start from Node.js 20
FROM node:20
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy application code
COPY . .
# Expose port
EXPOSE 3000
# Start the app
CMD ["npm", "start"]
That's it. That's a basic Dockerfile. Save it, build it, run it. Your app is now containerized.
Image
An image is a snapshot of your container. It's what you build from a Dockerfile. Images are layered—each instruction adds a layer. This makes builds fast because Docker caches layers.
Build an image:
docker build -t my-app .
The -t flag tags it with a name. The . means "use the Dockerfile in this directory."
Container
A container is a running instance of an image. You can run multiple containers from the same image. Each is isolated—they don't interfere with each other.
Run a container:
docker run -p 3000:3000 my-app
The -p flag maps port 3000 inside the container to port 3000 on your machine. Now you can access your app at localhost:3000.
Docker Compose
Most apps need more than one container. Your app needs a database. Maybe Redis. Maybe a message queue. Docker Compose lets you define and run multiple containers together.
docker-compose.yml:
version: '3'
services:
app:
build: .
ports:
- "3000:3000"
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: password
Run everything:
docker compose up
Both containers start. They can talk to each other. Your app connects to db as the hostname. No manual database setup.
Essential Docker Commands
You don't need to memorize 100 commands. These 12 will handle 95% of your Docker usage:
| Command | What It Does |
|---|---|
docker build -t name . | Build an image |
docker run -p 3000:3000 name | Run a container |
docker ps | List running containers |
docker ps -a | List all containers (including stopped) |
docker stop container_id | Stop a container |
docker rm container_id | Remove a container |
docker images | List images |
docker rmi image_id | Remove an image |
docker logs container_id | View container logs |
docker exec -it container_id bash | Enter a running container |
docker compose up | Start all services |
docker compose down | Stop all services |
That's it. Master these, and you're more Docker-proficient than most developers.
Common Dockerfile Patterns
Writing Dockerfiles is an art. Here are patterns that work well:
Multi-Stage Builds
Don't ship your node_modules to production. Use multi-stage builds:
# Build stage
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
The final image only has what's needed to run, not to build. Smaller images = faster deploys = less attack surface.
Use .dockerignore
Just like .gitignore, but for Docker. Prevents unnecessary files from being copied into your image:
node_modules
npm-debug.log
.git
.env
Dockerfile
.dockerignore
Without this, Docker copies everything. Your image gets huge. Builds get slow. Don't skip this.
Use Specific Versions
Don't use FROM node:latest. Use FROM node:20.11.0. Latest changes. Your build breaks when it does. Pin versions for reproducible builds.
Debugging Common Issues
Things will break. Here's how to fix them:
Container Exits Immediately
Problem: You run the container, and it stops instantly.
Solution: Check the logs:
docker logs container_id
Usually means your app crashed on startup. Missing environment variables, database connection failed, port already in use.
Can't Access the App
Problem: Container is running, but localhost:3000 doesn't work.
Solution: Check port mapping:
docker ps
Make sure you see 0.0.0.0:3000->3000/tcp. If you don't see the mapping, you forgot the -p flag.
Disk Space Full
Problem: Docker is eating your disk space.
Solution: Clean up unused resources:
docker system prune
This removes stopped containers, unused networks, and dangling images. Run it monthly.
When NOT to Use Docker
Docker isn't always the answer. Skip it when:
- Learning to code: Add one more layer of complexity you don't need yet.
- Simple static sites: Just deploy to Netlify or Vercel. Overkill otherwise.
- Quick experiments: Spinning up a container takes more time than just running
npm startfor a 5-minute test. - Development on low-spec machines: Docker uses RAM. If you have 8GB or less, it might slow you down more than it helps.
Docker is a tool, not a religion. Use it when it solves a problem. Ignore it when it doesn't.
Conclusion
Where to Go From Here
You now know enough Docker to be dangerous. Containerize your next project. Add a Dockerfile. Write a docker-compose.yml for your app + database.
Don't worry about Kubernetes. Don't worry about orchestration. Don't worry about the 100 advanced features you'll never use.
Master the basics. Ship working software. That's what matters.
Questions about Docker? Ask on Twitter @mehitsfine.
Tags: