The Problem Docker Solves

You try to run a friend's project. You clone the repo, install dependencies, configure the database... and it still doesn't work. "Works on my machine" — you've heard this before.

Docker fixes this. It packages your application with everything it needs — code, libraries, runtime, config — into a single portable unit called a container. If it runs on your machine, it runs anywhere.

I run Docker on a $4/month VPS to host multiple services without conflicts. In our website building guide, we set up Nginx directly on the server. With Docker, you could run that same Nginx plus three other apps, each in its own isolated box, on the same cheap machine.

Containers vs Virtual Machines

Beginners often confuse containers with VMs. Here's the key difference:

Virtual Machine Docker Container
Boot time Minutes Seconds
Disk space GBs per VM MBs per container
Performance Near-native, but overhead Native — no overhead
What it shares Nothing (full OS per VM) The host's Linux kernel

A VM virtualizes hardware. Each VM runs a complete operating system — its own kernel, memory, disk. Heavy but fully isolated.

A container virtualizes the OS. All containers share the host's Linux kernel. Each gets its own file system, processes, and network — but without duplicating the entire OS. Lightweight and fast.

Think of VMs as separate houses on a street. Containers are separate rooms in one house — each has privacy, but they share plumbing and electricity.

Installing Docker

On Ubuntu/Debian

# One command installs everything
sudo apt update && sudo apt install docker.io docker-compose -y

# Add yourself to the docker group (so you don't need sudo every time)
sudo usermod -aG docker $USER

# Log out and back in, then verify
docker --version

On Windows / Mac

Download Docker Desktop from docker.com. It includes a GUI and sets up everything automatically.

Your First Container: Hello World

Let's run the simplest possible container:

docker run hello-world

You'll see:

Hello from Docker!
This message shows that your installation appears to be working correctly.

Here's what Docker just did: 1. Looked for an image called hello-world on your machine 2. Didn't find it, so pulled it from Docker Hub (a public registry of images) 3. Created a container from that image and ran it 4. The container printed the message, then exited

Understanding Images and Containers

These two words come up constantly. Here's the simple version:

  • Image = the blueprint. A read-only template with the OS, dependencies, and app code. Like a .iso file or a recipe.
  • Container = a running instance of an image. Like a VM booted from the ISO, or a cake from the recipe.

You can run 10 containers from the same image. Each one is isolated — they don't see each other's files or processes.

# List images you've downloaded
docker images

# List running containers
docker ps

# List ALL containers (including stopped ones)
docker ps -a

Running a Real Service: Nginx in Docker

Let's run something useful — an Nginx web server:

# Run Nginx in the background, map port 8080 to container's port 80
docker run -d -p 8080:80 --name my-nginx nginx

Now open http://localhost:8080 — you'll see the Nginx welcome page.

Breaking down the command: - -d = detached mode (run in background) - -p 8080:80 = map host port 8080 to container port 80 (Nginx's default) - --name my-nginx = give it a friendly name - nginx = the image name (pulled from Docker Hub automatically)

# Check it's running
docker ps

# Stop it
docker stop my-nginx

# Start it again
docker start my-nginx

# Remove it completely
docker rm my-nginx

What Are Docker Images Made Of? (Dockerfile)

Every Docker image is built from a recipe called a Dockerfile. Here's a simple one for a Python app:

# Start from Python 3.12
FROM python:3.12-slim

# Set the working directory inside the container
WORKDIR /app

# Copy dependency list and install
COPY requirements.txt .
RUN pip install -r requirements.txt

# Copy the rest of the application
COPY . .

# Tell Docker which port the app uses
EXPOSE 8000

# The command that runs when the container starts
CMD ["python", "app.py"]

Build and run it:

docker build -t my-python-app .
docker run -d -p 8000:8000 my-python-app

Each line in the Dockerfile creates a layer. Docker caches layers — if you only change your app code, it won't re-install dependencies. This makes rebuilds fast.

Docker Compose: Running Multiple Containers

Real apps often need multiple services. A web app might need: app server + database + Redis cache.

Docker Compose lets you define all of them in one file and start them together:

# docker-compose.yml
version: "3"
services:
  web:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      - db

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secretpassword
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

One command starts everything:

docker-compose up -d

One command stops everything:

docker-compose down

Practical Use Cases for Beginners

1. Learn without messing up your system

Want to try PostgreSQL? Instead of installing it directly:

docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=test postgres:16

Play with it, then docker rm -f when you're done. Your main system stays clean.

2. Run multiple projects on one cheap server

I run 4 services on a single 1GB RAM VPS, each in its own container — no conflicts, no "it works on the server but not on my machine."

3. Reproducible development environments

New teammate? They run docker-compose up and have the exact same database version, Python version, and config as everyone else.

4. Self-hosted apps

A huge ecosystem of ready-to-use containers: Pi-hole (ad blocking), Nextcloud (file sync), Home Assistant (smart home), Plex (media server), and hundreds more.

Essential Docker Commands Cheat Sheet

# ── Images ──
docker images                     # List local images
docker pull <image>               # Download an image
docker rmi <image>                # Delete an image

# ── Containers ──
docker run -d -p 3000:80 <img>    # Run container (detached, mapped port)
docker ps                         # Running containers
docker ps -a                      # All containers
docker stop <container>           # Stop (graceful)
docker kill <container>           # Kill (force)
docker start <container>          # Restart a stopped container
docker rm <container>             # Delete a container
docker rm -f <container>          # Force-delete (even if running)

# ── Debugging ──
docker logs <container>           # View container logs
docker logs -f <container>        # Follow logs (live tail)
docker exec -it <container> bash  # Open a shell inside the container

# ── Cleanup ──
docker system prune -a            # Remove ALL unused images, containers, networks

Next Steps

Once you're comfortable with running containers, the natural next step is:

  1. Write a Dockerfile for your own project
  2. Use Docker Compose to run your project + database together
  3. Deploy to a VPS — build on your machine, push to Docker Hub, pull on the server

For command-line practice, check out our Linux Terminal Basics guide — knowing the terminal well makes Docker much smoother.

Docker turned me from "too scared to touch my server" to "let me spin up a new service in 30 seconds." It's the single most valuable skill I've learned for self-hosting on a budget.