# Docker Compose for self-hosters: the only tutorial you need

> Docker Compose for self-hosters: install it, then learn compose.yaml, .env files, volumes, networks, restart policies, safe updates and backups with examples.

Published 2026-10-06 by appsgit editors. Category: guides. Tags: docker, docker-compose, self-hosting, beginners, backup, homelab.

**Docker Compose is the tool that runs almost every self-hosted app: you describe the app's containers, storage and networking in one `compose.yaml` file, and `docker compose up -d` starts it.** For self-hosters it replaces long `docker run` commands with a readable file you can version, back up and move to another machine. Learn six concepts (services, `.env` files, volumes, networks, restart policies and updates) and you can deploy nearly anything in our catalog. Each example below links to a full deploy guide that uses the same pattern.

## Install Docker Compose

Compose today is a plugin for the Docker CLI, used as `docker compose` (with a space). The older standalone `docker-compose` binary (with a hyphen) is legacy.

- **Linux:** install Docker Engine from Docker's official repository for your distribution and include the `docker-compose-plugin` package. Your distribution's own `docker.io` package is often older.
- **Windows and macOS:** Docker Desktop includes Compose.

Confirm it works:

```bash
docker compose version
```

Add your user to the `docker` group if you want to run commands without `sudo`, and remember that membership in that group is effectively root access. Our [self-hosting roadmap](https://appsgit.com/blog/how-to-start-self-hosting) covers preparing a fresh server.

## Your first compose.yaml

Create one folder per app. Everything the app needs lives inside it, which makes backups and migrations simple.

```yaml
services:
  uptime-kuma:
    image: louislam/uptime-kuma:2
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - "127.0.0.1:3001:3001"
    volumes:
      - ./data:/app/data
```

Run `docker compose up -d` in that folder. The parts:

- **`services`** lists containers. The key (`uptime-kuma`) is the service name, which also becomes its hostname on the project's network.
- **`image`** is what to run. Pin at least the major version (`:2`) instead of `latest`, so a breaking release never arrives by surprise.
- **`ports`** maps host port to container port. Prefixing `127.0.0.1:` keeps the app reachable only from the server itself, ready for a reverse proxy.
- **`volumes`** keeps data outside the container, so you can delete and recreate the container without losing anything.

This is the same setup our [Uptime Kuma deploy guide](https://appsgit.com/guides/uptime-kuma) builds on.

A note on file names: Compose accepts `compose.yaml` (preferred) and `docker-compose.yml`. You may still see a `version: "3.8"` line at the top of older examples; it is obsolete and can be deleted.

## The .env file: settings and secrets

Compose automatically reads a file named `.env` in the project folder and substitutes its values into `compose.yaml` wherever you write `${NAME}`. This is how most projects separate configuration from the file itself.

```bash
# .env
APP_VERSION=v3
UPLOAD_LOCATION=/srv/photos
DB_PASSWORD=change-me-to-a-long-random-string
```

```yaml
services:
  server:
    image: ghcr.io/example/server:${APP_VERSION:-latest}
    volumes:
      - ${UPLOAD_LOCATION}:/data
    environment:
      DB_PASSWORD: ${DB_PASSWORD}
```

Two related features are easy to confuse:

| Feature | Read by | Purpose |
|---|---|---|
| `.env` file in the project folder | Compose | Fills in `${VARIABLES}` in compose.yaml |
| `env_file:` key on a service | The container | Loads every line of a file into the container's environment |
| `environment:` key | The container | Sets individual variables inline |

Run `docker compose config` to print the final file with every variable resolved; it is the fastest way to debug a typo. [Immich](https://appsgit.com/apps/immich) ships its official setup as a Compose file plus an `.env` file for the upload location, database password and version, so the [Immich deploy guide](https://appsgit.com/guides/immich) is a good real-world example.

**Treat `.env` as a secret.** Restrict it with `chmod 600 .env`, never commit it to Git, and generate passwords with `openssl rand -base64 32`.

## Volumes: where your data lives

Containers are disposable; volumes are not. Compose offers two kinds:

- **Bind mounts** (`./data:/app/data`) map a host folder into the container. You can see and back up the files directly. Most self-hosters prefer them.
- **Named volumes** (`db_data:/var/lib/postgresql/data` plus a top-level `volumes:` entry) are managed by Docker under `/var/lib/docker/volumes`. They avoid some permission issues and are common for databases.

Add `:ro` to mount something read-only, for example a media library that the app should never modify. Whichever you choose, the rule is the same: anything not in a volume is lost when the container is recreated.

## Networks: how containers talk

Every Compose project gets its own default network, and services reach each other by service name. A web app connects to its database at `db:5432`, not at an IP address, and the database needs no published port at all.

```yaml
services:
  webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    depends_on:
      db:
        condition: service_healthy
    environment:
      PAPERLESS_DBHOST: db
  db:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U paperless"]
      interval: 10s
      retries: 5
```

`depends_on` with `condition: service_healthy` makes the app wait until the database actually accepts connections, which avoids a common first-start crash. [Paperless-ngx](https://appsgit.com/apps/paperless-ngx) runs this way with Postgres and a Redis-compatible broker; see the [Paperless-ngx guide](https://appsgit.com/guides/paperless-ngx), and the [Gitea guide](https://appsgit.com/guides/gitea) for a Git server with Postgres.

**Sharing a reverse proxy across projects.** Create one network once (`docker network create proxy`), declare it as `external: true` in each project, and attach both the proxy and each web app to it. [Traefik](https://appsgit.com/apps/traefik) then discovers containers from their labels; the [Traefik guide](https://appsgit.com/guides/traefik) walks through it, and the [Caddy guide](https://appsgit.com/guides/caddy) shows the simpler file-based approach. If you are choosing, read [Caddy vs Traefik](https://appsgit.com/compare/caddy-vs-traefik).

**A firewall warning.** Ports you publish with Docker are opened through Docker's own iptables rules, which bypass UFW on Ubuntu. Publish only what must be public (normally just the proxy on 80 and 443) and bind everything else to `127.0.0.1`.

## Restart policies

| Policy | Behaviour | Use it for |
|---|---|---|
| `no` (default) | Never restarts | One-off tasks |
| `always` | Restarts on exit and on boot, even after you stopped it manually | Rarely what you want |
| `unless-stopped` | Restarts on exit and on boot, unless you stopped it | Almost every self-hosted service |
| `on-failure` | Restarts only after a non-zero exit code | Jobs and workers |

## Everyday commands

```bash
docker compose up -d          # create or update and start in the background
docker compose ps             # status of this project's containers
docker compose logs -f app    # follow one service's logs
docker compose exec app sh    # open a shell inside a running container
docker compose down           # stop and remove containers (volumes stay)
```

`docker compose down -v` also deletes named volumes. Do not type it on a project whose data you want to keep.

## Updating safely

1. Read the release notes for breaking changes, especially major versions.
2. Back up first (see below).
3. Run `docker compose pull`, then `docker compose up -d`. Compose recreates only containers whose image changed.
4. Clean old images occasionally with `docker image prune`.

Fully automatic updaters are convenient, but for apps that hold data, a notification that an update exists is safer than an unattended upgrade.

## Backing up a Compose project

Because each app lives in one folder, backups are straightforward:

- **Files:** back up the project folder: `compose.yaml`, `.env` and every bind-mounted data folder.
- **Databases:** do not copy a live database's files. Dump it instead, for example `docker compose exec -T db pg_dump -U app app > backup.sql`, or stop the stack before copying.
- **Off-site:** send it all to another location with an encrypted, deduplicating tool such as Restic or Kopia, and test a restore regularly.

## Prefer a web UI?

[Dockge](https://appsgit.com/apps/dockge) manages Compose stacks from a browser while keeping the files on disk, and [Portainer](https://appsgit.com/apps/portainer) adds broader container management. Our [Portainer vs Dockge comparison](https://appsgit.com/compare/portainer-vs-dockge) helps you choose.
- [Dockge](https://appsgit.com/apps/dockge): Fancy, easy-to-use manager for Docker compose stacks from the creator of Uptime Kuma. (24,690 stars, MIT, TypeScript, Docker)
- [Portainer](https://appsgit.com/apps/portainer): Web UI for managing Docker, Swarm and Kubernetes environments. (38,722 stars, Zlib, TypeScript, Docker)
Every [deploy guide](https://appsgit.com/guides) on appsgit uses the patterns above. Pick an app from [the 25 best self-hosted apps in 2026](https://appsgit.com/blog/best-self-hosted-apps-2026), make a folder, and write your next `compose.yaml`.

## Apps mentioned

- [Uptime Kuma](https://appsgit.com/apps/uptime-kuma): Easy-to-use self-hosted uptime monitor with status pages and 90+ notification channels. (92,919 stars, MIT, JavaScript, Docker)
- [Immich](https://appsgit.com/apps/immich): Photo and video backup solution directly from your mobile phone (alternative to Google Photos). (117,263 stars, AGPL-3.0, TypeScript, Docker)
- [Paperless-ngx](https://appsgit.com/apps/paperless-ngx): Scan, index, and archive all of your paper documents with an improved interface (fork of Paperless). (47,100 stars, GPL-3.0, Python, Docker)
- [Gitea](https://appsgit.com/apps/gitea): Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD. (58,694 stars, MIT, Go, Docker)
- [Traefik](https://appsgit.com/apps/traefik): HTTP reverse proxy and load balancer that makes deploying microservices easy. (65,373 stars, MIT, Go, Docker)
- [Caddy](https://appsgit.com/apps/caddy): Powerful, enterprise-ready, open source web server with automatic HTTPS. (78,419 stars, Apache-2.0, Go, Docker)
- [Dockge](https://appsgit.com/apps/dockge): Fancy, easy-to-use manager for Docker compose stacks from the creator of Uptime Kuma. (24,690 stars, MIT, TypeScript, Docker)
- [Portainer](https://appsgit.com/apps/portainer): Web UI for managing Docker, Swarm and Kubernetes environments. (38,722 stars, Zlib, TypeScript, Docker)

## FAQ

### How do I install Docker Compose?

On Linux, install Docker Engine from Docker's official apt or dnf repository and include the docker-compose-plugin package. Docker Desktop on Windows and macOS already includes it. Check with docker compose version. The old standalone docker-compose command is legacy.

### What is the difference between .env and env_file in Docker Compose?

A .env file in the project folder is read by Compose itself to fill in ${VARIABLES} inside compose.yaml. The env_file key passes variables from a file into the container's environment. Many self-hosted apps use both: .env for settings like versions and paths, env_file or environment for the app.

### Should I name the file compose.yaml or docker-compose.yml?

Compose accepts both. compose.yaml is the preferred name in Docker's current documentation, and docker-compose.yml still works. The top-level version key is obsolete and can be removed.

### How do I update containers with Docker Compose?

Read the release notes, then run docker compose pull followed by docker compose up -d in the project folder. Compose recreates only the containers whose image changed. Pin major versions in image tags so upgrades happen when you choose.

---

Canonical page: https://appsgit.com/blog/docker-compose-for-self-hosters
Source: appsgit (https://appsgit.com), the app store for github. Data as of 2026-10-26 (GitHub API), refreshed daily.
Descriptions adapted from [awesome-selfhosted](https://github.com/awesome-selfhosted/awesome-selfhosted-data) ([CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/)).
Machine access: JSON API https://appsgit.com/api/v1/apps (OpenAPI: https://appsgit.com/openapi.json), MCP server https://mcp.appsgit.com/mcp, full index https://appsgit.com/llms-full.txt.
