[Software] Docker Compose for Beginners: Run Your First Three Self-Hosted Apps

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:

  1. Remove conflicting packages first. Docker lists the
    unofficial packages that conflict, including docker.io and
    docker-compose, and gives a command to remove them.
    Immich’s docs also warn that the wrong Docker version causes errors such
    as unknown shorthand flag: 'd' in -d, which happens with
    the older docker.io package on Ubuntu 22.04.3.
  2. 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, which
pins 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 folder
inside the container (/app/data).
ports: Open a door from your computer to the container, as
host: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:

  1. Create a folder, for example immich-app.
  2. Download docker-compose.yml and
    example.env from Immich’s latest release page, and rename
    example.env to .env.
  3. Edit .env. Set UPLOAD_LOCATION to a new
    directory with plenty of free space, change DB_PASSWORD
    (use only the characters A-Z, a-z and 0-9, Immich advises), and set your
    timezone.
  4. 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
    2283
  • immich-machine-learning, a separate container for the
    machine-learning features, with a named volume called
    model-cache
  • redis, which uses a Valkey image
  • database, a PostgreSQL image with your data stored at
    the DB_DATA_LOCATION path

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 .env file in the same folder. That keeps
    passwords out of the YAML.
  • depends_on. The server lists
    redis and database, so Compose creates those
    first and removes them last.
  • The database is not published. Only the server has
    a ports entry. 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 docker group
    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 :release always moves.
    Choose deliberately.
  • Do not store secrets in a public repository. Keep
    .env out 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.

By Early Adopted Editorial Team. Last updated: November 10,
2026

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top