v6.14.1 introduces PUID/PGID user mapping. Before restarting your container after pulling the new image you must:
1. Find your UID/GID (the user that should own the files mStream writes):
id <your-username> # example output: uid=1000(jan) gid=1000(jan)2. Fix ownership of the mStream data directories on your host (they were written as root before):
# Replace 1000:1000 with your actual UID:GID chown -R 1000:1000 /path/to/save \ /path/to/image-cache \ /path/to/waveform-cacheDo not chown your music library — those files are already owned correctly.
3. Add PUID/PGID to your
compose.yamlunderenvironment::environment: PUID: 1000 # replace with your UID PGID: 1000 # replace with your GID4. Then pull and restart:
docker compose pull docker compose down docker compose up -dIf you skip steps 2–3 the container will still start (it falls back to root), but new files created by mStream will be owned by root on your host.
If you installed via compose.yaml with image: ghcr.io/aroundmyroom/mstream-velvet:latest:
docker compose pull # fetch the new image
docker compose down
docker compose up -d # recreate the containerThat's it — your save/ folder (config, database, logs) and music volume are mounted from the host, so no data is lost.
Pinned to a specific version? Update the tag in
compose.yaml(e.g.v6.14.1-velvet), then run the same three commands. Check releases/ or the GitHub releases page for the latest tag.
The easiest way. No build step required.
docker pull ghcr.io/aroundmyroom/mstream-velvet:latestOr pin to a specific release:
docker pull ghcr.io/aroundmyroom/mstream-velvet:v6.14.5-velvetservices:
mstream:
image: ghcr.io/aroundmyroom/mstream-velvet:latest
container_name: mstream-velvet
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./save:/app/save
- /media/music:/music # adjust host path to your library
- ./waveform-cache:/app/waveform-cache
- ./image-cache:/app/image-cache
environment:
PUID: 1000 # UID of the user that owns your music files
PGID: 1000 # GID of the user that owns your music files
MSTREAM_MUSIC_DIR: /music # triggers first-run auto-config (optional, see below)docker compose up -dgit clone https://github.com/aroundmyroom/mStream.git
cd mStream
docker build -t mstream-velvet .Then change the image: line in compose.yaml to mstream-velvet.
Every time a v*-velvet tag is pushed to GitHub, the workflow .github/workflows/docker-publish.yml automatically:
- Builds a multi-arch image (
linux/amd64+linux/arm64) - Pushes it to
ghcr.io/aroundmyroom/mstreamwith the version tag andlatest
No manual steps are needed — tagging a release is enough.
| Volume | What it stores | Required? |
|---|---|---|
/app/save |
Config file (save/conf/default.json), SQLite database (save/db/mstream.sqlite), logs, sync state |
Yes — without this, all data is lost on container restart |
/music (or any host path) |
Your music files — must be added to the config as a folder (see below) | Yes, unless music is already inside the image |
/app/waveform-cache |
Pre-computed waveforms (regenerated if missing, but takes time) | Recommended |
/app/image-cache |
Cached album art, podcast art, radio logos | Recommended |
By default the container runs as root. This means any files mStream writes — tag edits, recordings, downloaded YouTube tracks — will be owned by root on your host. On a NAS or shared volume that is usually wrong.
Set PUID and PGID to the UID and GID of the user that owns your music files. mStream Velvet will drop privileges to that user before starting.
On the host (or in the NAS shell):
id <your-music-user>
# example output: uid=1000(soulseek) gid=1000(soulseek) environment:
PUID: 1000 # replace with your UID
PGID: 1000 # replace with your GIDThat's all — the entrypoint creates the matching user inside the container and runs Node.js as that user.
If you previously ran without PUID/PGID, the save/, image-cache/, and waveform-cache/ directories on your host will be owned by root. Fix them before restarting:
docker compose down
# Replace 1000:1000 with your actual PUID:PGID
chown -R 1000:1000 /path/to/save \
/path/to/image-cache \
/path/to/waveform-cache
# Add PUID/PGID to compose.yaml, then:
docker compose up -dYour music files are already owned by your NAS user — do not chown those.
On first start mStream creates a blank config at save/conf/default.json.
Add an environment: block to your compose.yaml. mStream will write the initial config automatically on the very first start and skip this step on every subsequent restart.
Complete copy-paste example:
services:
mstream:
image: ghcr.io/aroundmyroom/mstream-velvet:latest
container_name: mstream-velvet
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./save:/app/save # config, database, logs
- /media/music:/music # your music library (adjust host path)
- ./waveform-cache:/app/waveform-cache
- ./image-cache:/app/image-cache
environment:
PUID: 1000 # UID of the user that owns your music files (run: id <user>)
PGID: 1000 # GID of the user that owns your music files
MSTREAM_MUSIC_DIR: /music # must match the volume target above
# Admin account (optional).
# If omitted the server starts in open mode — no login required.
# MSTREAM_ADMIN_USER: admin
# MSTREAM_ADMIN_PASS: changeme
# Extra feature folders — uncomment to enable.
# By default each type is applied directly to MSTREAM_MUSIC_DIR (/music).
# If your files live in a sub-folder, add the matching *_SUBDIR variable:
# MSTREAM_ENABLE_YOUTUBE: "true"
# MSTREAM_YOUTUBE_SUBDIR: YouTube # → folder root becomes /music/YouTube
# You can also add, change or remove folders at any time in the Admin panel.
# For full control, skip env vars and edit save/conf/default.json directly.
# AudioBooks & Podcasts (type: audio-books)
# MSTREAM_ENABLE_AUDIOBOOKS: "true"
# MSTREAM_AUDIOBOOKS_SUBDIR: Audiobooks # optional — omit to use /music directly
# Radio Recordings (type: recordings — also enables the radio feature)
# MSTREAM_ENABLE_RECORDINGS: "true"
# MSTREAM_RECORDINGS_SUBDIR: Recordings # optional — omit to use /music directly
# YouTube Downloads (type: youtube)
# MSTREAM_ENABLE_YOUTUBE: "true"
# MSTREAM_YOUTUBE_SUBDIR: YouTube # optional — omit to use /music directlydocker compose up -dOpen http://localhost:3000 (or the admin panel at /admin to start a scan).
When env vars are NOT sufficient — use Option 2 instead if you need: multiple mount points, child-vpaths,
albumsOnly/filepathPrefixfiltering, or any advanced folder layout.
Edit save/conf/default.json to point at your music volume:
{
"folders": {
"music": {
"root": "/music"
}
}
}Then restart the container:
docker compose restartOpen the admin panel at http://localhost:3000/admin — no login is required on a fresh install with no users. Start a scan from the Scan button.
Once the library has been scanned, create your first user in the admin panel under Users. The first user should have admin access.
After creating at least one user, the server requires login and the no-auth bypass is disabled.
Pull the latest changes, rebuild the image, and restart:
git pull
docker build -t mstream-velvet .
docker compose up -dYour data in the mounted volumes is untouched.
| Command | Effect |
|---|---|
docker compose up -d |
Start in background |
docker compose down |
Stop and remove container |
docker compose restart |
Restart after config change |
docker compose logs -f |
Follow live logs |
docker exec -it mstream-velvet sh |
Shell into the running container |
docker run -d \
--name mstream-velvet \
--restart unless-stopped \
-p 3000:3000 \
-v /home/mStream/save:/app/save \
-v /media/music:/music \
-v /home/mStream/waveform-cache:/app/waveform-cache \
-v /home/mStream/image-cache:/app/image-cache \
mstream-velvetIf you run mStream behind nginx or Caddy, see deploy.md for the recommended nginx configuration — required for large FLAC libraries to avoid stall on idle connections.