[Docker] PeerTube on Synology NAS

[Docker] PeerTube on Synology NAS

YouTube is one of the world’s largest video streaming platforms. For many people, it’s not just a place to share content but also a source of livelihood.
But what if you could run your own version of YouTube on a personal server?
Someone thought of that — and that’s how PeerTube was born: a self-hosted, federated video platform.

Since PeerTube comes with an official Docker image, I decided to give it a try on my Synology NAS. Spoiler: it wasn’t as smooth as I hoped.


First Attempts

I started the installation with a light heart, thinking it would be a weekend side project.
But PeerTube has a lot of environment variables, and if you miss just one, you’ll be greeted with the dreaded:

“Build failed”

So, instead of over-customizing everything on the first run, I aimed for the simplest possible configuration that would at least launch successfully.


Folder Structure

PeerTube stores data in multiple places (database, Redis, app config, and uploaded videos).
On Synology, it’s a good idea to create dedicated folders under /volume1/docker/peertube so you can map them as persistent volumes.

/volume1/docker/peertube/
├── docker-compose.yml # Project root file (run docker compose up here)
├── .env # Environment variables (can also be named stack.env)
├── redis/ # Redis persistent data
├── db/ # PostgreSQL persistent data
├── data/ # PeerTube videos and uploaded media
└── config/ # PeerTube configuration files

Reverse Proxy Setup

I used Synology’s built-in Reverse Proxy (Control Panel → Login Portal → Advanced → Reverse Proxy).

Required settings:

  • Target: Forward requests from tube.yourdomain.com to the PeerTube container (localhost:9500 in my case).
  • WebSocket: Enable WebSocket support. This automatically adds the required Upgrade and Connection headers.
  • Custom Headers:
    • X-Forwarded-For = $remote_addr → Passes the real client IP
    • X-Forwarded-Proto = $scheme → Marks all traffic as HTTPS (since the proxy terminates TLS)
    • X-Real-IP = $remote_addr → Some apps prefer this

Without these headers, PeerTube won’t log the right IPs or may not recognize secure connections properly.


docker-compose.yml

Here’s my docker-compose.yml. I’ve added comments so you can follow what’s happening:

services:
  # ── Redis for caching and background jobs ───────────────
  redis:
    image: redis:7
    command: ["redis-server","--requirepass","${REDIS_PASSWORD}"] 
    container_name: peertube-redis
    hostname: peertube-redis
    security_opt:
      - no-new-privileges:true
    healthcheck:
      # Ensure Redis is alive by pinging it with a password
      test: ["CMD-SHELL", "redis-cli -h 127.0.0.1 -p 6379 -a \"$REDIS_PASSWORD\" ping | grep -q PONG"]
      interval: 15s
      timeout: 5s
      retries: 10
      start_period: 10s
    volumes:
      - /volume1/docker/peertube/redis:/data:rw
    env_file: ./.env
    restart: unless-stopped
    networks:
      - peertube-net

  # ── PostgreSQL database for PeerTube ────────────────────
  db:
    image: postgres:16
    container_name: peertube-db
    hostname: peertube-db
    security_opt:
      - no-new-privileges:true
    healthcheck:
      # Check PostgreSQL readiness with pg_isready
      test: ["CMD-SHELL","pg_isready -q -d \"$POSTGRES_DB\" -U \"$POSTGRES_USER\""]
      timeout: 60s
      interval: 15s
      retries: 10
    env_file: ./.env
    volumes:
      - /volume1/docker/peertube/db:/var/lib/postgresql/data:rw
    restart: unless-stopped
    networks:
      - peertube-net

  # ── The PeerTube application itself ─────────────────────
  peertube:
    image: chocobozzz/peertube:v7.2.3-bookworm
    container_name: peertube
    hostname: peertube
    security_opt:
      - no-new-privileges:true
    # Optional: enable hardware transcoding on Intel-based NAS
    devices:
      - /dev/dri:/dev/dri
    env_file: ./.env
    environment:
      TZ: Asia/Seoul
      # Logging verbosity
      PEERTUBE_LOG_LEVEL: info
      # Hardware transcoding options (optional)
      PEERTUBE_TRANSCODING_HWACCEL: vaapi
      PEERTUBE_TRANSCODING_FFMPEG_ARGS__GLOBAL: '["-vaapi_device","/dev/dri/renderD128"]'
      PEERTUBE_TRANSCODING_FFMPEG_ARGS__H264: '["-c:v","h264_vaapi","-bf","0"]'
    healthcheck:
      # PeerTube healthcheck: API ping
      test: ["CMD-SHELL","curl -fsS http://localhost:9000/api/v1/ping || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 10
    stop_grace_period: 60s
    ports:
      - 1934:1935   # RTMP live streaming
      - 9500:9000   # Web UI, proxied by Synology RP
    volumes:
      - /volume1/docker/peertube/data:/data:rw
      - /volume1/docker/peertube/config:/config:rw
    restart: unless-stopped
    depends_on:
      redis:
        condition: service_healthy
      db:
        condition: service_healthy
    networks:
      - peertube-net

networks:
  peertube-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.59.0.0/16

.env

Here’s the environment file. I assumed Gmail as the SMTP provider:

# ── Domain and email settings ─────────────────────────────
PEERTUBE_WEBSERVER_HOSTNAME=tube.yourdomain.com

# Always quote email addresses
PEERTUBE_ADMIN_EMAIL="your email"
PEERTUBE_SMTP_USERNAME=your email
PEERTUBE_SMTP_PASSWORD=your-google-app-password  # Use a Google APP Password (16 digits)
PEERTUBE_SMTP_FROM=your email
PEERTUBE_SMTP_HOSTNAME=smtp.gmail.com

# ── Random 32-character secret ────────────────────────────
# Change this value to your own generated secret
PEERTUBE_SECRET=2TcW4FHZmEF8l501xrbKGovXE5OzhTCe

# ── Database and Redis settings ───────────────────────────
POSTGRES_USER=peertube
POSTGRES_PASSWORD=peertube
POSTGRES_DB=peertube
REDIS_PASSWORD=redispass

# ── Webserver config ─────────────────────────────────────
PEERTUBE_WEBSERVER_USE_HTTP=false
PEERTUBE_WEBSERVER_TRUST_PROXY=true

# ── Mail config ──────────────────────────────────────────
PEERTUBE_SMTP_PORT=465
PEERTUBE_SMTP_TLS=true
PEERTUBE_SMTP_DISABLE_STARTTLS=false

# ── Internal service links ───────────────────────────────
PEERTUBE_DB_NAME=$POSTGRES_DB
PEERTUBE_DB_USERNAME=$POSTGRES_USER
PEERTUBE_DB_PASSWORD=$POSTGRES_PASSWORD
PEERTUBE_DB_SSL=false
PEERTUBE_DB_HOSTNAME=peertube-db
PEERTUBE_REDIS_HOSTNAME=peertube-redis
PEERTUBE_REDIS_AUTH=$REDIS_PASSWORD

# ── Trust proxy IP ranges ────────────────────────────────
PEERTUBE_TRUST_PROXY=["127.0.0.1", "loopback", "172.59.0.0/16"]

# ── Optional: object storage ACLs ─────────────────────────
PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PUBLIC="public-read"
PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PRIVATE="private"

# ── Logging ──────────────────────────────────────────────
PEERTUBE_LOG_LEVEL=info

First Login

After building and starting the project in Synology’s Container Manager, the first screen should appear.
However, before logging in as the root user, you’ll need to set a password:

# Open a terminal inside the peertube container
docker exec -it peertube bash

(or you may use Container Manager to open Terminal)

# Run the password reset command
npm run reset-password -- -u root

You’ll be prompted to enter a new password. If you see
User password updated.
you’re ready to log in at https://tube.yourdomain.com.


Impressions

And there it was: my very own personal YouTube.
Well, not exactly “YouTuber” but more like a PeerTuber 😅

PeerTube even has a mobile app, and it can connect to not just your own server but also other PeerTube instances around the world. It’s meant to form a federated network, but in practice, it’s still a bit niche — and you often need to sign up on each server individually.

For me, it was mostly the fun of having a personal video archive. On a small scale, it could also work well for a private team to share videos.

But running it on a Synology NAS isn’t easy. Uploading and transcoding videos is heavy, and my NAS struggled. Since I’m already running multiple Docker services (including this blog), system resources were stretched thin, and performance of other apps suffered.

Still, the experiment was worth it. I now have my own video storage service, and it was a fun project to try out.