- Python 66.8%
- Shell 33.2%
| asmr | ||
| homeassistant | ||
| immich | ||
| media | ||
| minecraft | ||
| music | ||
| nextcloud | ||
| .gitignore | ||
| deploy.sh | ||
| README.md | ||
media-server
this is my personal media server stack, using jellyfin, sonarr, radarr, jellyseerr, and aria2-pro for video, navidrome + nicotine+ for music, and immich (photos, on :2283) with GPU-accelerated transcoding and machine learning. there's also nextcloud (files/calendar/contacts) via the all-in-one image.
if you decide to use this, there are a couple things that you need to do before it'll all be functional:
- I've provided
template.envwith blank entries for each required environment variable in each stack directory. Copy them into.envfiles, and fill out the variables inside. - inside
${BASE_MEDIA_DIR}/media, you should ideally have separate directories for each library. for example, mine:
/mnt/mediastorage/plex/data/media/
├── anime
├── concerts
├── movies
├── music
├── tv
└── vaporwave
SELinux volume labels (:z vs :Z)
This host enforces SELinux, so bind mounts use a relabel suffix. The case matters:
:z(lowercase) — shared label. Use this for any directory mounted by more than one container (e.g. the media library, downloads, and books dirs, which are shared by jellyfin/sonarr/radarr/qbittorrent/calibre/etc, andMUSIC_DIRshared by nicotine+ + navidrome).:Z(uppercase) — private, exclusive label stamped with that one container's MCS categories. Only use it for a dir mounted by a single container (e.g. each app's./data/config/<app>).
Do not put :Z on a shared directory. Uppercase Z relabels the dir to one container's private categories; every other container — and even the same container after a recreate — then gets permission denied and sees an empty folder, which shows up as files "missing" / failed scans. Whenever a new service mounts an already-shared path, give it :z.
Nextcloud AIO is the exception: its mastercontainer uses security_opt: label:disable because it needs the docker socket, and it labels its own child containers.
Nextcloud (nextcloud/)
This stack is not like the others. The compose file starts a single mastercontainer, which then creates and manages every other Nextcloud container (apache, postgres, redis, collabora, talk, ...) through the docker socket. You pick which of those run from the AIO web interface, not from the compose file. Anything you hand-edit on the child containers gets reverted on the next AIO update.
First-time setup, after ./deploy.sh:
- Point a subdomain (e.g.
nextcloud.xdlna.org) at the vps in DNS. - Copy
nextcloud/traefik-vps.ymlinto Traefik's dynamic config on the vps, fix the domain and entrypoint names, reload. - Open
https://<tailscale-ip>:8080— https, not http, and a self-signed cert, so accept the warning. Save the passphrase it shows you; it is displayed exactly once. If you miss it, recover it withdocker exec nextcloud-aio-mastercontainer grep password /mnt/docker-aio-config/data/configuration.json. - Enter the domain from step 1. AIO spins up a throwaway
nextcloud-aio-domaincheckcontainer on port 11000 holding a token, then fetches the public URL and expects that token back. "Your domain does not point to this server" means the round trip failed — usually Traefik, not DNS. A Traefik service pointing at 8080 instead of 11000 produces exactly this, because 8080 is https and the proxy speaks http to it. - Pick optional containers, then start. First run pulls several GB.
- Set
trusted_proxiesto the vps's tailscale IP (command is at the bottom oftraefik-vps.yml).
Two values in nextcloud/.env are effectively permanent once step 5 completes: NEXTCLOUD_DATADIR (moving it later strands your files) and the nextcloud_aio_mastercontainer volume name (AIO's backup resolves it by name). The rest can be changed and re-applied with docker compose up -d.