Eight docker run commands in your shell history are a memory test. One Compose file is a document.
The short answer
Docker Compose is a tool for defining and running multi-container applications from a single YAML file, the Compose file [1]. You describe services, networks and volumes once, and one command creates and starts all of them [1][2]. Why use it? The file is a readable, versionable record of how your setup is built. Our assessment: for a homelab with more than one container, Compose is the first tool to learn before anything bigger.
How it works
- The file. The default Compose file is
compose.yaml(preferred) orcompose.ymlin the working directory; the older namesdocker-compose.yamlanddocker-compose.ymlstill work [2]. The file follows the Compose Specification [2]. - Services. A service is an abstract description implemented by running the same container image and configuration one or more times [2]. A web app and its database are two services.
- Networks. Services talk to each other over networks, which establish an IP route between containers [2]. Our assessment: inside one Compose project, services can usually reach each other by service name, so you rarely type addresses.
- Volumes. Services store and share persistent data in volumes [2]. This matters because a container is disposable:
docker compose downremoves containers and networks, but anonymous volumes are not removed by default, and they are not automatically mounted by a laterup. Docker's advice is to use explicit paths as bind mounts or named volumes for data that must persist [4]. - Configs and secrets. The specification also has configs, mounted into containers as files, and secrets, a variant for sensitive data [2]. Our assessment: keep real passwords out of the Compose file you commit.
- Projects. A project is one deployment of the application. Its name groups the resources and isolates them from other installations, so the same file can run twice under different names [2].
- Check before you start.
docker compose configmerges your files, resolves variables and prints the resulting model, and--quietonly validates it [3].docker compose downstops and removes the containers and networks of the project, and with-vit also removes named volumes declared in the file [4]. Read that option twice before you use it. - Published ports. A
portsentry makes a container reachable on the host. Our assessment: write127.0.0.1:8080:80when the service should be reachable only from the host itself, and put a reverse proxy in front for everything else.
A homelab example
The Bytearium homelab runs rack servers (HPE ProLiant DL360 Gen9 and DL380 Gen10) and a QNAP NAS, as of 14 to 18 Oct 2026. We have not recorded which containers run there, so we say nothing about that. A general example for a Minecraft Java server with the community image itzg/minecraft-server, whose documentation shows port 25565, the /data directory for persistent data and the setting EULA=TRUE [5]:
services:
mc:
image: itzg/minecraft-server
ports:
- "25565:25565"
environment:
EULA: "TRUE"
volumes:
- mc-data:/data
volumes:
mc-data:With EULA: "TRUE" you accept Mojang's end user licence agreement, so read it first; that decision is yours [5]. We have not pinned an image tag here and the right tag depends on your version needs (unverified). Run docker compose config --quiet, then docker compose up -d.
Checklist
- Put the Compose file in its own folder with a clear project name.
- Run
docker compose config --quietbefore every first start [3]. - Store persistent data in named volumes or bind mounts [4].
- Pin image tags once the setup works, instead of floating on the default.
- Keep passwords and tokens out of the committed file.
- Publish only the ports that need to be reachable, and bind local-only ones to 127.0.0.1.
- Read
docker compose down -vas "delete my named volumes" [4]. - Keep a backup of the volumes, not only of the file.