Self-hosting Immich, Pi-hole, Jellyfin and Home Assistant on TrueNAS
, 7 min read
Once your TrueNAS server is storing files, it can run your other home services too. This guide installs four popular apps with Docker Compose, then adds Tailscale so that you can reach them away from home without opening any ports.
- Immich: photo and video backup
- Pi-hole: ad blocking for your whole network
- Jellyfin: film, TV and music streaming
- Home Assistant: home automation
TrueNAS also has a catalog with one-click versions of many apps. It's a good option if you don't need control over the configuration, but this guide uses Compose because the files are portable and stay close to each project's own documentation.
Prepare TrueNAS
Choose an apps pool. Go to Apps > Configuration > Choose Pool and select tank. TrueNAS creates an ix-apps dataset there for Docker's own files. Leave that dataset alone and keep your app data elsewhere.
Create datasets. TrueNAS recommends host paths to your own datasets for production data. Go to Datasets and create these with the Apps preset:
tank/apps
tank/apps/immich
tank/apps/immich-db
tank/apps/pihole
tank/apps/jellyfin
tank/apps/homeassistant
tank/apps/tailscale
tank/photos
Check the DNS settings. Once Pi-hole is running, the server shouldn't depend on it for its own DNS. If it did and Pi-hole stopped, TrueNAS couldn't download the images it needs to restart it. Under System > Network > Global Configuration, set the nameservers to your router or a public DNS service.
Learn the install flow. Each app below uses the same steps:
- Go to Apps > Discover, open the menu and select Install via YAML.
- Enter a lowercase name.
- Paste the Compose file into Custom Config and save.
Run the shell commands in this guide from System > Shell. Replace Australia/Sydney with your time zone throughout.
Immich
Immich changes quickly, and its install guide warns that Compose files should match the release. So rather than copying a file from here, download the current one to your server:
cd /mnt/tank/apps/immich
RELEASE=https://github.com/immich-app/immich/releases/latest/download
sudo curl -fsSL -o docker-compose.yml "$RELEASE/docker-compose.yml"
sudo curl -fsSL -o .env "$RELEASE/example.env"
openssl rand -hex 24 # Copy this for DB_PASSWORD
sudo nano .env
Edit these lines in .env:
UPLOAD_LOCATION=/mnt/tank/photos
DB_DATA_LOCATION=/mnt/tank/apps/immich-db
TZ=Australia/Sydney
DB_PASSWORD=paste-the-value-here
Immich only allows letters and numbers in that password, which is why the command above generates hex. Its database should ideally be on a local SSD. If your pool only has hard drives, open docker-compose.yml and uncomment DB_STORAGE_TYPE: 'HDD'.
Because the file and its settings are already on the server, the app you install in TrueNAS only needs to point at them:
include:
- /mnt/tank/apps/immich/docker-compose.yml
Name the app immich. Once it's running, open http://your-server-ip:2283 and create the first account, which becomes the administrator. Then install the mobile app and enter the same address.
Two requirements are worth checking on an old desktop. Immich needs at least 6 GB of RAM, and its machine learning container needs a CPU with x86-64-v2 support. If memory is tight, you can turn machine learning off, but you'll lose smart search and face recognition.
If the database container fails with a permissions error, recreate the empty immich-db dataset with the Generic preset and try again.
Pi-hole
Pi-hole answers your network's DNS requests and blocks known ad and tracking domains. Its official Compose file needs two changes on TrueNAS. The TrueNAS web interface already uses ports 80 and 443, so Pi-hole's web interface moves to 8081. The extra capabilities are also removed, because they're only needed if Pi-hole is your DHCP or NTP server.
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
ports:
- "53:53/tcp"
- "53:53/udp"
- "8081:80/tcp"
environment:
TZ: "Australia/Sydney"
FTLCONF_webserver_api_password: "choose-a-strong-password"
FTLCONF_dns_listeningMode: "ALL"
volumes:
- /mnt/tank/apps/pihole:/etc/pihole
restart: unless-stopped
Open http://your-server-ip:8081/admin to check that it's running, and test it from another computer:
nslookup example.com your-server-ip
If that works, change the DNS server in your router's DHCP settings to the server's IP address. Devices pick up the change when they renew their network lease, or straight away if you reconnect them.
Keep these points in mind:
- The server becomes critical. If it goes down, websites will seem broken on every device. Make a note of how to change the DNS setting on your router, so that you can switch back quickly.
- Check IPv6. If your router gives devices an IPv6 DNS server as well, they can bypass Pi-hole. Check its IPv6 settings if ads still get through.
- Don't expose port 53 to the internet. An open DNS resolver can be abused to attack other people.
Jellyfin
Jellyfin streams your media to browsers, TVs and phones. It can run as the built-in apps user, which limits what it can touch on the server. That user needs its own folders and permission to read your media:
sudo mkdir -p /mnt/tank/apps/jellyfin/config /mnt/tank/apps/jellyfin/cache
sudo chown -R 568:568 /mnt/tank/apps/jellyfin
To allow reading, go to Datasets, select tank/media and edit its permissions. Add an entry for the apps group with Read access, and apply it to the existing files.
services:
jellyfin:
image: jellyfin/jellyfin
container_name: jellyfin
user: "568:568"
ports:
- "8096:8096/tcp"
- "7359:7359/udp"
volumes:
- /mnt/tank/apps/jellyfin/config:/config
- /mnt/tank/apps/jellyfin/cache:/cache
- type: bind
source: /mnt/tank/media
target: /media
read_only: true
restart: unless-stopped
Open http://your-server-ip:8096, follow the setup wizard and add /media as a library.
Hardware transcoding
Jellyfin has to transcode a video when a device can't play the format directly. That can be too much for an old CPU, but many Intel desktop CPUs have Quick Sync built in. Find the ID of the render group:
getent group render | cut -d: -f3
Then add this to the jellyfin service, using your ID in place of 122:
group_add:
- "122"
devices:
- /dev/dri/renderD128:/dev/dri/renderD128
Finally, turn on Quick Sync or VA-API in Jellyfin's dashboard and untick any codecs your CPU doesn't support. The Intel guide has the details, and there are matching guides for other GPUs.
Home Assistant
Home Assistant brings your smart devices together in one place. The official Compose file uses host networking, which helps it discover devices on your network, and privileged mode:
services:
homeassistant:
container_name: homeassistant
image: ghcr.io/home-assistant/home-assistant:stable
volumes:
- /mnt/tank/apps/homeassistant:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
restart: unless-stopped
stop_grace_period: 60s
privileged: true
network_mode: host
environment:
TZ: Australia/Sydney
Privileged mode gives the container broad access to the server, so only use images you trust. Open http://your-server-ip:8123 to finish the setup.
This container install has one important limit: it doesn't have access to Home Assistant apps, the add-ons you can install with one click on other install types. You can still run those services as separate containers. If you'd rather have the full experience, the project recommends Home Assistant OS for most users, which you can run on its own small machine or in a VM.
Remote access with Tailscale
Don't forward these apps to the internet. Most home services aren't built to be public, and a single flaw could expose your files. Tailscale instead creates a private, encrypted network between your own devices, with no open ports on your router. It has a free plan for personal use, but check its current limits.
In this setup, Tailscale runs on the server's host network. The server joins your private network as one device, so every app port is available at its Tailscale address.
First, create an account and generate an auth key. Then install this app:
services:
tailscale:
image: tailscale/tailscale:latest
container_name: tailscale
hostname: truenas
network_mode: host
environment:
- TS_AUTHKEY=tskey-your-auth-key
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false
- TS_AUTH_ONCE=true
volumes:
- /mnt/tank/apps/tailscale:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- net_raw
restart: unless-stopped
The state folder is what keeps the server's identity across restarts. Without it, each restart would add a new device to your account. Then get the server's Tailscale address:
sudo docker exec tailscale tailscale ip -4
Install Tailscale on your phone, turn off Wi-Fi and open http://that-address:8096. If Jellyfin loads, it's working. You can use the same address in the Immich mobile app when you're away from home.
Tailscale keys expire by default, so the server will eventually drop off your network until you log it in again. You can turn off key expiry for this device in the admin console, but this reduces security if the device is compromised, so weigh that up. If you share your Tailscale network with anyone, also set access rules, because this setup exposes every port on the server, including the TrueNAS web interface.
Block ads away from home
You can use Pi-hole on your phone wherever you are. On Tailscale's DNS page, add a custom nameserver with the server's Tailscale address and turn on Override DNS servers. The FTLCONF_dns_listeningMode: "ALL" setting from earlier lets Pi-hole answer these requests.
The trade-off is that your devices lose DNS whenever Tailscale is on and the server is down. To fix this, turn Tailscale off on that device.
Before you rely on this
Your apps now hold data that's hard to replace, so add the new datasets to your snapshot tasks and backups, especially tank/photos and tank/apps. A photo library on a single server isn't a backup of your photos. If you've removed the originals from your phone, the offsite copy matters even more.
If you'd like to add one more service, the next guide runs an AI model on your own hardware.
Setup details were checked against each project's documentation in November 2026. These projects change often, so check their current install guides, especially before a major upgrade.