Tutorials 9 min read

Getting Started with Docker Compose in Your Home Lab for 2026

First Docker Compose stack for your homelab: Docker from the official repository, the Compose v2 plugin, Nginx, MariaDB and LAN-only phpMyAdmin, with valid files and a check at every step.

See our picks
S By Selfhostr Team · no sponsored content
Getting Started with Docker Compose in Your Home Lab for 2026
ⓘ ⓘ This article may contain affiliate links (no extra cost to you, it funds the site). As an Amazon Associate, Selfhostr earns from qualifying purchases. See the disclosure.
⏱️
~45 min
Duration
🎚️
Intermediate
Difficulty
💶
0 € (open source software)
Cost
📦
Docker installed on your server
Key Prerequisites

👍 What we like

  • ✓A single compose.yaml file describes and redeploys the whole stack.
  • ✓Validate the file before starting with docker compose config.
  • ✓Healthcheck: phpMyAdmin only starts once the database is ready.
  • ✓Simple updates with docker compose pull then up -d.

👎 What to watch

  • ✕Requires understanding YAML, Docker volumes and networks.
  • ✕Published ports bypass ufw/firewalld, so publish them carefully.
  • ✕Publishing over HTTPS on the Internet requires an extra reverse proxy.
  • ✕You still have to organize database backups yourself.

🏆 Our picks

Affiliate links · same price for you
Recommended
📦

ASUS Tinker Board S

Voir sur Amazon

Amazon · price and availability on the listing

Essential for Backup and Storage
Synology DS2419+ NAS

Synology DS2419+ NAS

Voir sur Amazon

Amazon · price and availability on the listing

Optimal Performance
Crucial P3 Plus SSD 500GB

Crucial P3 Plus SSD 500GB

Voir sur Amazon

Amazon · price and availability on the listing

See all our recommended gear in the shop.

📑 Contents ▾

This tutorial walks you through building your first Docker Compose stack on a homelab server: an Nginx web server, a MariaDB database and the phpMyAdmin interface to manage it. Every file provided is valid as is. Each step ends with a check that actually tests something. The goal is to learn good habits: the official Docker repository, the docker compose plugin (Compose v2), passwords kept out of the YAML file, separate networks, a healthcheck, and an admin interface that is never exposed to the Internet.

The stack is meant for your local network. Publishing over HTTPS on the Internet is covered separately in the FAQ, because it requires a reverse proxy and some security planning.

Prerequisites

  • A server running Debian 12 or 13, or Ubuntu 22.04, 24.04 or 26.04 LTS, with a sudo account and SSH access from the local network only. These are the versions listed in the Docker documentation at the time of writing: check the list for your version.
  • An amd64 or arm64 architecture for this particular stack. The official mariadb image is only published for amd64, arm64, ppc64le and s390x (there is no 32-bit ARM build).
  • Ports 8080 and 8081 free on the server.
  • No domain name and no port forwarding on your router: nothing will be exposed to the Internet.

Which hardware?

  • ASUS Tinker Board S: this board uses a Rockchip RK3288 SoC (ARMv7, 32-bit) with 2 GB of RAM and 16 GB of eMMC. Docker provides armhf packages for Debian, and the nginx and phpmyadmin images exist for arm32v7. However, there is no 32-bit build of the official mariadb image, so this stack will not start as is on this board. It suits lightweight single-container services, not this example.
  • Synology DS2419+ NAS: this NAS has an Intel Atom C3538 processor (x86-64). On DSM 7.2 or later, the Container Manager package lets you deploy a Compose file from Container Manager > Project > Create. Check in Package Center that Container Manager is available for your model and DSM version, and confirm the exact labels in the built-in help. If you go this route, skip steps 1 to 3 below.
  • Crucial P3 Plus SSD 500GB: this is an M.2 NVMe SSD. It works as the system drive of a mini PC or x86 server with an M.2 NVMe slot. The Tinker Board S has no M.2 slot. The DS2419+ has no built-in one: Synology’s M2D18 expansion card is meant for SSD caching, so check Synology’s compatibility list before buying anything.

Step-by-step installation

Step 1: Remove unofficial packages and add the Docker repository

The distribution packages docker.io and docker-compose conflict with the official packages. The Docker documentation says to remove them first. This command does not delete your existing images or volumes in /var/lib/docker.

sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc | cut -f1)

sudo apt update
sudo apt install -y ca-certificates curl openssl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL "https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg" -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update

$ID is set to debian or ubuntu, so the same block works on both distributions.

Check: the repository must offer the docker-ce package.

apt-cache policy docker-ce | head -n 3

The Candidate: line must show a version, not (none).

Step 2: Install Docker Engine and the Compose plugin

Compose v2 is installed as a plugin (docker-compose-plugin) and run as docker compose, with no hyphen. Do not use the old, manually downloaded docker-compose binary.

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Check:

sudo systemctl is-active docker
sudo docker run --rm hello-world
docker compose version

You should see active, the “Hello from Docker!” message, and version v2.x or later.

Step 3 (optional): Use Docker without sudo

sudo usermod -aG docker $USER
newgrp docker

⚠️ According to the Docker documentation, the docker group grants root-level privileges. Only add trusted accounts. If you skip this step, prefix the docker commands below with sudo.

Check: docker ps must run without “permission denied”.

Step 4: Create the project and the .env file

Passwords go in a .env file, which Compose reads automatically from the project directory, rather than in plain text in the YAML.

mkdir -p ~/homelab/premier-stack/{nginx,site}
cd ~/homelab/premier-stack

cat > .env <<EOF
MARIADB_ROOT_PASSWORD=$(openssl rand -hex 24)
MARIADB_DATABASE=homelab
MARIADB_USER=homelab
MARIADB_PASSWORD=$(openssl rand -hex 24)
EOF
chmod 600 .env

Check:

ls -l .env && grep -c '=' .env

The permissions must be -rw------- and the count must be 4. Never commit this file to a public Git repository.

Step 5: Write the compose.yaml file

compose.yaml is the recommended default name, though docker-compose.yml still works. The web server is published on port 8080 of the local network. phpMyAdmin only listens on 127.0.0.1, so it can’t be reached from the network, let alone from the Internet. MariaDB publishes no port at all.

name: premier-stack

services:
  web:
    image: nginx:stable
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./site:/usr/share/nginx/html:ro
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - frontend

  db:
    image: mariadb:lts
    restart: unless-stopped
    environment:
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:?set it in .env}
      MARIADB_DATABASE: ${MARIADB_DATABASE:-homelab}
      MARIADB_USER: ${MARIADB_USER:-homelab}
      MARIADB_PASSWORD: ${MARIADB_PASSWORD:?set it in .env}
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      start_period: 10s
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - backend

  phpmyadmin:
    image: phpmyadmin:latest
    restart: unless-stopped
    environment:
      PMA_HOST: db
    ports:
      - "127.0.0.1:8081:80"
    depends_on:
      db:
        condition: service_healthy
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  db_data:

The ${VAR:?message} syntax stops Compose with a clear error if the variable is empty. The healthcheck follows the MariaDB documentation example: phpMyAdmin only starts once the database is ready.

Check: the command must finish without errors (exit code 0) and print only the confirmation message.

docker compose config --quiet && echo "compose.yaml is valid"

Step 6: Configure Nginx and the home page

The official image already ships a complete nginx.conf (with the events and http blocks) that loads /etc/nginx/conf.d/*.conf. You only need to provide a server block:

cat > nginx/default.conf <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name _;

    root /usr/share/nginx/html;
    index index.html;
    server_tokens off;

    add_header X-Content-Type-Options "nosniff" always;

    location / {
        try_files $uri $uri/ =404;
    }
}
EOF

cat > site/index.html <<'EOF'
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Homelab</title></head>
<body><h1>My first Docker Compose stack is running</h1></body>
</html>
EOF

You’ll check the syntax in the next step, once the container is running.

Step 7: Start the stack

docker compose up -d
docker compose ps

Check: all three services must be Up, and db should show (healthy) after a few seconds. Then test Nginx:

docker compose exec web nginx -t
curl -sI http://localhost:8080 | head -n 1

Expected output: syntax is ok then test is successful, and HTTP/1.1 200 OK. From another machine on the local network, open http://SERVER-IP:8080.

Also check that the application user can reach its database:

docker compose exec db sh -c 'mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" -e "SHOW DATABASES;"'

The homelab database must appear in the list.

Step 8: Reach phpMyAdmin through an SSH tunnel

Because phpMyAdmin only listens on the server’s loopback interface, use an SSH tunnel from your workstation:

ssh -L 8081:127.0.0.1:8081 user@SERVER-IP

Keep this session open, then open http://localhost:8081 on your workstation. Log in with homelab and the MARIADB_PASSWORD from the .env file.

Server-side check:

ss -tln | grep 8081

Only 127.0.0.1:8081 should appear, never 0.0.0.0:8081.

Verification checks

docker compose ps
docker compose logs --tail=20 web
docker compose logs --tail=20 db

The official image sends Nginx logs to the container’s standard output. Read them with docker compose logs, not in /var/log/nginx on the host. After a server reboot, docker compose ps should show the containers running again, thanks to restart: unless-stopped.

Security and best practices

  • Firewall: the Docker documentation states that ports published by containers bypass ufw and firewalld rules. Only publish what you need, bind admin interfaces to 127.0.0.1, and do not forward ports 8080 or 8081 on your router.
  • Keep sensitive interfaces private: phpMyAdmin, SSH, the Docker socket (/var/run/docker.sock) and SMB shares must never be reachable from the Internet. For remote access, use a VPN (WireGuard, for example).
  • Secrets: keep .env at chmod 600. The MARIADB_* variables are only read on first start, when the volume is empty.
  • Database backup:
docker compose exec -T db sh -c 'mariadb-dump -uroot -p"$MARIADB_ROOT_PASSWORD" --all-databases' > backup-$(date +%F).sql
head -n 5 backup-$(date +%F).sql

The dump header should be displayed. Then copy the file off the server, for example to a NAS.

  • Updates: docker compose pull && docker compose up -d. Read the MariaDB release notes before a major version upgrade.

Troubleshooting common errors

docker-compose: command not found

Compose v2 runs as docker compose, with no hyphen. Check that the docker-compose-plugin package is installed (step 2).

required variable MARIADB_ROOT_PASSWORD is missing a value

The .env file is missing, empty, or in another directory. Run Compose from ~/homelab/premier-stack, then check with docker compose config --environment.

Bind for 0.0.0.0:8080 failed: port is already allocated

Another service is using the port. Find it with sudo ss -tlnp | grep 8080, or replace "8080:80" with a free port.

dependency failed to start: container ... is unhealthy

MariaDB failed its healthcheck. Check docker compose logs db: a full disk or a volume initialized with other credentials are common causes.

The new password in .env is not applied

Initialization variables are ignored when the volume already contains a database. Change the password in SQL. If you are just starting out and the database holds nothing useful, you can delete the volume instead.

⚠️ Destructive: the following command permanently erases all database data. Check the volume name first with docker volume ls.

docker compose down -v

no matching manifest for linux/arm/v7

Your machine is 32-bit ARM (as with the ASUS Tinker Board S): the mariadb image does not exist for this architecture. Use an amd64 or arm64 machine.

FAQ

Can I publish this site over HTTPS on the Internet?

Yes, but only the web service, and never phpMyAdmin. Certbot’s --nginx plugin edits an Nginx installed on the host, so it doesn’t fit this setup. You need to add a containerized reverse proxy that handles ACME certificates (Caddy, Traefik…) to the stack, or run Certbot in certonly --webroot mode with a volume shared with Nginx. Follow the official documentation of the tool you choose.

docker-compose.yml or compose.yaml?

Both names work. compose.yaml is the default name recommended by Docker.

How do I add a service?

Add a block under services:, validate it with docker compose config --quiet, then run docker compose up -d. Only the changed services are recreated.

How do I limit a container’s memory?

Use the deploy.resources.limits key (for example memory: 512M) under the relevant service, then check the result with docker stats.

Read also

Host this yourself

No hardware to manage? This kind of service runs on a managed VPS, ready in minutes. See PlanetHoster plans

Tags: docker-composehomelabnasself-hostingcloud

Related

Tutorials

Recycle an Old PC into a Home Server in 2026

Turn an old PC into a home server in 2026: Ubuntu Server LTS, official Docker, Nextcloud with MariaDB and Caddy for automatic HTTPS, plus a firewall and backups.

Read
Tutorials

Configure Dockge to manage your Docker containers in 2026

Learn how to install and configure Dockge, a lightweight and self-hosted Docker Compose stack manager, perfect for homelabs and self-hosting.

Read
Tutorials

Metal Rack Assembly for Home Lab 2026: Step-by-Step Guide

Discover our comprehensive and detailed tutorial to install a metal rack in 2026. Follow our practical tips, complete configuration files, and exact commands.

Read