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:
wget https://raw.githubusercontent.com/suitux/Tagr/main/docker-compose.ymlGenerate a secret for signing sessions and paste the output into AUTH_SECRET:
openssl rand -hex 32Edit the environment block, set your own AUTH_USER and AUTH_PASSWORD, and point the second volume at your music. Then bring it up:
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:docker compose up -dOpen 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:
volumes:
- /home/user/Music:/music/library
- /mnt/nas/Music:/music/nasMUSIC_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:
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:
git clone https://github.com/suitux/Tagr.git
cd Tagr
pnpm install
pnpm build && pnpm startScanning 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:
docker compose pull && docker compose up -dBackups
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:
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 tagrTroubleshooting
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:
tagr.example.com {
reverse_proxy localhost:3000
}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.