Docker Compose for Normal People: Run Home Services Without Container Chaos
Docker Compose is a repeatable way to describe a small service stack. The important parts are not YAML tricks. The important parts are stable folders, clear volumes, documented ports, safe environment files, update notes, and backups.
Design principle: Prefer a design that is easy to validate and recover over one that looks impressive but is hard to operate.
The Short Version
- Docker Compose is a repeatable way to describe a small service stack. The important parts are not YAML tricks. The important parts are stable folders, clear volumes, documented ports, safe environment files, update notes, and backups.
- The practical decision is operational, not cosmetic: choose the path you can document, test, maintain, and recover.
- Use the decision matrix below, then prove the result with the validation checklist before making it the default.
Why This Matters Now
A long docker run command can launch a container, but it is easy to lose the exact ports, mounts, environment, and restart policy later. A Compose file stores that intended runtime configuration in reviewable YAML. It can be version-controlled without secrets, compared before an update, and executed again on a replacement host.
Compose does not back up data, patch the host, validate an image, or make a database crash-consistent. It describes how containers should run. The operator still owns image trust, persistent storage, network exposure, secrets, health checks, updates, and recovery.
Use one Compose project per operational unit. A media stack may contain several tightly coupled services, while DNS and a password manager deserve separate projects so one update does not restart unrelated household infrastructure.
Recommended Baseline
Use a supported Docker Engine and the current Compose plugin on a patched Linux host. Keep each project under /srv/containers, bind management ports to localhost or a trusted interface, use explicit persistent mounts, and record an image tag or digest before every change. Run one stack at a time and keep databases on storage that supports their locking and durability requirements.
Compose Mental Model
A Compose file describes services, networks, ports, volumes, and settings. It turns a long run command into something you can review and repeat.
Treat it like documentation. A future you should know why each port and volume exists.
Volumes, Bind Mounts, And Permissions
Persistent data must live outside the disposable container layer. Bind mounts are easy to inspect; named volumes are tidy but need backup awareness.
Map user IDs and permissions carefully. Permission errors are one of the fastest ways to make a simple stack feel broken.
A Small Compose File You Can Explain
This Nginx example is deliberately simple. It publishes a local-only test port, mounts site content read-only, keeps runtime scratch paths temporary, adds a health check, and uses a dedicated default project network. The stable-alpine tag moves over time, so record its digest after pulling or replace it with a reviewed immutable digest for a controlled deployment.
services:
web:
image: docker.io/library/nginx:stable-alpine
ports:
- "127.0.0.1:8080:80"
volumes:
- ./site:/usr/share/nginx/html:ro
read_only: true
tmpfs:
- /var/cache/nginx
- /var/run
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1/ >/dev/null || exit 1"]
interval: 30s
timeout: 5s
retries: 3
services names the containers in the project. image identifies what code runs. The three-part port mapping means host address, host port, then container port; binding to 127.0.0.1 prevents direct LAN exposure. The bind mount makes ./site persistent and inspectable on the host. :ro and read_only limit writes, while tmpfs restores only the paths Nginx needs to write at runtime.
cd /srv/containers/web
mkdir -p site
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100
curl -fsS http://127.0.0.1:8080/
docker compose images
ss -ltnp
config --quiet validates the merged Compose model without printing interpolated values. pull retrieves the image before the maintenance event. up -d reconciles the running project in the background. ps and logs check state and startup errors. The local curl exercises the service, while ss confirms which host addresses and ports are actually listening.
Environment Files and Secrets
A .env file is convenient variable input, not a secrets vault. Restrict its permissions, exclude it from public repositories, back it up securely, and rotate credentials after exposure. Be careful with docker compose config because normal output can include resolved values. Use Compose secrets when an image supports file-based credentials, and grant each service only the secrets it needs.
Treat every Compose file as code with host-level consequences. Bind mounts can expose host files, privileged: true removes important isolation, host network or PID modes share namespaces, device mappings expose hardware, and mounting /var/run/docker.sock effectively grants control of the Docker host. Read unfamiliar fields before running a file from the internet.
Updates And Rollback
Do not update every container blindly. Read release notes for important apps, take an application-consistent backup, record docker compose images and the current image digest, then pull and recreate one project. Verify logs, health, data, authentication, and one real workflow before moving to another stack.
Rollback may require three pieces: the previous Compose file, the previous image digest, and pre-upgrade application data. Reusing the old image without restoring a database changed by a migration can make recovery worse. Follow the application's downgrade instructions rather than assuming containers make schema changes reversible.
Decision Matrix
| Compose Field | Plain-English Meaning | Common Risk |
|---|---|---|
| image | The container build you will run. | Floating latest tags can change unexpectedly. |
| ports | What the host exposes. | Publishing admin ports broadly. |
| volumes | Where persistent data lives. | Losing data because it stayed inside the container. |
| environment | Settings and secrets. | Committing secrets to notes or repos. |
Preflight Worksheet
Complete this for each Compose project. It turns the YAML into an operating record and exposes missing recovery details before the service holds important data.
| Item | Record | Recovery Use |
|---|---|---|
| Project identity | Folder, Compose project name, owner, repository, and documentation URL. | Finds the intended configuration instead of reconstructing it from running containers. |
| Images | Registry, version tag, digest, release notes, and update date. | Identifies the exact artifact needed for rollback. |
| Persistent data | Every bind mount and named volume, including database type and consistency method. | Defines what must be backed up and restored. |
| Exposure | Host bind address, port, reverse-proxy hostname, firewall rule, and authentication owner. | Shows whether the service is local, LAN-only, VPN-only, or public. |
| Secrets | Secret names, storage location, rotation method, and dependent services without recording values. | Allows credentials to be recovered or rotated without leaking them into notes. |
| Acceptance test | Health command, real user workflow, log check, backup artifact, and restore result. | Separates a running container from a working and recoverable service. |
A Compose Layout That Survives Rebuilds
Use a boring folder layout and stick to it. One service stack per folder keeps updates, restores, and permissions understandable:
/srv/containers/
web/
compose.yaml
.env
data/
backups/
README.md
Keep .env files private, write down ports and reverse-proxy hostnames, and back up bind mounts or named volumes intentionally. Update with a rollback path: pull, restart one stack, check logs, test the app, then move to the next stack.
Back Up Persistent Data Safely
Start with docker compose config --volumes and the rendered service mounts so no writable path is missed. Bind mounts can be copied with normal filesystem tools when the application is stopped or supports snapshots. Named volumes need an explicit export method and restore instructions. A backup of compose.yaml alone contains configuration, not the files stored in volumes.
Use application-aware dumps for PostgreSQL, MariaDB/MySQL, and other databases. Copying live database files can produce an inconsistent backup even when the copy command succeeds. For file-backed SQLite applications, stop the writer or use the application's documented online backup method. Store backups outside the Docker host, encrypt secret-bearing archives, and test a restore into a separate project name and port.
Update One Project With a Rollback Point
cd /srv/containers/web
docker compose config --quiet
docker compose images
docker image inspect docker.io/library/nginx:stable-alpine \
--format '{{index .RepoDigests 0}}'
# Take the application-consistent backup here.
docker compose pull
docker compose up -d --remove-orphans
docker compose ps
docker compose logs --since=10m
curl -fsS http://127.0.0.1:8080/
If validation fails before any data migration, restore the previous Compose file and recreate the service from the recorded digest. If the application changed persistent data, stop it and follow its documented downgrade or database-restore process first. Never run old and new writers against the same database to make rollback look faster.
Evidence To Keep
- The validated Compose file with secrets excluded and permissions recorded.
- The image registry, tag, digest, and release notes used for the change.
docker compose ps, relevant startup logs, and the real workflow check.- The backup filename, timestamp, database-consistency method, off-host destination, and restore result.
ss -ltnpoutput or a firewall check showing only intended host exposure.- The rollback command and any application-specific downgrade restriction.
This guide is documentation-backed. It does not claim that TechGeeks deployed the example, measured performance, captured screenshots, or restored a production database. A healthy container and successful HTTP request do not prove data integrity, authentication, authorization, or recovery.
When Compose Is the Wrong Tool
- Use a normal package or systemd service when the application is simpler and better supported that way.
- Use a virtual machine when the workload needs a separate kernel, stronger isolation boundary, or vendor appliance image.
- Use orchestration designed for multiple nodes when automatic rescheduling, distributed secrets, rolling deployment, or cluster networking is a real requirement.
- Do not run an untrusted container merely because its Compose file is short. Review image ownership, update history, required privileges, mounts, networks, and devices.
Validation Checklist
- Run
docker compose config --quietto catch model and interpolation errors without printing resolved configuration. - Restart the stack and confirm named volumes and bind-mounted data persist.
- Review
docker compose logsand the application's own health state after startup. - Restore a data backup into an isolated project and execute one real read/write workflow.
- Confirm only intended ports and host addresses are listening.
- Reboot the Docker host and verify restart order, dependencies, and health.
Common Mistakes
- Using
latesteverywhere with no rollback plan. - Putting database files on unreliable network shares.
- Publishing admin interfaces to the LAN or internet without intent.
- Losing secrets because
.envwas not backed up. - Assuming deleting a container deletes all risk or all data.
Troubleshooting
| Symptom | Likely Cause | First Check |
|---|---|---|
| Container restarts repeatedly | Bad environment, unavailable dependency, permission error, or failing health command. | Run docker compose ps and docker compose logs --tail=200 web. |
| Data disappears after recreate | The application wrote into the disposable container layer or the wrong host path. | Inspect docker compose config privately and docker inspect mounts. |
| Bind mount shows permission denied | Host ownership, numeric UID/GID, SELinux label, or read-only mode does not match the image. | Check the image's documented runtime user and host path permissions. |
| Service works locally but not through proxy | Wrong bind address, Docker network, proxy target, firewall, or forwarded-header setting. | Test the container from the host, then test each proxy hop separately. |
| Rollback image will not start | A newer release migrated persistent data. | Stop writers and restore the pre-upgrade database using the app's downgrade procedure. |
Maintenance Cadence
Schedule maintenance per project so a quiet stack does not drift indefinitely and every stack is not recreated at once.
- Weekly: Check failed health states, restart loops, host disk pressure, and backup-job errors.
- Monthly: Review image release notes, host and Docker updates, exposed ports, expiring certificates, and unused containers or networks.
- Quarterly: Restore one database or volume into an isolated project and verify a real workflow.
- Yearly: Rebuild one low-risk project from its Compose file and recovery notes on a clean host or VM.
Prune images only after rollback windows expire. Never run broad docker system prune --volumes as routine cleanup; named volumes may contain the only copy of application data.
When To Spend Money
Buy hardware after measuring the host constraint. Container counts alone do not size a system; CPU bursts, memory working set, database writes, transcodes, network interfaces, and backup windows do.
| Signal | First Response | Hardware Case |
|---|---|---|
| Docker root filesystem is filling | Find logs, build cache, old images, and misplaced writable data. | Add storage only after retention and volume layout are corrected. |
| Database latency rises during backups | Use application-aware backups, schedule them, and inspect storage latency. | Move databases to reliable SSD storage when measurements show an I/O bottleneck. |
| Updates or transcodes saturate CPU | Record per-container CPU and separate batch work from interactive services. | Upgrade when the normal peak workload cannot meet the service target. |
| One host outage stops critical services | Improve backups and replacement-host notes first. | Buy a spare or second host only with a tested placement and recovery plan. |
Useful Gear And Buyer Notes
The product links below are intentionally search links, starting with Intel N100 mini PC Docker server, because model numbers, bundles, and prices change quickly. Use them to compare categories, then verify exact specifications against the article's decision points before buying. For infrastructure gear, prioritize firmware support, replaceability, warranty, idle power, and recovery behavior over headline specs.
Affiliate disclosure: As an Amazon Associate, TechGeeks may earn from qualifying purchases. The product links below are buying references, not a requirement to buy a specific brand or seller. Verify compatibility, seller quality, warranty, and current specs before ordering.
- Amazon search: Intel N100 mini PC Docker server
- Amazon search: 2TB NVMe SSD
- Amazon search: USB 3.2 SATA SSD enclosure
- Amazon search: UPS USB Linux
- Amazon search: label maker network cables
Related TechGeeks resources
- Linux and Homelab Notes: Start Here
- Docker Volume and Database Backup Guide
- Homelab Backup Strategy: Restore Tests, NAS, and Offsite Copies
- Beginner Proxmox Home Server Build
- Monitoring and Health Checks for a Plex and Arr Homelab
- Homelab VLAN Design: Simple Network Segmentation That Works
What This Does Not Protect or Validate
Compose improves repeatability; it does not verify that an image is trustworthy, isolate containers from a vulnerable shared kernel, encrypt secrets, create consistent backups, or provide high availability across hosts. Docker socket access, privileged mode, host namespaces, writable bind mounts, and exposed devices can collapse expected isolation.
Image tags, Compose fields, Docker Engine behavior, and product prices can change. Check current Docker documentation and image release notes before deployment. The examples do not prove performance, compatibility, or recovery for another application.
Practical FAQ
How should a normal person organize Docker Compose files?
Use one directory per project under a consistent parent path. Keep compose.yaml, a private .env when needed, bind-mounted data, backup notes, and a short README together. Keep actual secrets and backup archives out of the configuration repository.
Where should .env files, secrets, volumes, and backups live?
Store .env beside the Compose file with restrictive permissions when the variables are not better handled as secrets. Put bind mounts under the project or a documented data root; inspect named volumes with docker volume inspect. Send encrypted, application-consistent backups to a target outside the Docker host.
How do I update without breaking every self-hosted service at once?
Update one project during a maintenance window. Record image digests, back up persistent data, pull, recreate, inspect logs, and run a real workflow. Stop and restore the previous image plus compatible data if validation fails.
References
- Docker Compose documentation
- Install the Docker Compose plugin on Linux
- Compose file reference
- Docker Compose trust model
- Manage secrets securely in Docker Compose
- Docker bind mounts
- OWASP Docker Security Cheat Sheet
Final Thought
Compose is not magic. It is a service runbook you can execute. Keep it readable, back up the data, and update with evidence.
Need help applying this?
Bring TechGeeks into the real environment.
If you are working through this on a live network, WordPress site, Linux server, AI workflow, or PisoWiFi deployment, send the context and we can help turn it into a practical plan.

