Skip to content
Brandon Johnson edited this page Sep 7, 2026 · 3 revisions

Docker

The Docker image is the recommended way to run Obelo. It is a multi-stage build that compiles the React/Vite SPA, embeds it into the Go binary, and runs the result on a slim Debian image carrying ffmpeg and the GPU userspace for every hardware backend Obelo supports on Linux.

The image is the release build, so the embedded Tailscale node is compiled in (Remote Access with Tailscale). Nothing about the container changes to support it: no extra capabilities, no /dev/net/tun, no NET_ADMIN, and it still runs as the unprivileged obelo user, because the node runs in userspace inside the server process.


Build

The build context is the repository root, not the docker/ directory:

docker build -f docker/Dockerfile -t obelo .

Building on an Apple Silicon / arm64 host still produces an amd64 image — the Go binary cross-compiles (pure Go, no CGO) and the runtime stage is pinned to linux/amd64. If your Docker cannot run amd64 images locally:

docker buildx build --platform linux/amd64 -f docker/Dockerfile -t obelo . --load

Run

docker run -d --name obelo -p 8080:8080 \
  -v "$PWD/data:/data" \
  -v /path/to/your/media:/media:ro \
  obelo

What the image already sets for you:

OBELO_DATA_DIR /data
OBELO_LISTEN_ADDR :8080
Exposed port 8080
Declared volume /data
Runtime user obelo (unprivileged)

Pass anything else with -e — see Configuration for the full list.

Volumes

  • /data is the only writable directory Obelo needs, and it holds everything you would miss: the SQLite database, artwork and subtitle caches, transcode scratch, and TLS material. Mount a real volume. A container without one loses your library on every docker run --rm, and in acme mode re-issues certificates until the CA rate-limits you.
  • Mount media read-only (:ro). Obelo never writes to your media folders, and mounting them read-only means a bug never can either.
  • Media can be mounted anywhere. You point libraries at these container paths from the web UI, so /media/movies is what you type — not the host path.

docker compose

There is no compose file in the repository; here is a complete one to copy. It builds from a checkout in the same directory:

services:
  obelo:
    build:
      context: .
      dockerfile: docker/Dockerfile
    image: obelo
    container_name: obelo
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./data:/data
      - /srv/media/movies:/media/movies:ro
      - /srv/media/tv:/media/tv:ro
      - /srv/media/music:/media/music:ro
    environment:
      OBELO_SERVER_NAME: "Living Room"
      OBELO_SCAN_INTERVAL: "1h"
      OBELO_MAX_CONCURRENT_TRANSCODES: "3"
      # Enrichment is off until you configure it. First boot only —
      # afterwards the admin settings UI is authoritative.
      # OBELO_TMDB_API_KEY: "…"
      # OBELO_MUSICBRAINZ_ENABLED: "true"

Bring it up and read the log for the first-admin claim token:

docker compose up -d
docker compose logs -f obelo

With an NVIDIA GPU

services:
  obelo:
    # …as above…
    environment:
      OBELO_HARDWARE_ACCEL: "nvenc"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

With an Intel / AMD render node

services:
  obelo:
    # …as above…
    environment:
      OBELO_HARDWARE_ACCEL: "vaapi"
    devices:
      - /dev/dri:/dev/dri
    group_add:
      - "993"   # the HOST's render GID — check with: getent group render

The group_add value is host-specific and there is no portable way to write it. Look it up with getent group render | cut -d: -f3. Getting this wrong is the single most common cause of "the GPU works as root but not normally" — see Hardware Transcoding.


Networking

The default: a published port

-p 8080:8080 is right for almost everything. The web app, the API, and native clients all work over it. The one thing it cannot do is LAN discovery.

--network host, for discovery only

mDNS (Bonjour, _obelo._tcp) is link-local multicast and does not cross a Docker bridge. A published port gives you a perfectly reachable server that no client will ever discover:

docker run -d --name obelo --network host \
  -e OBELO_LISTEN_ADDR=:8088 \
  -v "$PWD/data:/data" \
  -v /path/to/your/media:/media:ro \
  obelo

Manual address entry still works without it — discovery is a convenience, never the only path. The full story, including how to read the boot log line that tells you what was advertised, is on LAN Discovery.

In compose, that is network_mode: host (and you then drop the ports: block, which host networking ignores).


Reading the boot log

Obelo says what it decided at startup, and those lines are the fastest diagnosis you will get. Three worth knowing:

obelo: no users yet — first-Admin claim token: <token>
obelo: advertising "Living Room" as nuc.local. on _obelo._tcp port 8088 at 192.168.1.50 (id: …)
obelo: hardware acceleration: using configured hardware backend nvenc (h264_nvenc validated)
docker logs obelo
docker logs -f obelo    # follow

The claim token appears only while the database has no users. If you missed it and have not completed setup, restart the container and it is printed again.


Updating

git pull
docker build -f docker/Dockerfile -t obelo .
docker stop obelo && docker rm obelo
docker run -d --name obelo …   # same flags, same -v "$PWD/data:/data"

Or with compose: docker compose build && docker compose up -d.

Your database, settings, users and watch history live in /data and survive this — that is the entire reason the volume matters. Schema migrations run automatically at startup.

Back up data/obelo.db before a major update. The caches (artwork/, subtitles/) rebuild themselves and are not worth backing up.


Health and resources

  • Transcodes are the expensive thing. Direct play and remux are cheap; full transcodes can peg a box. OBELO_MAX_CONCURRENT_TRANSCODES (default 3) bounds only that tier. Raise it if you have cores to spare, lower it if the server does other work.
  • /data grows. Artwork and subtitle caches are bounded by library size; transcode scratch is transient but can be large mid-session.
  • The admin Transcoding tab shows the resolved backend and live load, plus GPU telemetry on NVENC. It is the right place to look before guessing.

Obelo

Home

Getting started

Reference

Going further

Plugins

Sharing with another household

Reaching it from away

When it breaks

Clone this wiki locally