Skip to content

Install and configure Tagr

Everything you need to run Tagr, the self hosted music metadata editor, on your own server. Docker Compose is the fastest path. A manual Node install is documented below it.

Requirements

Docker and the Docker Compose plugin, on any host that runs linux/amd64 or linux/arm64. That covers most NAS boxes, a home server, a VPS, and a Raspberry Pi 4 or 5 on a 64 bit OS. If you would rather not use Docker, you need Node.js 22 or newer and pnpm. You also need read and write access to the music folders you want to edit, since Tagr writes tags back into the files themselves.

Quick start with Docker Compose

Grab the compose file from the repository:

bash
wget https://raw.githubusercontent.com/suitux/Tagr/main/docker-compose.yml

Generate a secret for signing sessions and paste the output into AUTH_SECRET:

bash
openssl rand -hex 32

Edit the environment block, set your own AUTH_USER and AUTH_PASSWORD, and point the second volume at your music. Then bring it up:

docker-compose.yml
services:
  tagr:
    image: ghcr.io/suitux/tagr:latest
    container_name: tagr
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - PUID=1000
      - PGID=1000
      - NODE_ENV=production
      - DATABASE_URL=file:/data/tagr.db
      - AUTH_SECRET=paste-your-generated-secret-here
      - AUTH_USER=admin
      - AUTH_PASSWORD=your-password-here
      - AUTH_URL=https://your-domain.com
    volumes:
      - sqlite_data:/data
      - /path/to/your/music:/music

volumes:
  sqlite_data:
bash
docker compose up -d

Open http://localhost:3000, log in with the credentials you set, and press the scan button to index your library. The first scan reads every file once, so on a large library it takes a while.

Mounting music folders

Tagr scans /music recursively. If your music lives in several places on the host, mount each of them as a subdirectory under /music and they are all picked up automatically:

docker-compose.yml
volumes:
  - /home/user/Music:/music/library
  - /mnt/nas/Music:/music/nas

MUSIC_FOLDERS is only needed when you want to restrict scanning to specific subdirectories, for example to index /music/library but skip /music/podcasts. Leave it unset and everything under /music is scanned.

Tagr needs write access to these paths. Set PUID and PGID to the user and group that own the files on the host, otherwise saving a tag fails with a permission error.

Environment variables

Variable Required Description Example
DATABASE_URL Yes Path to the SQLite database. Keep it on a persisted volume. file:/data/tagr.db
AUTH_SECRET Yes Secret used to sign JWT sessions. Generate it with openssl rand -hex 32. c5398a60cfd6...
AUTH_USER Yes Username for the initial account. admin
AUTH_PASSWORD Yes Password for the initial account. a-long-password
AUTH_URL No Public URL of the instance. Required when Tagr sits behind a reverse proxy, so redirects and session cookies resolve to the right origin. https://tagr.example.com
MUSIC_FOLDERS No Comma separated list of paths to scan. Defaults to /music. Set it only to restrict scanning to specific subdirectories. /music/library,/music/nas
PUID No User ID the container process runs as. Match it to the owner of your music files. Docker only. 1000
PGID No Group ID the container process runs as. Docker only. 1000
NODE_ENV No Runtime mode. Use production for a normal deployment. production

Manual installation

Requires Node.js 22 or newer.

Create a .env file in the project root:

.env
DATABASE_URL=file:./data/tagr.db
AUTH_SECRET="c5398a60cfd61607192d74ae8db237aaeaa07a98cd8ecdb8776c86eb87376ba3"
AUTH_USER="admin"
AUTH_PASSWORD="admin"
MUSIC_FOLDERS="/Users/youruser/Music,/Volumes/External/Music"

Then build and start:

bash
git clone https://github.com/suitux/Tagr.git
cd Tagr
pnpm install
pnpm build && pnpm start

Scanning your library

A scan walks the configured folders, reads the tags out of every audio file with music-metadata, and upserts one row per track into SQLite. Nothing is copied, nothing is moved, and no audio is re-encoded. The database is an index of your files, not a replacement for them.

Rescanning is safe to repeat. New files are added, files that disappeared from disk are dropped from the index, and existing rows are updated in place, so your change history survives. When a scan finishes, a summary dialog reports how many files were added, updated and removed.

Scan from the folder tree context menu to reindex a single folder rather than the whole library.

Updating

Pull the new image and recreate the container. The database volume is untouched, so nothing is rescanned:

bash
docker compose pull && docker compose up -d

Backups

Two things are worth backing up, and only one of them belongs to Tagr. Your tags live inside the audio files themselves, so your existing music backup already covers them. What Tagr owns is the SQLite database in the sqlite_data volume, which holds the index, your saved filters, your playlists and the full change history.

Copy the database out of the volume while the container is stopped, or snapshot the volume:

bash
docker compose stop tagr
docker run --rm -v sqlite_data:/data -v "$PWD":/backup alpine \
  tar czf /backup/tagr-db-backup.tar.gz -C /data .
docker compose start tagr

Troubleshooting

Saving a tag fails with a permission error

The container process cannot write to the file. Check who owns the music on the host with ls -ln, then set PUID and PGID to that user and group and recreate the container. A read only bind mount produces the same symptom, so make sure the volume is not marked :ro.

The scan finishes but no files show up

The music is probably not under /music inside the container. Exec into it and look: docker exec -it tagr ls /music. If the folder is empty, the bind mount on the left side of the colon points at the wrong host path. If MUSIC_FOLDERS is set, confirm the paths in it are container paths, not host paths.

Login redirects to the wrong host behind a reverse proxy

Set AUTH_URL to the public URL you actually browse to, including the scheme, for example https://tagr.example.com. Then make sure the proxy forwards the Host, X-Forwarded-Proto and X-Forwarded-For headers.

arm64 and Raspberry Pi notes

The image covers linux/arm64, so a Pi 4 or Pi 5 works, but it has to be running a 64 bit OS. A 32 bit Raspberry Pi OS cannot pull the image. On a Pi, expect the first scan of a large library to be slow, since it is bound by reading every file off the SD card or USB disk.

Reverse proxy examples

Caddy, which handles TLS for you:

Caddyfile
tagr.example.com {
  reverse_proxy localhost:3000
}

Nginx:

nginx
server {
  listen 443 ssl;
  server_name tagr.example.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

In both cases set AUTH_URL to the public URL so session redirects land on the right origin.