Effortless Jellyfin Setup with Docker, Caddy Reverse Proxy, and Tailscale Networking

Jellyfin Server Setup with Docker, Caddy, and Tailscale

This guide will walk you through the process of setting up a Jellyfin server using Docker, Caddy, and Tailscale. This setup allows you to access your server from anywhere in the world while keeping your media private and secure.

Table of Contents

  1. Directory Structure
  2. Understanding the Folders
  3. Setup Instructions
  4. Setup for jellyfin-local
  5. Setup for jellyfin-tailscale
  6. Running the Server
  7. Troubleshooting

Directory Structure

The layout of the current folder is as follows:

.
├── README.md
├── jellyfin-local
│   └── docker-compose.yaml
├── jellyfin-server
│   ├── cache
│   └── config
└── jellyfin-tailscale
    ├── caddy
    │   ├── Caddyfile
    │   ├── config
    │   └── data
    ├── docker-compose.yaml
    └── tailscale
        └── varlib

jellyfin-local/docker-compose.yaml

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: "jellyfin-l"
    user: 1000:1000
    ports:
      - 127.0.0.1:8096:8096 # enable this for localhost access only - recommended for insecure networks
      #- 8096:8096 # enable this for LAN access only
    volumes:
    # you do not have to use the same local filepaths that I do for volume mapping in the containers,
    # but you do have to make sure whatever filepath you use is mapped to the correct filepath in the container
      - ~/Jellyfin/jellyfin-server/config:/config
      - ~/Jellyfin/jellyfin-server/cache:/cache
      - ~/Documents/Jellyfin/Movies:/Movies:ro
      - ~/Documents/Jellyfin/Shows:/Shows:ro
    restart: unless-stopped

jellyfin-tailscale/docker-compose.yaml

networks:
  # network created via docker
  # all other containers are also on it
  proxy-network:
    name: "proxy-network"

volumes:
  # shared volumes any container in the same docker-compose file can access
  # used to share the tailscaled.sock file with caddy
  sock_volume:

  # you do not have to use the same local filepaths that I do for volume mapping in the containers,
  # but you do have to make sure whatever filepath you use is mapped to the correct filepath in the container
services:
  jellyfin:
    image: jellyfin/jellyfin
    container_name: "jellyfin-ts"
    user: 1000:1000
    networks:
      - proxy-network
    volumes:
      - ~/Jellyfin/jellyfin-server/config:/config
      - ~/Jellyfin/jellyfin-server/cache:/cache
      # ro means read only, we don't want jellyfin accidentally deleting our files
      - ~/Documents/Jellyfin/Movies:/Movies:ro
      - ~/Documents/Jellyfin/Shows:/Shows:ro
    restart: unless-stopped

  caddy:
    image: caddy
    container_name: "caddy"
    hostname: caddy
    networks:
      # caddy is in the network with the other containers
      - proxy-network
    depends_on:
      # wait for tailscale to boot
      # to communicate to it using the tailscaled.sock
      - tailscale 
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ~/Jellyfin/jellyfin-tailscale/caddy/Caddyfile:/etc/caddy/Caddyfile
      - ~/Jellyfin/jellyfin-tailscale/caddy/data:/data
      - ~/Jellyfin/jellyfin-tailscale/caddy/config:/config
      # get socket tailscale created in the shared volume and share it with caddy
      # caddy expects the socket to be at /var/run/tailscale/tailscaled.sock
      - sock_volume:/var/run/tailscale
    restart: unless-stopped

  tailscale:
        container_name: tailscaled
        image: tailscale/tailscale
        network_mode: host
        # tailscale sets new machine names to the OS hostname
        # docker-desktop is the default hostname for docker
        # if you modify this and recreate the container, the machine name will be updated automatically
        # make sure this matches the machine name you set in the Caddyfile
        hostname: jellyfin
        cap_add:
            - NET_ADMIN
            - NET_RAW
        volumes:
            # saves container state after container is recreated
            # used varlib because var folder isn't needed locally
            - ~/Jellyfin/jellyfin-tailscale/tailscale/varlib:/var/lib
            # containerized version of tailscale uses /tmp/tailscaled.sock
            # binds the socket to a docker volume so it can be accessed by other containers
            # this can't be a local directory because the socket is created by the container
            - sock_volume:/tmp
        environment:
            # if you add a command key, it will override environment key variables with default values!
            # info: https://tailscale.com/kb/1282/docker#ts_socks5_server

            # set the authkey to reusable when generating it from tailscale
            - TS_AUTHKEY=tskey-auth-xxxxxxxxxxxxxxxxxxxx
            # prevents a new machine from being added each time the container is restarted
            - TS_STATE_DIR=/var/lib/tailscale
            # https://tailscale.com/kb/1112/userspace-networking
            - TS_USERSPACE_NETWORKING=userspace-networking
        restart: unless-stopped

# Steps
#1. run the command: docker-compose up -d
#2. run the command: docker exec tailscaled tailscale --socket /tmp/tailscaled.sock cert <machine-name>.<tailnet-name>.ts.net
#3. remove the auth key from docker-compose.yaml, save the file, you can keep the container running
#4. run the command: docker-compose up -d

# Filepath
# .
# ├── caddy
# │   ├── Caddyfile
# │   ├── config
# │   └── data
# ├── docker-compose.yaml
# └── tailscale
#     └── varlib

jellyfin-tailscale/caddy/Caddyfile

# make sure the machine name is the same as the hostname of the tailscale container in docker-compose.yml
<machine-name>.<tailnet-name>.ts.net {
	reverse_proxy jellyfin:8096
}

# set to loopback address so it works on the host machine
127.0.0.1 {
	reverse_proxy jellyfin:8096
}

To verify that your folder is structured correctly, use the tree command:

$ tree

Understanding the Folders

The Jellyfin setup consists of three main folders:

  1. jellyfin-server: Contains all the information your Jellyfin server needs to run, excluding media files. Changes made to the server will be saved here.

  2. jellyfin-local: Contains the Docker Compose file that runs your server, configured to restrict access to your machine or local network. This setup is ideal for situations where Tailscale connectivity is unavailable.

  3. jellyfin-tailscale: Contains the Docker Compose file that runs your server and allows connections only from devices on your Tailnet. It uses a reverse proxy through Caddy for HTTPS connections.

Important: Both jellyfin-tailscale and jellyfin-local interact with jellyfin-server. To avoid conflicts, do not run these two Docker containers simultaneously.

Setup Instructions

Follow these steps to set up your environment:

  1. Docker Setup

  2. Tailscale Setup

Setup for jellyfin-local

This section guides you through setting up jellyfin-local.

Steps

  1. Open the Docker Compose File
    Navigate to the jellyfin-local directory and open docker-compose.yaml.

  2. Navigate to Jellyfin Volumes
    Locate services: jellyfin: volumes:.

  3. Understand File Paths
    The section contains file paths split by a colon (:). The part before the colon is the file’s location on your machine; after is its location in the Docker container.

  4. Replace Local File Paths
    Update local file paths as follows:

    - ~/Jellyfin/jellyfin-server/config:/config
    - ~/Jellyfin/jellyfin-server/cache:/cache
    - ~/Documents/Jellyfin/Movies:/Movies:ro
    - ~/Documents/Jellyfin/Shows:/Shows:ro
    

Setup for jellyfin-tailscale

This guide walks you through setting up jellyfin-tailscale.

Steps

  1. Open the Docker Compose File
    Navigate to the jellyfin-tailscale directory and open docker-compose.yaml.

  2. Configure Jellyfin Volumes
    Under services: jellyfin: volumes:, replace local file paths:

    - ~/Jellyfin/jellyfin-server/config:/config
    - ~/Jellyfin/jellyfin-server/cache:/cache
    - ~/Documents/Jellyfin/Movies:/Movies:ro
    - ~/Documents/Jellyfin/Shows:/Shows:ro
    
  3. Configure Caddy Volumes
    Under services: caddy: volumes::

    - ~/Jellyfin/jellyfin-tailscale/caddy/Caddyfile:/etc/caddy/Caddyfile
    - ~/Jellyfin/jellyfin-tailscale/caddy/data:/data
    - ~/Jellyfin/jellyfin-tailscale/caddy/config:/config
    
  4. Configure Tailscale Volumes
    Under services: tailscale: volumes::

    - ~/Jellyfin/jellyfin-tailscale/tailscale/varlib:/var/lib
    
  5. Set Hostname
    Under services: tailscale: hostname:, set a hostname (e.g., jellyfin).

  6. Set Tailscale Authentication Key
    Under services: tailscale: environment: replace TS_AUTHKEY with your key from Tailscale admin console.

  7. Setup Caddyfile:

    • Navigate to jellyfin-tailscale/caddy and open Caddyfile.
    • Replace <machine-name> with your hostname.
    • Replace <tailnet-name> with your tailnet name from Tailscale admin console.

Running the Server

Jellyfin-Local

  1. Navigate to the jellyfin-local directory in your terminal.
  2. Execute the command:
    docker-compose up -d
    
  3. Access your server at http://localhost:8096.
  4. Stop this server before running jellyfin-tailscale.

Jellyfin-Tailscale

  1. Navigate to the jellyfin-tailscale directory in your terminal:
  2. Execute:
    docker-compose up -d
    
  3. Check connection status in Tailscale admin console.
  4. Generate a certificate:
    docker exec tailscaled tailscale --socket /tmp/tailscaled.sock cert <machine-name>.<tailnet-name>.ts.net 
    
  5. Comment out or remove authentication key line in docker-compose.yaml.
  6. Remake Docker container:
    docker-compose up -d 
    
  7. Access your server at https://<machine-name>.<tailnet-name>.ts.net.

Troubleshooting

  • Check File Paths: Ensure all file paths in both docker-compose.yaml files are correct.
  • Check Docker Logs: Review logs in Docker Desktop if connection issues arise.
  • Verify Tailscale Key: Ensure authentication key is correct and set to reusable.
  • Check Network Connection: Confirm stable network connection.
  • Inspect Docker Services: Use:
    docker-compose ps 
    
    All services should be in an “Up” state.
  • Check Caddyfile Configuration: Ensure hostname and tailnet name match those in Tailscale admin console.
  • Restart Docker Services:
    docker-compose down 
    
    Followed by:
    docker-compose up -d 
    
  • USB Passthrough Issues: Note that Docker Desktop does not support USB passthrough; consider using colima for that purpose.