Docker Compose lets you describe a whole app — its code, storage, and networking — in one readable file, and bring it up or down with a single command.

It's the pattern behind nearly every modern self-hosting project you'll encounter. Understand this one tool, and installation instructions like "just run docker compose up" stop being magic and start being routine.

The problem Compose solves

Without containers, installing an app on a server means installing its language runtime, its database, its dependencies — all directly onto your system, where versions collide and uninstalls leave debris. Docker packages each app with everything it needs. Compose goes one step further: it defines groups of containers that work together, so a blog plus its database is one unit, not two separate puzzles.

A real example

Here's a complete, working Compose file for Uptime Kuma, the monitoring tool from our essential repos list. One file, one command, fully running app:

# docker-compose.yml
services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    volumes:
      - uptime-kuma-data:/app/data
    ports:
      - "3001:3001"
    restart: unless-stopped

volumes:
  uptime-kuma-data:

Read it top to bottom: pull this image, keep the data in a named volume, publish port 3001, restart automatically if the server reboots. Every Compose file you'll ever meet is variations on those five ideas.

Bring it up

# In the directory holding the file:
docker compose up -d

# See what's running:
docker compose ps

# Read logs when something looks wrong:
docker compose logs -f

Point a browser at http://your-server-ip:3001 and Uptime Kuma is waiting for you. That's the entire installation.

One folder per app, always

The habit that keeps a server sane: every app lives in its own directory with its own compose file.

/opt/
  uptime-kuma/
    docker-compose.yml
  vaultwarden/
    docker-compose.yml
  immich/
    docker-compose.yml

Each project is isolated — its own containers, its own network, its own volumes. Updating or removing one app never touches another.

Updates without fear

docker compose pull      # fetch newer images
docker compose up -d     # recreate only what changed

Because the data lives in named volumes, the containers themselves are disposable. A broken update rolls back the same way: pin the previous image tag, up -d again.

Add your compose folders to your backup routine — the compose files plus the volumes underneath them are your entire app. The backups guide covers exactly how.

What to run first

If you're following the learning path, good first Compose projects are Uptime Kuma (one container, instant gratification), then a two-container app with a database once you're comfortable. Platforms like Coolify build a whole UI on top of this same pattern — the Compose mental model transfers directly.