Homeio
⌘KDocker home server

Set up a Docker home server, step by step

Install Docker on Debian or Ubuntu, give every app its own folder, run your first Compose stack, and set up updates and backups before you need them. Every command on this page can be copied.

Updated

Homeio
Homeio Docker app store running on a home server

What you need before you start

A Docker home server is one Linux machine running your self-hosted apps as containers. It doesn't need to be special hardware. A used office mini PC or an old desktop is the most common starting point, because it is quiet, cheap to run, and has enough CPU for several apps at once. A Raspberry Pi 4 or 5 works too, as long as the apps you want publish arm64 images, which most popular ones do.

  • 8 GB of RAM is comfortable for five to ten typical apps. 4 GB is enough for light services like Pi-hole, Vaultwarden, and Syncthing. Photo apps with machine learning, such as Immich, want more.
  • An SSD for the operating system and app data. Databases on a spinning disk are slow. Media libraries can live on larger hard drives.
  • Wired Ethernet. Wi-Fi works, but streaming and backups are the first things to suffer on it.
  • A fixed address on your network. Set a DHCP reservation in your router so the server keeps the same IP. Every bookmark and app setting will point at it.
  • Debian stable or the current Ubuntu Server LTS. Both are well supported by Docker and by every guide you will read later. Skip the desktop environment; you won't need it.

If you have a spare machine and are unsure, start with it. Moving a well-organised Docker setup to new hardware later is mostly copying folders, which is one of the main reasons to use Docker in the first place.

Install Docker and Docker Compose

Install Docker from Docker's own repository rather than the distribution package, so you get a current Engine and the Compose plugin together. Docker's convenience script does that in one step on Debian, Ubuntu, and Raspberry Pi OS:

Install Docker Engine and the Compose pluginbash
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

Then let your user run Docker without sudo. Log out and back in afterwards so the new group applies.

sudo usermod -aG docker $USER

Membership of the docker group is equivalent to root on that machine: anyone in it can start a container that mounts the whole filesystem. Only add accounts you would trust with sudo.

Check that both the Engine and Compose work:

docker run --rm hello-world
docker compose version

Note the space in docker compose. The old standalone docker-compose binary is deprecated, and guides that still use it translate one-to-one.

Give every app its own folder

The decision that saves you the most pain later is where things live. Keep one folder per app, holding that app's compose.yaml, its .env file, and its configuration. Keep bulk data such as media and photos somewhere separate.

A layout that stays easy to back uptext
/opt/stacks/
  jellyfin/
    compose.yaml
    config/
  immich/
    compose.yaml
    .env
    postgres/
/srv/data/
  media/
  photos/
  backups/

With this layout, backing up the server means backing up two trees: /opt/stacks for everything that describes and configures your apps, and /srv/data for the files they serve. Rebuilding on a new machine means installing Docker, copying both trees back, and running docker compose up -d in each folder.

That is also why this guide uses bind mounts (a host folder such as ./config mapped into the container) rather than named volumes. Named volumes work, but they live under /var/lib/docker/volumes, where they are easy to forget when you set up backups.

sudo mkdir -p /opt/stacks /srv/data/media /srv/data/photos /srv/data/backups
sudo chown -R $USER:$USER /opt/stacks /srv/data

Run your first Compose stack

Jellyfin makes a good first app: it is useful on day one, it shows how ports, volumes, and hardware access work, and it runs happily on modest hardware. Create /opt/stacks/jellyfin/compose.yaml:

/opt/stacks/jellyfin/compose.yamlyaml
services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    ports:
      - "8096:8096"
    volumes:
      - ./config:/config
      - ./cache:/cache
      - /srv/data/media:/media:ro
    devices:
      - /dev/dri:/dev/dri   # Intel or AMD GPU; remove if the machine has none
    restart: unless-stopped
  • ports maps port 8096 on the server to port 8096 in the container. The left side is the host, the right side is the container.
  • volumes keeps Jellyfin's configuration in the app folder, and mounts your media read-only (:ro), so a misbehaving app can't delete your library.
  • devices passes the GPU through for hardware transcoding, which is what lets a small mini PC stream to several screens at once.
  • restart: unless-stopped brings the container back after a crash or a reboot, but respects it when you stop it on purpose.

Start it and watch the logs until it settles:

cd /opt/stacks/jellyfin
docker compose up -d
docker compose logs -f

Press Ctrl+C to leave the logs; the container keeps running. Open http://your-server-ip:8096 and Jellyfin's setup wizard appears. Every other app follows the same pattern: a folder, a compose file, docker compose up -d.

About :latest: it is fine for self-contained apps like Jellyfin. For databases, pin the major version (for example postgres:16), because a major database upgrade needs a migration and should never happen by accident during a routine pull.

Ports, networks, and what not to expose

Compose creates a private network for each stack, and containers on it reach each other by service name. When an app's compose file has a db service, the app connects to db:5432, and that database never needs a port on the host at all. Only publish the port you actually open in a browser.

One trap catches almost everyone: ports published by Docker bypass UFW. Docker writes its own firewall rules, so a ufw deny 5432 does not protect a database you published with 5432:5432. If a service must be reachable from the host but not from your network, bind it to localhost instead:

    ports:
      - "127.0.0.1:5432:5432"

Two more rules keep a first server safe:

  • Don't forward ports on your router to reach apps from outside. Use a mesh VPN such as Tailscale for your own devices, or a tunnel such as Cloudflare Tunnel for the one app you want to share publicly.
  • Once you have more than a handful of apps, put a reverse proxy (Caddy, Nginx Proxy Manager, or Traefik) in front of them, so you get names like jellyfin.home.lan and HTTPS instead of a list of port numbers.

Keep passwords out of the compose file

Compose reads a .env file in the same folder automatically and substitutes its values into compose.yaml. Put database passwords and API keys there, so the compose file itself can be shared, pasted into a forum post, or kept in Git without leaking anything.

/opt/stacks/immich/.envbash
DB_PASSWORD=change-me-to-something-long
UPLOAD_LOCATION=/srv/data/photos
    environment:
      DB_PASSWORD: ${DB_PASSWORD}

Restrict who can read it with chmod 600 .env, and add .env to .gitignore if the stacks folder is a Git repository.

Update apps on purpose

Updating a Compose app is two commands: pull the new image, then recreate the container from it. Your data survives, because it lives in the mounted folders, not in the container.

cd /opt/stacks/jellyfin
docker compose pull
docker compose up -d
docker image prune -f

The last command deletes the old, now-unused images; skip it and they slowly fill the disk. Whether to automate updates depends on the app. Self-contained apps are safe to update on a weekly schedule. Apps with a database and a fast release cycle, Immich being the usual example, occasionally ship changes that need a manual step, so read their release notes before pulling.

Back up before you trust it with anything

The most common way to lose data on a home server is to set up backups after the first disk failure. Decide what you are protecting before you import the family photos:

  • /opt/stacks: compose files, .env files, and app configuration. Small, and the thing that takes longest to recreate by hand.
  • Databases: dump them rather than copying their data folder while they run. A copied live Postgres folder can restore as a corrupt one.
  • /srv/data: photos, documents, and media. Large, but often the only copy that exists.
Dump a Postgres database that runs in a stackbash
cd /opt/stacks/immich
docker compose exec -T database pg_dumpall -U postgres > /srv/data/backups/immich-$(date +%F).sql

The service is called database in Immich's compose file; use whatever name your app's file gives it. Then copy everything to a second place with a tool built for it, such as restic, Borg, or Kopia, all of which do deduplicated, encrypted backups and can send them off-site. Follow the 3-2-1 rule: three copies, on two kinds of storage, one of them outside your house. Finally, restore one file on purpose. A backup you have never restored is a hope, not a backup.

⌘HWhere Homeio fits

Where Homeio fits on a Docker server.

Everything above works without Homeio, and it is worth doing by hand once so you know what is underneath. Homeio sits on top of the same pieces: its app store installs each app as a Compose stack with its data under /DATA/AppData, it shows containers you started yourself, and it puts logs, updates, scheduled backups, files, and live resource use in one browser tab. Nothing it installs depends on it, so you can go back to plain docker compose at any time.

⌘?FAQ

Docker home server questions.

Is Docker good for a home server?

Yes, for most self-hosted apps. Nearly every popular project publishes an official image and a Compose example, apps are isolated from each other, and a well-organised setup moves to new hardware by copying folders. The exception is software that expects to own the machine: Home Assistant's container install, for example, does not include the add-on store that Home Assistant OS has, so some people run HA OS in a virtual machine instead.

Should I use Docker or Proxmox for a home server?

They solve different problems and are often combined. Proxmox runs virtual machines and containers side by side, which is useful when you want to test operating systems or isolate workloads. Docker runs apps. A single-machine home server that only needs apps is simpler with Debian or Ubuntu plus Docker. If you later want VMs, a common setup is Proxmox on the host with Docker inside one VM.

How much RAM does a Docker home server need?

8 GB is comfortable for five to ten typical apps. 4 GB, such as a Raspberry Pi 4, handles lightweight services like Pi-hole, Vaultwarden, AdGuard Home, and Syncthing. Photo libraries with machine learning, large Nextcloud installs, and several simultaneous media transcodes are what push you to 16 GB.

Should I use bind mounts or named volumes?

For a home server, bind mounts to folders you choose are easier to reason about and to back up, because all app data sits in a place you picked. Named volumes are fine technically, but they live under /var/lib/docker/volumes and are easy to leave out of a backup.

Does Homeio replace Docker Compose?

No. Homeio installs apps as ordinary Docker Compose stacks and reads CasaOS store archives, so you can still open, edit, and run every compose file yourself. It adds a browser interface for installing, updating, and inspecting those stacks, plus files, monitoring, and scheduled tasks.