Skip to content
On this page

Docker Installation

Use Docker when MetaTana should run on a server, NAS, homelab host, or other always-on machine. The public image supports native linux/amd64 and linux/arm64 pulls.

Installing on Unraid? Use the Unraid installation guide for the exact Add Container fields. On TrueNAS, install from the app store with the TrueNAS installation guide. On CasaOS, use this Compose guide: the CasaOS App Store listing is submitted but not published yet.

Start With Compose

services:
  metatana:
    image: ghcr.io/metatana/metatana-app:stable
    container_name: metatana
    ports:
      - "3000:3000"
    volumes:
      - metatana-data:/app/data
      - /path/to/media:/media:ro
    environment:
      - PUID=1001
      - PGID=1001
      - MEDIA_DIRS=/media
    restart: unless-stopped
    stop_grace_period: 30s

volumes:
  metatana-data:

Replace /path/to/media with the host folder that contains your library. Replace 1001:1001 with the numeric user and group that can access that folder; do not add a Compose user: override because the image prepares /app/data before dropping privileges. MEDIA_DIRS=/media is optional because the image already uses that default. Then start the service:

docker compose up -d

Open http://YOUR-SERVER:3000, then continue with Sign In And Scan below.

The same container runs the web page and background work in separate processes. A long scan does not share the page's main thread. The web page starts first so a scheduled database restore can finish before scanning starts. The container checks both processes and restarts if either exits. No extra port or container is needed.

Using your own domain or a reverse proxy? Add METATANA_ALLOWED_HOSTS

MetaTana only answers on addresses it can trust. Opening it by IP address (http://192.168.1.20:3000), localhost, a one-word name (http://nas:3000), or a local name such as nas.local or nas.lan needs no change.

If you open it through your own domain name, for example https://metatana.example.com behind a reverse proxy or Cloudflare Tunnel, add that name, then recreate the container. Without it, the page says MetaTana was opened as "metatana.example.com", which is not on its list of allowed host names. and names the setting to change.

    environment:
      - METATANA_ALLOWED_HOSTS=metatana.example.com
docker run -d --name metatana -p 3000:3000 \
  -v metatana-data:/app/data -v /path/to/media:/media:ro \
  -e METATANA_ALLOWED_HOSTS=metatana.example.com \
  ghcr.io/metatana/metatana-app:stable

Details and per-proxy notes: Your Own Domain Or Reverse Proxy.

Three Rules That Prevent Most Problems

  1. Keep the /app/data volume persistent. It contains the SQLite database, settings, thumbnails, logs, and runtime state. It also holds automatic library database backups in /app/data/backups.
  2. Use container paths inside MetaTana. If the host path /mnt/media is mounted as /media, add /media as the folder, not /mnt/media.
  3. Keep the Web UI private. Do not expose it directly to the internet. For remote access, use a VPN or put it behind TLS and an access layer you control. A reverse proxy or tunnel on your own domain also needs METATANA_ALLOWED_HOSTS.

The example uses a read-only media mount for an initial scan-and-review trial. A :ro folder scans and is watched like any other, and Folders shows Read only with "Scans work, file changes are blocked". Remove :ro only from the required mount when you want NFO, artwork, rename, organize, delete, or other file changes.

Startup stops with a clear error if the data volume is not writable by the configured user. Check the volume permissions and PUID/PGID, then restart. The media folder can stay read-only when you only want scanning.

Sign In And Scan

Hosted sign-in and Link Device work when you open the UI through localhost, a private LAN address, a local hostname, or a reverse proxy whose domain is listed in METATANA_ALLOWED_HOSTS. A verified hosted session initializes the local browser session; normal Docker sign-in does not require a separate bootstrap token.

Each install belongs to one MetaTana account. The first account that signs in becomes the install's owner, and other accounts see This MetaTana is linked to a different account. If several people share one install, sign in with the owner's account on every device. Signing out or deleting the account keeps the owner.

The first sign-in asks for a claim code, so only someone who can see the server can claim a new install. Until an account is linked, MetaTana prints a one-time code such as ABCD-EFGH to the container log every time it starts. After you sign in, the screen shows Enter the claim code: type the code and select Link this MetaTana. Find the code with either command:

docker logs metatana
docker exec metatana metatana owner claim-code

Installs that already have an owner never ask for the code. To link a different account, run this on the Docker host, then sign in with the new account and enter the new claim code it prints:

docker exec metatana metatana owner reset

Replace metatana with your container name (docker ps lists it). docker exec metatana metatana owner status shows whether an account is linked. See One Account Per Install for how updated installs pick their owner.

Docker sign-in state is stored by the browser for the exact WebUI origin. Changing the scheme, hostname, port, or browser can require another email-code sign-in. The /app/data volume still preserves the library and app configuration.

Hooks, webhooks, and the metatana command-line tool need Pro and run in MetaTana's background process. Opening MetaTana in a browser signed in to a Pro account confirms Pro for them, and that confirmation keeps them running for 24 hours, including across a container restart. After that they pause until someone opens MetaTana again. If nobody opens this install in a browser, set METATANA_HOSTED_SESSION_TOKEN to your hosted Pro session token in the container environment so MetaTana can confirm Pro by itself. In a split setup, set it on the background container. docker exec passes it on to the command-line tool. Treat it as a secret, like a password.

After sign-in, Home shows Add a folder. Select Choose a folder and pick one of the mounted folders, then follow Drop A Folder Or Files. The example's read-only media mount keeps the first pass safe: MetaTana checks the folder and stores what it finds in the persistent app database, and the drop's page says it can't change the folder until you remove :ro.

Mounted Folders

Choose a folder on Home's first-run screen, and Add folder on Home, Folders and the Library, open Choose a folder, which lists the folders mounted into the container under Mounted folders, such as /media, /movies and /tv. Select one to open it, go down to any folder inside it (Back goes up), and select Use this folder to drop the folder that is open. MetaTana's own folders (/app/data and the system mounts) are left out; Show everything lists them too. A folder you use as an incoming folder is marked Incoming folder, and dropping it says new files there are handled in the Inbox.

  • Don't see a folder? Add it as a volume in your Compose file or docker run command and recreate the container. The list shows each mount as it is inside the container.
  • Read-only mounts. A folder mounted with :ro, or one the configured PUID/PGID cannot write, is checked like any other, and the drop's page says "MetaTana can read Videos but can't change it" with "Remove :ro from the volume, or set PUID". Rename stays off until Check again finds the folder writable.
  • Two mounts of one disk (for example /shelf and /shelf/New as separate volumes) count as two drives: MetaTana never moves a file between them, and Done lists it as Can't move between drives. Mount the parent folder once if you want files to move inside it.

Update Or Pin A Release

MetaTana checks for new versions in the background and shows a banner at the top of every page when one is out. The container cannot update itself, so the banner asks you to pull the new image. Turn the checks off in Settings › General › Updates.

Docker installs can send crash reports, with paths and media names removed. They are not tied to your account. They start on only when the container's TZ is in one of the countries listed under Agreeing to the updated Privacy Policy, and off otherwise (including when TZ is unset). Sentry, the crash report service, sees the IP address a report comes from and is set not to store it. Turn crash reports off in Settings › Account › Privacy & data, or set METATANA_CRASH_REPORTS=off in the container environment.

Stop MetaTana and back up the persistent /app/data volume before changing the image. Then pull and recreate the service without changing the volume mapping:

docker compose pull && docker compose up -d

The current stable image is ghcr.io/metatana/metatana-app:stable. Stable releases also publish immutable version and SHA tags, such as ghcr.io/metatana/metatana-app:2.0.0 and ghcr.io/metatana/metatana-app:sha-802f254. The public package page is metatana-app.

Store listings follow this image. Unraid updates with it; TrueNAS and CasaOS are run by outside maintainers and can lag behind. A submitted catalog update is not a live store update.

New images can apply SQLite migrations. Returning to an older tag may require restoring the matching /app/data backup; changing the tag alone is not a database rollback. An older MetaTana refuses to open a library upgraded by a newer app. Use the latest app, or restore the matching backup before switching to an older tag. MetaTana also saves a Before upgrade database backup in /app/data/backups before a new image changes the database. You can restore it from Settings › Maintenance › Backups (see Back up your library), but it lives on the same volume, so keep your own volume backup too. On a slow disk, such as a NAS hard drive, writing that copy and upgrading the database can take minutes. The container waits for the upgrade to finish, up to an hour, instead of restarting part-way.

Your Own Domain Or Reverse Proxy

MetaTana checks the host name in each request. This blocks a web page on another site from reaching MetaTana through your browser by pointing its own domain at your server (DNS rebinding).

These work with no setting:

  • IP addresses, IPv4 or IPv6, on any port
  • localhost and names ending in .localhost
  • one-word names, such as nas, tower, or a Docker service name
  • names ending in .local, .lan, .home, .home.arpa, .internal, or .localdomain
  • the container's own host name

Anything else needs METATANA_ALLOWED_HOSTS:

  • Format: host names only, separated by commas: metatana.example.com,nas.tail1234.ts.net. No https://, no path. A port is ignored.
  • Wildcards: *.example.com allows every subdomain of a domain you own, but not example.com itself. Do not use a wildcard for a shared domain where other people can create names, such as *.ts.net, *.synology.me, or a dynamic-DNS domain. List your exact name instead.
  • * turns the check off and brings back the risk it blocks. Avoid it.
  • Apply it: set it on the MetaTana container (the web container in a split setup), then recreate or restart it.

On Synology, QNAP and ASUSTOR packages, open MetaTana by the NAS's IP address or its .local name (for example http://nas.local:3000). Opening it through your own domain name isn't supported in these packages yet.

Your proxy should pass the browser's original Host header. Most do by default. If yours replaces Host with the backend address, pages still load, and changes such as sign-in work only when the browser marks the request as coming from the same page origin and the name you opened MetaTana with is allowed: an IP address, a local name such as nas.local, or a name in METATANA_ALLOWED_HOSTS. Otherwise they fail with 403 and a message that starts MetaTana only accepts changes from its own pages. Passing the original Host avoids this.

Proxy What to set
Nginx Proxy Manager, SWAG Passes Host by default. Add your proxy host's domain to METATANA_ALLOWED_HOSTS.
Traefik, Caddy Pass Host by default. Add the router or site domain.
Plain nginx Add proxy_set_header Host $http_host; to the location block, and add the domain.
Apache Add ProxyPreserveHost On to the virtual host, and add the domain.
Cloudflare Tunnel Add the public hostname. Leave the tunnel's HTTP Host Header setting empty.
Tailscale http://nas:3000 works as is. A full MagicDNS or Tailscale Serve name such as nas.tail1234.ts.net must be listed exactly.
Advanced deployment details

The public all-in-one image runs as a unified app process by default: one container serves the UI and owns background work for the mounted database.

Larger supported deployments can split web and background roles, but exactly one owner must perform background writes for a database. The background container must be running: the web container only serves the app, and scans, Autopilot, Scrape sessions, Arrivals, fetches, and sync run in the background container. Keep every role on the same release during normal updates.

Provider keys can be passed as environment variables or secret files for advanced deployments, or configured in the UI. See Provider Keys And Secret Files. If Free uses an environment variable, run the live provider test before scanning. Use only credentials licensed for your use, and keep them out of public logs, screenshots, and support reports.

Provider Keys And Secret Files

A container has no system keychain, so keys you save in Settings › Providers › Your keys, the Trakt sign-in, media server sign-ins, and saved webhooks are stored as plain text in the database inside /app/data. Keep that volume and its backups private. To keep keys out of the database, pass them to the container instead, as an environment variable or as a secret file.

Environment variable:

    environment:
      - TMDB_API_KEY=your-tmdb-key

Secret file (Docker secrets). Add _FILE to the variable name and point it at the file:

services:
  metatana:
    # ...image, ports, and volumes as above
    environment:
      - TMDB_API_KEY_FILE=/run/secrets/tmdb_api_key
    secrets:
      - tmdb_api_key

secrets:
  tmdb_api_key:
    file: ./secrets/tmdb_api_key.txt

The file holds only the key. Every provider key variable in Environment Variables accepts the _FILE form, and so does METATANA_HOSTED_SESSION_TOKEN for background Pro checks.

When more than one is set, MetaTana uses the first of:

  1. a key saved in Settings › Providers › Your keys
  2. the _FILE secret file
  3. the plain environment variable

If the secret file is missing or empty, MetaTana logs the variable name (never the value) and uses the plain variable. It does the same when the file exists but can't be read, for example because of its permissions, and Settings › Providers › Your keys then shows Couldn’t read the key file; using the environment key under that provider. In a split setup, give the keys to both containers.

Path Mapping For Playback Clients

Configure a path mapping when MetaTana and a playback client see the same file through different paths.

Example:

  • MetaTana sees /media/Movies/Big Buck Bunny (2008).mkv.
  • Plex sees /mnt/media/Movies/Big Buck Bunny (2008).mkv.
  • Map /media to /mnt/media before playback sync.

During docker stop or compose recreate, MetaTana handles the shutdown signal by stopping background services and releasing its hold on the database before flushing local operation logs and exiting. That keeps everything MetaTana knows in the persistent data volume across image updates and restarts.

See File Layout for the data-volume structure and Environment Variables for advanced container configuration.