Docker Homelab
Three stacked blue shipping containers cabled to a white mini PC, two-antenna router and small drive on a cream base, an isometric self-hosted server stack.
guides

Docker Compose Setup: First Self-Hosted Stack

This guide covers installing Docker, creating a Docker Compose file, deploying Vaultwarden, managing the stack, and adding Pi-hole.

By Docker Homelab Editorial · ·Updated · 5 min read

Docker Compose is how most self-hosted services are deployed on home servers. Instead of typing long docker run commands every time, you write a simple YAML file describing your services, and Docker handles the rest. Once you run more than one service, the part that trips people up is networking; our Docker Compose networking guide covers it from first principles.

This guide walks through installing Docker, understanding the basics of Compose, and running your first stack. By the end, you’ll have a working Vaultwarden (self-hosted password manager) instance running, and you’ll understand the pattern well enough to add other services.

Once you grasp the pattern, our interactive compose stack builder lets you pick apps from a catalog and generates one valid docker-compose.yml with shared Postgres and Redis merged and your reverse proxy auto-wired, so you skip the copy-paste-and-debug stage entirely.

Prerequisites

  • A Linux server (Ubuntu 22.04 or Debian 12 recommended). This can be a mini PC, a Raspberry Pi, a cloud VPS, or anything else.
  • SSH access or a keyboard and monitor attached.
  • Basic comfort with a terminal.

These instructions assume Ubuntu. Adjust package manager commands for other distros.

Step 1: Install Docker

Docker provides an official install script that handles everything:

curl -fsSL https://get.docker.com | sh

After it finishes, add your user to the docker group so you don’t need sudo for every command:

sudo usermod -aG docker $USER

Log out and back in for the group change to take effect, then verify the install:

docker run hello-world

You should see a success message. Docker is working.

Docker Compose is included with modern Docker installs as docker compose (with a space). You can verify with:

docker compose version

Step 2: Understand the Basic Compose File Structure

A Docker Compose file (docker-compose.yml) describes one or more services. Here’s the minimal structure:

services:
  service-name:
    image: image-name:tag
    container_name: my-container
    ports:
      - "host-port:container-port"
    volumes:
      - ./local-path:/container-path
    environment:
      - VARIABLE=value
    restart: unless-stopped

Key concepts:

image — The Docker image to use. Pulled from Docker Hub by default.

ports — Maps a port on your host machine to a port inside the container. "8080:80" means your host port 8080 reaches container port 80.

volumes — Mounts a directory from your host into the container. This is how data persists when the container restarts. The data lives on your host at ./local-path.

environment — Environment variables passed into the container. Used for configuration (database passwords, secret keys, etc.).

restart: unless-stopped — Tells Docker to restart the container automatically if it crashes or if the host reboots, unless you explicitly stop it.

Step 3: Create Your First Stack

We’ll set up Vaultwarden, a self-hosted Bitwarden-compatible password manager. It’s a good first service because it’s widely used, has one container, and demonstrates the pattern clearly.

Create a directory for your services:

mkdir -p ~/services/vaultwarden
cd ~/services/vaultwarden

Create the Compose file:

nano docker-compose.yml

Paste this content:

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    volumes:
      - ./data:/data
    ports:
      - "8080:80"
    environment:
      - WEBSOCKET_ENABLED=true
      - SIGNUPS_ALLOWED=true
    restart: unless-stopped

Save and exit (Ctrl+X, Y, Enter in nano).

Step 4: Start the Stack

From the directory containing your docker-compose.yml:

docker compose up -d

The -d flag runs the containers in the background (detached mode). Docker will pull the Vaultwarden image and start the container.

Check that it’s running:

docker compose ps

You should see the vaultwarden container with a status of “Up”.

Step 5: Access the Service

The Vaultwarden web vault relies on the browser’s Web Crypto API, which browsers only expose in a secure context: HTTPS or localhost. Opening http://your-server-ip:8080 from another machine loads the page, but account registration fails. For this first test, reach the vault through an SSH tunnel so your browser sees localhost. On your own machine, run:

ssh -L 8080:localhost:8080 <your-server>

Leave that session open, then open http://localhost:8080 in a browser on the same machine. You should see the Vaultwarden web vault. Access from other devices on your LAN needs the HTTPS reverse proxy covered in the Vaultwarden Docker Compose guide.

Create an account. This is your personal instance — you’re the only one on it, so the initial account you create has full access. The full walkthrough for that service is self-host Vaultwarden with Docker Compose.

Step 6: Common Operations

View logs:

docker compose logs -f

The -f flag follows the log output in real time. Useful for troubleshooting.

Stop the stack:

docker compose down

This stops and removes the containers. Your data is preserved in the ./data volume.

Start again:

docker compose up -d

Update to a newer image:

docker compose pull
docker compose up -d

This pulls the latest image and restarts the container.

Step 7: Adding More Services

The power of Compose is running multiple services in the same file. Before you add Pi-hole on Ubuntu, free port 53: systemd-resolved already holds it, and the port 53 section of the Pi-hole Docker Compose guide shows how. Here’s a Compose file that adds Pi-hole alongside Vaultwarden:

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    volumes:
      - ./vaultwarden-data:/data
    ports:
      - "8080:80"
    environment:
      - WEBSOCKET_ENABLED=true
    restart: unless-stopped

  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8081:80"
    environment:
      - FTLCONF_webserver_api_password=changethis
      - FTLCONF_dns_listeningMode=ALL   # answer LAN clients on a bridge network
    volumes:
      - ./pihole-etc:/etc/pihole
    restart: unless-stopped

Note that we’ve put each service’s data in its own subdirectory (./vaultwarden-data, ./pihole-etc, etc.) to keep things organized.

What’s Next

Once you’re comfortable with this pattern, the next steps are:

HTTPS/TLS — Vaultwarden requires HTTPS for the browser extension to work. A reverse proxy like Caddy or Nginx Proxy Manager handles this, and both run in Docker. The same proxy also retires the port numbers once you outgrow them; Nginx Proxy Manager is the gentlest starting point. This is worth setting up before relying on Vaultwarden for your actual passwords.

Automated backups — Your data directories need to be backed up. A simple cron job that copies ~/services to an external drive or cloud storage is enough for most cases.

More services — The same pattern applies to Nextcloud, Jellyfin, Home Assistant, and everything else in the homelab ecosystem. Find the Docker image, find or write a Compose file, run it, or start from all self-hosted service walkthroughs on this site. For a shortlist rather than a search, the best Docker containers for a home server roundup covers what each service is for, what it costs in RAM, and which ones are worth running first.

The Compose file is a configuration file you can version control, share, and restore from. That’s the real advantage over running containers manually.

Sources

  1. Docker Docs: Install Docker Engine
  2. Docker Docs: Compose file reference
  3. Vaultwarden Wiki: Using Docker Compose
  4. Pi-hole Docker Documentation

Related