Quick answer: Docker Compose lets you describe an
app in one text file, compose.yaml, and start it with one
command: docker compose up -d. That is why almost every
self-hosted project (Immich, Uptime Kuma, Jellyfin and many more) ships
a Compose file. In this guide you will install Docker Engine, learn the
five parts of a Compose file, and run three real apps: a web server, a
monitoring dashboard and a photo library. The most important habit to
learn on day one: publish ports to 127.0.0.1 unless you
really want the whole network to reach the app.
Self-hosting guides often start with “just paste this YAML” and leave
you guessing what any of it does. Here, we explain each line, so the
next Compose file you find will make sense.
This is a tutorial based on official documentation, not a hands-on
test. We did not run these apps for this article. The commands and file
contents come from the Docker, Immich and Uptime Kuma documentation and
repositories, all checked on November 10, 2026. Project
files change, so always download the current Compose file from the
project instead of copying one from an article, including this one.
What is Docker Compose?
Docker runs software in containers: isolated,
packaged copies of an app and everything it needs. Running one container
is one docker run command with many options. Real apps
often need several containers (a web app, a database, a cache), and the
commands get long and easy to forget.
Docker’s documentation describes Compose as a way to “configure your
application’s services” in a YAML file and then “create and start all
the services” from it with the Compose command line. Compose treats an
app as a set of:
- Services: each service runs a container image, one
or more times. - Networks: private connections between the
services. - Volumes: storage that survives when containers are
replaced. - Configs and secrets: configuration data, and
sensitive data that is passed in as files.
For a beginner, the first three matter. The file is named
compose.yaml by default. Compose also accepts
compose.yml, docker-compose.yaml and
docker-compose.yml, and prefers compose.yaml
if several exist. Many project files still use the older
docker-compose.yml name.
One more thing. Older guides start the file with
version: "3". Docker’s docs say the top-level
version property is obsolete and you will receive a warning
if you use it. You can leave it out.
Step 1: Install Docker Engine
The best-supported route on Linux is Docker’s own apt
repository, not the distribution’s own docker.io package.
As of November 10, 2026, Docker’s install pages list these supported
releases:
- Debian: 13 (Trixie, stable) and 12 (Bookworm,
oldstable) - Ubuntu: 26.10, 26.04 LTS, 24.04 LTS and 22.04
LTS
Docker Engine for Debian supports x86_64 (amd64), armhf, arm64 and
ppc64le, so it also runs on a Raspberry Pi 5 (arm64). Follow Docker’s
current instructions for your distribution, which include steps for
adding the repository, installing the packages and testing with
sudo docker run hello-world. The package list includes
docker-ce, docker-ce-cli,
containerd.io, docker-buildx-plugin and
docker-compose-plugin. The last one is Compose, and it
gives you the docker compose command (with a space).
Two warnings from Docker’s installation page:
- Remove conflicting packages first. Docker lists the
unofficial packages that conflict, includingdocker.ioand
docker-compose, and gives a command to remove them.
Immich’s docs also warn that the wrong Docker version causes errors such
asunknown shorthand flag: 'd' in -d, which happens with
the olderdocker.iopackage on Ubuntu 22.04.3. - Docker bypasses ufw and firewalld. The page says
that when you expose container ports with Docker, “these ports bypass
your firewall rules.” We come back to that below.
Running Docker without sudo
By default, only root can talk to the Docker daemon. Docker’s
post-installation page shows how to add your user to the
docker group (sudo usermod -aG docker $USER,
then log out and back in). It warns that the docker group
“grants root-level privileges to the user.” On a home server you manage,
that is usually an acceptable trade. On a shared machine, use
sudo, or look at Docker’s rootless mode.
Step 2: The anatomy of a
Compose file
Here is the official Uptime Kuma Compose file, which is short enough
to read line by line:
services:
uptime-kuma:
image: louislam/uptime-kuma:2
restart: unless-stopped
volumes:
- ./data:/app/data
ports:
# <Host Port>:<Container Port>
- "3001:3001"
| Line | What it means |
|---|---|
services: |
The list of containers that make up the app. This file has one. |
image: |
The packaged software to run. The :2 is a tag, whichpins the major version. |
restart: unless-stopped |
Restart the container after a crash or a reboot, unless you stopped it yourself. |
volumes: |
Link a folder on your computer (./data) to a folderinside the container ( /app/data). |
ports: |
Open a door from your computer to the container, ashost:container. |
Docker’s reference lists four restart policies: no (the
default), always, on-failure and
unless-stopped.
The volumes line is the most important for a
beginner. A container is disposable: when you replace it with a newer
version, whatever was inside is gone. Anything you want to keep must
live in a volume. A bind mount like ./data is a normal
folder you can see and back up. A named volume is managed by Docker.
Docker’s docs say that for data that must persist between updates, use
explicit paths as bind mounts or named volumes.
Step 3: The commands you need
Run these from the folder that contains your
compose.yaml:
| Command | What it does |
|---|---|
docker compose up -d |
Create and start everything in the background |
docker compose ps |
List the services and their status |
docker compose logs |
Show the output, useful for debugging |
docker compose pull |
Download newer images |
docker compose down |
Stop and remove the containers and networks |
Docker documents up, down,
logs and ps as the key commands. By default,
docker compose down removes only the containers and
networks for the services in your file. Your bind-mounted folders stay
put.
App 1: A web server (learn
the loop)
Start with something harmless. The official nginx image serves static
files from /usr/share/nginx/html. Make a folder, create a
site subfolder containing an index.html file,
and add this compose.yaml next to it:
services:
web:
image: nginx
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
volumes:
- ./site:/usr/share/nginx/html:ro
Run docker compose up -d, then open
http://localhost:8080. The :ro at the end of
the volume makes it read-only inside the container, which the nginx
image documentation uses in its own example. Edit the HTML file and
refresh the page. Then run docker compose down and
docker compose up -d again, and notice that your page is
still there, because it lives in your ./site folder.
Look at the port line. 127.0.0.1:8080:80 means “only
this computer can connect to port 8080.” Docker’s Compose reference
warns that if you do not specify a host IP such as
127.0.0.1, Docker binds to all interfaces, bypassing host
firewall rules, which can expose the container to the internet if the
host has a public IP address. Without the 127.0.0.1, anyone
on your network could reach the page. Remove it only when you want other
devices to connect.
App 2: Uptime Kuma (a
real app, one service)
Uptime Kuma is a self-hosted monitoring tool that watches your sites
and services. Its README says it can monitor HTTP(s), TCP, ping, DNS
records and Docker containers, among other types. Its documented install
is:
mkdir uptime-kuma
cd uptime-kuma
curl -o compose.yaml https://raw.githubusercontent.com/louislam/uptime-kuma/master/compose.yaml
docker compose up -d
The README says Uptime Kuma then runs on all network interfaces, at
http://localhost:3001 or http://your-ip:3001.
For a first install, that is fine on a trusted home network. If you
would rather limit it to your own computer, change the ports line to
"127.0.0.1:3001:3001". The README shows the same idea for
the docker run form.
One more warning from the README: network file systems like NFS are
not supported, so map the data folder to a local directory or
volume.
Once you have several apps on different ports, you may want names
instead of 192.168.1.20:3001. That is the job of a reverse
proxy, which is beyond this beginner guide. For now, a bookmark works.
Our home lab starter guide covers
the wider plan, including how to reach your apps from outside.
App 3: Immich
(several services, one command)
Immich is a self-hosted photo and video library. Its documentation
calls Docker Compose the recommended way to run it. Immich’s install
steps are:
- Create a folder, for example
immich-app. - Download
docker-compose.ymland
example.envfrom Immich’s latest release page, and rename
example.envto.env. - Edit
.env. SetUPLOAD_LOCATIONto a new
directory with plenty of free space, changeDB_PASSWORD
(use only the characters A-Z, a-z and 0-9, Immich advises), and set your
timezone. - Run
docker compose up -d.
Check the requirements first. Immich lists a minimum of 6 GB of RAM
(8 GB recommended) and 2 CPU cores (4 recommended), and runs on amd64
and arm64. If you are still choosing hardware, see our mini PC buying guide.
The Compose file shows how a bigger app is put together. As of the
release we checked, it defines four services:
immich-server, the main app, which publishes port
2283immich-machine-learning, a separate container for the
machine-learning features, with a named volume called
model-cacheredis, which uses a Valkey imagedatabase, a PostgreSQL image with your data stored at
theDB_DATA_LOCATIONpath
You will recognize image, restart,
volumes and ports from earlier. Three new
ideas appear:
${VARIABLE}placeholders. Values like
${UPLOAD_LOCATION}and${DB_PASSWORD}are
filled in from the.envfile in the same folder. That keeps
passwords out of the YAML.depends_on. The server lists
redisanddatabase, so Compose creates those
first and removes them last.- The database is not published. Only the server has
aportsentry. The database talks to the app over the
private network Compose creates, and Immich’s docs say it is not
publicly exposed.
Immich’s docs advise that the database should be on local SSD storage
and never on a network share. Plan your folders with that in mind.
Updating Immich
Immich’s upgrade page gives two lines, run in the folder with your
Compose file:
docker compose pull && docker compose up -d
Read the release notes first, because Immich says breaking changes
are limited to major versions. You can pin to the current major version
with a tag such as v3. The server only works with its
matching major version, and the mobile app is typically compatible with
the current and prior major version.
Keep your setup safe
- Publish only what you need. Use
127.0.0.1:in front of ports for anything that should stay
local. - Do not expose Docker’s socket or
dockergroup
access casually. Both are root-equivalent. - Back up your volumes and your
.env
file. Your photos and databases live outside the containers, so
back those folders up, following our Linux backup guide. - Pin versions on purpose. A tag like
:2
follows a major version. A tag like:releasealways moves.
Choose deliberately. - Do not store secrets in a public repository. Keep
.envout of any Git repo you share.
FAQ
Is it
docker-compose or docker compose?
Use docker compose (a space). It comes from the Compose
plugin that Docker installs. Immich’s docs note that the hyphenated
docker-compose fails on a plain Ubuntu 22.04 install.
Do I need to write the YAML
myself?
Not at first. Download the project’s own file, read it using this
guide, and change only what the project tells you to change, such as
paths and passwords.
Where does my data live?
In the volumes and folders you mapped, not in the containers. If you
delete a container with docker compose down, mapped folders
remain.
Can I run
several Compose projects on one machine?
Yes. Compose names each project (by folder name, or with the
top-level name element) so the same file can be deployed
twice with different names. Just make sure each app uses a different
host port.
Does this work on a Raspberry
Pi?
Docker Engine supports arm64, and Immich lists arm64 as a supported
platform. Check each image’s page for an arm64 build before you
commit.
Is Docker Desktop required?
No. On a Linux server, Docker Engine plus the Compose plugin is
enough. Docker Desktop for Linux is another option that bundles Docker
Engine.
Sources
All checked November 10, 2026.
- Docker Compose
overview - Compose
file reference: services - Compose
file reference: version and name - docker
compose down reference - Install
Docker Engine on Debian - Install
Docker Engine on Ubuntu - Docker
Engine post-installation steps for Linux - Docker:
packet filtering and firewalls - Uptime Kuma
README - Uptime
Kuma compose.yaml - Immich:
Docker Compose install - Immich
requirements - Immich
upgrading - Immich
docker-compose.yml (latest release) - Official
nginx image documentation
By Early Adopted Editorial Team. Last updated: November 10,
2026