Skip to content
me.lasindu.com
All writing

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:

  1. Go to Apps > Discover, open the menu and select Install via YAML.
  2. Enter a lowercase name.
  3. 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.