Skip to content

Running Noct in Docker

services:
noct:
image: drumandgeorge/noct:latest
ports:
- "7356:7356"
volumes:
- ./noct-data:/data
- /path/to/media:/media:ro
environment:
PUID: 1000
PGID: 1000
devices:
- /dev/dri:/dev/dri # optional: hardware encoding
restart: unless-stopped

Add the folders under /media in the web app.

The container starts as root only to become PUID:PGID (1000:1000 unless you say otherwise), and Noct runs as that. Set them to your user’s on the host, which id shows, so that what is written to /data is yours: the database, backups, artwork. It’s the only thing Noct writes to: media folders can be mounted read only (:ro), and nothing is lost by it.

If you’d rather not start as root at all, run the container with user: "1000:1000" and make /data writable by it yourself; the PUID and PGID settings are then not used.

UMASK sets who else can read what Noct writes: 002 makes it group-writable, for a backup tool running as another user in the same group, and 077 keeps it private to Noct’s user, which is worth having since backups hold your TMDB key. It’s three or four octal digits. Unset, files get Docker’s default (022), and it applies when you start the container with user: too.

Next to its database, Noct keeps what it can make again: the video it converts as it’s watched, resized pictures, subtitles, the pictures on the seek bar. It’s all under /data/cache, so backing up /data backs up gigabytes of it, and the constant writing of converted video wears out whatever disk the database is on. NOCT_CACHE_DIR puts it somewhere else, such as a fast disk or memory:

environment:
NOCT_CACHE_DIR: /cache
tmpfs:
- /cache

The container makes the folder and gives it to PUID:PGID as it does /data. Nothing is lost when it’s emptied, and what’s converted for a device is made again. Without it, nothing changes. The dashboard’s free space is still that of the disk /data is on.

Noct announces itself on the local network (_noct._tcp, over mDNS, which Apple calls Bonjour) so that its apps list it without anyone typing an address. Multicast doesn’t cross a container’s own network, so for that to work give the container the host’s: network_mode: host in place of ports. Without it everything else works, and an app is given the address (http://<host>:7356). NOCT_MDNS=off turns the announcement off where it isn’t wanted.

The image has a health check, so docker ps says healthy once the server is answering, and unhealthy if it stops. It runs noct-server healthcheck, which asks the server’s own /api/health.

For Prometheus and Grafana, set NOCT_METRICS_TOKEN and scrape /api/metrics with it: see metrics.

Without it, anything that has to be converted for a device that can’t play it as it is (see Transcoding) is encoded by the CPU, which a NAS can be slow at. With an Intel or AMD graphics card, Noct can use VA-API instead:

  1. Pass the card in: devices: ["/dev/dri:/dev/dri"] (or --device /dev/dri).
  2. That’s all. The entrypoint finds the group that owns the card’s devices on your host and adds Noct to it, so there’s no group_add or render group id to look up.

The image has the VA-API drivers for Intel (on amd64) and for AMD, and ffmpeg. Noct looks for /dev/dri/renderD128, which is the first card. Nothing is done about NVIDIA’s NVENC yet.

If hardware encoding doesn’t start, Noct falls back to the CPU on its own, and says so in the log (encoder failed, trying the next), so a card that doesn’t work shows up as slow, not as video that doesn’t play.

ffmpeg from Debian, which is what looks inside your files (ffprobe) and converts them. Without it Noct would only know their names and sizes. That makes the image bigger than it would otherwise be.

.github/workflows/docker.yml publishes :edge on every push to main and the version tags on a release. When it can’t run (out of Actions minutes, say), docker/publish.sh builds the same Dockerfile on your own machine and pushes it to Docker Hub:

Terminal window
docker login
docker/publish.sh --tag edge --platforms linux/amd64 # what the workflow would push, for an x86 server
docker/publish.sh # :<this branch's name>, for trying a branch out
docker/publish.sh --no-push # build only

Run it from a clean checkout of main for :edge, since it builds what is in the folder. It makes a buildx builder of its own (noct-builder) the first time, and keeps a build cache in ~/.cache/noct-docker-build. The web build needs a few GB: on Colima or Docker Desktop’s default 2 GB virtual machine it is killed with cannot allocate memory, so give the VM 8 GB (colima start --memory 8 --cpu 4). It never tags :edge or :latest unless you ask with --tag.