Docker Containers for Beginners: Complete Tutorial
Last updated: October 2025 β’ Reviewed by Docker experts
"It works on my machine!" - Every developer's famous last words. Docker fixes this problem forever. I remember the days of spending hours configuring environments, dealing with dependency conflicts, and the nightmare of "but it worked yesterday!" Docker changed all that.
This isn't a theory guide. This is everything I wish someone told me when I started with Docker. We'll build real containers, deploy real apps, and skip all the stuff you don't actually need to know.
π€What is Docker? (In Plain English)
Docker is like a shipping container for your code. Just as shipping containers standardized global trade (any container fits on any ship), Docker containers standardize software deployment (any container runs on any server).
π° Without Docker
- β’ "Works on my machine" syndrome
- β’ Different setups for dev/staging/prod
- β’ Dependency hell and conflicts
- β’ Hours spent on environment setup
- β’ "Did you install Node 18 or 20?"
π With Docker
- β’ Works everywhere, always
- β’ Identical environments everywhere
- β’ Isolated, no conflicts
- β’ Setup in seconds, not hours
- β’ Version locked in the container
πΈDocker Images: The Blueprint
A Docker image is like a recipe. It contains everything your app needs: code, runtime, libraries, dependencies. You build an image once, then create containers from it.
Your First Dockerfile:
# Dockerfile
# Start with Node.js base image
FROM node:18-alpine
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy your code
COPY . .
# Expose port
EXPOSE 3000
# Start command
CMD ["npm", "start"]This Dockerfile creates an image with Node.js, your dependencies, and your code. Simple!
Building Your Image:
# Build the image
docker build -t my-app:1.0 .
# The dot (.) means "current directory"
# -t tags your image with a name and version
# List your images
docker images
# Output:
# REPOSITORY TAG IMAGE ID SIZE
# my-app 1.0 abc123def456 200MBπRunning Containers: Making It Live
A container is a running instance of an image. Think of the image as a class and containers as instances of that class. You can run multiple containers from the same image.
Basic Container Commands:
# Run container
docker run -p 3000:3000 my-app:1.0
# Run in background (detached mode)
docker run -d -p 3000:3000 --name my-container my-app:1.0
# List running containers
docker ps
# List all containers (including stopped)
docker ps -a
# View logs
docker logs my-container
# Follow logs (like tail -f)
docker logs -f my-container
# Stop container
docker stop my-container
# Remove container
docker rm my-containerUseful Flags Explained:
-p 3000:3000 - Port mapping (host:container)-d - Detached mode (runs in background)--name - Give your container a name-e - Set environment variables-v - Mount volumes (persist data)Real-World Example:
# Run with environment variables and volume
docker run -d \
--name my-api \
-p 3000:3000 \
-e DATABASE_URL=postgres://localhost/mydb \
-e NODE_ENV=production \
-v /app/uploads:/data \
my-app:1.0
# Access shell inside container
docker exec -it my-api /bin/sh
# Now you're inside the container!
# ls, cd, whatever you needπΌDocker Compose: Multi-Container Magic
Most real apps need multiple services: your app, a database, Redis, maybe a queue. Docker Compose lets you define and run all of them together with one command.
Complete App with docker-compose.yml:
version: '3.8'
services:
# Your Node.js app
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
volumes:
- ./src:/app/src # Hot reload in development
# PostgreSQL database
db:
image: postgres:15-alpine
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- db-data:/var/lib/postgresql/data
ports:
- "5432:5432"
# Redis cache
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
db-data:Using Docker Compose:
# Start everything
docker-compose up
# Start in background
docker-compose up -d
# View logs
docker-compose logs -f
# View logs for specific service
docker-compose logs -f app
# Stop everything
docker-compose down
# Stop and remove volumes (deletes data!)
docker-compose down -v
# Rebuild images
docker-compose build
# Restart a service
docker-compose restart appπ―Best Practices
β Keep Images Small:
# Bad: 1.2GB
FROM node:18
# Good: 200MB
FROM node:18-alpine
# Better: Multi-stage build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm install --production
CMD ["node", "dist/index.js"]β Use .dockerignore:
# .dockerignore
node_modules
npm-debug.log
.git
.env
*.md
.DS_Store
dist
coverageLike .gitignore but for Docker. Keeps your images clean and small.
β Don't Run as Root:
FROM node:18-alpine
# Create app user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY . .
RUN npm install
# Switch to non-root user
USER appuser
CMD ["npm", "start"]π¨Common Mistakes to Avoid
β Storing Data in Containers
Containers are ephemeral. Always use volumes for persistent data like databases, uploads, logs.
β Hardcoding Configuration
Use environment variables! Never bake secrets or config into images.
β Not Using Health Checks
HEALTHCHECK --interval=30s --timeout=3s \
CMD node healthcheck.js || exit 1β Docker Learning Checklist
π― Final Thoughts
Start simple. Dockerize one app. Get comfortable with the basic commands. Then add Docker Compose for multiple services. Before you know it, you'll wonder how you ever deployed without containers. The "it works on my machine" problem? Gone forever. And that's worth the learning curve.
About the Team
Written by DevMetrix's DevOps engineers who've containerized hundreds of applications from simple APIs to complex microservices architectures.
β Docker-certified engineers β’ β Updated October 2025 β’ β Production-tested practices
πTest Your Dockerized APIs
Once your app is containerized, test it with our API tester. Verify your endpoints work correctly in the Docker environment.
Try API Tester β