Self-hosting · Docker & Tailscale

Home server

My self-hosted setup for files, media, photos, and passwords. Everything runs in Docker, with private HTTPS access over Tailscale.

Explore the configuration on GitHub →

What I run

Private access, proper HTTPS

Each service has a Tailscale sidecar: a separate container that joins my private network as its own machine. My devices connect through Tailscale, and MagicDNS gives each service a name.

Tailscale Serve handles HTTPS with a valid Let’s Encrypt certificate and forwards requests to the application over the shared homeserver Docker network. The service Compose files publish no host ports, so these apps are reached through the tailnet rather than a public endpoint.

The addresses follow the pattern https://cloud.<tailnet-domain>, with media, photos, and vault for the other services. HTTPS also lets the Bitwarden apps and browser extension connect to Vaultwarden.

Containers and persistent data

Each application has its own Docker Compose file. They share a bridge network, while bind mounts keep application data and configuration under a configurable DATA_PATH on the host.

That includes ownCloud’s files and configuration, Jellyfin’s configuration and media library, Immich’s uploads and database, and Vaultwarden’s data. Each Tailscale sidecar keeps its state there too, so its identity survives container recreation.

The containers use restart: unless-stopped. Shared environment settings live in a local .env file, with an example checked into the repository.

Managing the stack

A small Makefile wraps Docker Compose so I can start, stop, or update one service at a time. For example, updating Jellyfin pulls its image and brings its stack back up:

make up service=jellyfin
make update service=jellyfin
make down service=jellyfin

This page reflects the configuration in the public repository, reviewed on .