What you'll build
You'll install Docker and create your first container: a Node.js app packaged to work identically on any computer. You'll learn to run Ubuntu containers, databases like PostgreSQL, and use volumes to avoid losing data. When you're done, you'll have a Dockerized app with its Dockerfile, docker-compose.yml, and the ability to spin up services with one command. It's the foundation for working in teams where "it works on my machine" will no longer be a problem.
Step 1: Install Docker
| System | Installation |
|---|---|
| macOS | Docker Desktop |
| Windows | Docker Desktop |
| Linux | `curl -fsSL https://get.docker.com |
Verify:
docker --version
docker run hello-world
⚠️ Windows: You need WSL2 enabled. Docker Desktop guides you through installation.
Step 2: Your first container
# Download and run Ubuntu in seconds
docker run -it ubuntu bash
# Now you're INSIDE the container
cat /etc/os-release # You'll see "Ubuntu"
apt update # Works like real Linux
exit # Exit the container
What happened? Docker downloaded an Ubuntu image (~30MB, not 4GB) and ran it isolated from your system.
Step 3: Run useful services
Docker shines when you need databases or services without permanent installation:
# PostgreSQL ready in 10 seconds
docker run -d --name my-postgres \
-e POSTGRES_PASSWORD=secret \
-p 5432:5432 \
postgres:16-alpine
# Redis for caching
docker run -d --name my-redis -p 6379:6379 redis:alpine
# Adminer (web interface for databases)
docker run -d --name adminer -p 8080:8080 adminer
Open http://localhost:8080 to see Adminer running.
⚠️ PROBLEM: If you run docker rm my-postgres, you lose ALL the data. Keep reading to fix this.
Step 4: Volumes (Persistent Data)
The most common beginner mistake: They create a database, save data, remove the container... and lose everything.
Why does this happen?
WITHOUT VOLUME:
┌─────────────────────────┐
│ Container │
│ ┌─────────────────┐ │
│ │ Database │ │ ← Data lives INSIDE
│ │ (your data) │ │ the container
│ └─────────────────┘ │
└─────────────────────────┘
↓
docker rm postgres
↓
💀 DATA LOST
WITH VOLUME:
┌─────────────────────────┐ ┌─────────────────┐
│ Container │ │ Volume │
│ ┌─────────────────┐ │ ──── │ (your disk) │
│ │ Database │────│──────│ your data │
│ └─────────────────┘ │ └─────────────────┘
└─────────────────────────┘ ↓
↓ Data is stored
docker rm postgres OUTSIDE container
↓ ↓
Container removed ✅ DATA SAFE
Create PostgreSQL with persistent volume
# Create a named volume
docker volume create postgres_data
# Run PostgreSQL using that volume
docker run -d --name my-postgres \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_USER=dev \
-e POSTGRES_DB=myapp \
-p 5432:5432 \
-v postgres_data:/var/lib/postgresql/data \
postgres:16-alpine
The magic is in -v postgres_data:/var/lib/postgresql/data:
postgres_data= volume name on your machine/var/lib/postgresql/data= where Postgres stores data inside the container
Test that it works
# Connect and create a table
docker exec -it my-postgres psql -U dev -d myapp -c "CREATE TABLE test (id INT);"
docker exec -it my-postgres psql -U dev -d myapp -c "INSERT INTO test VALUES (1), (2), (3);"
# Remove the container
docker stop my-postgres && docker rm my-postgres
# Create a new one with the SAME volume
docker run -d --name my-postgres \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_USER=dev \
-e POSTGRES_DB=myapp \
-p 5432:5432 \
-v postgres_data:/var/lib/postgresql/data \
postgres:16-alpine
# Verify the data is still there
docker exec -it my-postgres psql -U dev -d myapp -c "SELECT * FROM test;"
# Result: 1, 2, 3 ✅
Volume commands
| Command | What it does |
|---|---|
docker volume create name | Create a volume |
docker volume ls | List all volumes |
docker volume inspect name | See details (real location) |
docker volume rm name | Delete volume (and its data!) |
docker volume prune | Delete unused volumes |
Mount types
| Type | Syntax | Use case |
|---|---|---|
| Named Volume | -v my_volume:/data | Production, databases |
| Bind Mount | -v ./local:/data | Development, live reload |
| Anonymous | -v /data | Temporary, not recommended |
Bind Mount example for development:
# Your local code syncs with the container
docker run -d --name my-app \
-v $(pwd):/app \
-p 3000:3000 \
node:20-alpine npm start
Change a file → the container sees it immediately.
Step 5: Create your own image
Now that you understand containers and volumes, create your own image.
Create a docker-hello folder with these 3 files:
Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "index.js"]
index.js:
console.log("Hello from Docker! 🐳");
console.log("Date:", new Date().toISOString());
console.log("Node version:", process.version);
package.json:
{ "name": "docker-hello", "version": "1.0.0" }
Build and run:
docker build -t my-app .
docker run my-app
Step 6: Install Portainer (Visual Management)
Portainer is a web interface for Docker. Perfect for beginners:
# Create a volume to persist data
docker volume create portainer_data
# Install Portainer
docker run -d -p 9000:9000 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
Open http://localhost:9000 and create your admin user.
What can you do with Portainer?
- See all your containers in a table
- Start/stop/remove with one click
- View logs in real-time
- Create containers without commands
- Manage images, volumes, networks
💡 Tip: Keep Portainer running always. It's like "Finder" or "Explorer" but for Docker.
🛠️ Management tools
| Tool | Type | Ideal for |
|---|---|---|
| Portainer | Local/server UI | Visual Docker management |
| Docker Desktop | Desktop app | Mac/Windows, includes basic UI |
| Lazydocker | Terminal TUI | Terminal fans |
For production deployment
| Tool | Description | Cost |
|---|---|---|
| EasyPanel | Deploy like Heroku, but on your VPS | Free self-hosted |
| Coolify | Open source, very complete | Free self-hosted |
| Dokku | Mini-Heroku on your server | Free |
| CapRover | Simple PaaS with Let's Encrypt | Free |
🎯 Recommendation for beginners: Use Portainer to learn, then EasyPanel or Coolify when you want easy deployment.
Essential commands
| Command | What it does |
|---|---|
docker run -d name | Run in background |
docker ps | List active containers |
docker ps -a | List ALL (even stopped) |
docker logs name | View logs |
docker logs -f name | Real-time logs |
docker stop name | Stop container |
docker rm name | Remove container |
docker images | List downloaded images |
docker rmi name | Remove image |
docker system prune | Clean all unused |
If something failed
| Error | Cause | Solution |
|---|---|---|
daemon not running | Docker not running | Open Docker Desktop |
permission denied | No permissions | Linux: sudo usermod -aG docker $USER and restart session |
port already in use | Port occupied | Change port: -p 9001:9000 |
no space left | Disk full of images | docker system prune -a |
cannot connect to localhost | Container not exposing port | Add -p port:port |
What you learned
✅ Install Docker and verify it works ✅ Run pre-made containers (Ubuntu, Postgres, Redis) ✅ Use volumes to not lose data (beginner's #1 mistake) ✅ Create your own image with Dockerfile ✅ Use Portainer for visual management ✅ Essential commands for daily use
Next steps
→ Consume a JSON API — Connect your app with real data → Deploy with Docker — Take your container to production → Docker Basics (theory) — Understand containers vs VMs