Deployment Guide

This guide covers Docker-based deployment options and standard runtime operations.

For Kubernetes deployments, see KUBERNETES.md. For reverse proxy/tunnel routing, see REVERSE_PROXY_AND_TUNNELS.md. For container-by-container environment variables (required/optional, defaults, and purpose), see ENVIRONMENT_VARIABLES.md.

Important: Frontend Build-Time Environment Variables

In production frontend images, NEXT_PUBLIC_* values are compiled into the browser bundle at image build time.

If you need a different NEXT_PUBLIC_* behavior, build a custom frontend image with your desired build args.

Quick Start

One-command install (AIO container)

docker run -d \
  --name soundspan \
  -p 3030:3030 \
  -v /path/to/your/music:/music \
  -v soundspan_data:/data \
  ghcr.io/soundspan/soundspan:latest

Open http://localhost:3030 and create your account.

AIO with GPU acceleration (optional)

Requires NVIDIA Container Toolkit. See ADVANCED_ANALYSIS_AND_GPU.md.

docker run -d \
  --name soundspan \
  --gpus all \
  -p 3030:3030 \
  -v /path/to/your/music:/music \
  -v soundspan_data:/data \
  ghcr.io/soundspan/soundspan:latest

Compose File Matrix

File Purpose Typical command
docker-compose.aio.yml All-in-one (AIO) soundspan container (frontend+backend+db+redis bundled) docker compose -f docker-compose.aio.yml up -d
docker-compose.yml Split stack (frontend, backend, postgres, redis, sidecars, analyzers) with deployment-safe canonical ports and optional worker profile docker compose -f docker-compose.yml up -d
docker-compose.override.ha.yml HA-focused override for split stack (backend API role, dynamic backend host-port for scale-out, worker profile ready) docker compose -f docker-compose.yml -f docker-compose.override.ha.yml --profile worker up -d
docker-compose.services.yml Optional external Lidarr service layered onto either stack above docker compose -f docker-compose.yml -f docker-compose.services.yml up -d
docker-compose.local.yml Local npm/tsx host-run dependencies only (postgres+redis; optional analyzer profile), using +1 collision-avoidance ports docker compose -f docker-compose.local.yml up -d postgres-local redis-local
docker-compose.override.lite-mode.yml Optional override to disable analyzers in split stack cp docker-compose.override.lite-mode.yml docker-compose.override.yml && docker compose up -d
docker-bake.json Docker Buildx Bake file for building images by group (core/db/external/analysis/aio) docker buildx bake core

Deployment defaults in compose files use canonical ports:

For local host-run development, use +1 ports to avoid collisions:

docker compose -f docker-compose.local.yml up -d postgres-local redis-local
cd backend && PORT=3007 npm run dev
cd frontend && PORT=3031 BACKEND_URL=http://127.0.0.1:3007 NEXT_PUBLIC_API_URL=http://127.0.0.1:3007 NEXT_PUBLIC_API_PATH_MODE=direct npm run dev

Local host-run guardrails (ports + testing)

SOUNDSPAN_UI_BASE_URL=http://127.0.0.1:3031 npm --prefix frontend run test:predeploy
SOUNDSPAN_UI_BASE_URL=http://127.0.0.1:3031 npx --prefix frontend playwright test tests/e2e/predeploy/social-history.spec.ts --workers=1

Compose Multi-Replica Notes (Split Stack)

The AIO image is a single-container topology. For replica scaling, use docker-compose.yml.

Recommended role split:

Example (2 API replicas + 1 worker):

BACKEND_PROCESS_ROLE=api BACKEND_PORT=0 \
docker compose -f docker-compose.yml --profile worker up -d \
  --scale backend=2 \
  --scale backend-worker=1

Equivalent using HA override:

BACKEND_PORT=0 docker compose -f docker-compose.yml -f docker-compose.override.ha.yml \
  --profile worker up -d \
  --scale backend=2 \
  --scale backend-worker=1

Notes:

Experimental Features

Segmented streaming documentation has been moved to a dedicated experimental guide:

Use that guide for segmented runtime controls, rollout levels, observability, and primary-mode reversion procedures.

Building Images with Docker Bake

The repository includes a docker-bake.json file that defines all buildable images organized into groups. This is an alternative to docker compose build when you want fine-grained control over which images to build, or want to leverage BuildKit parallelism across targets.

Prerequisites

Docker Buildx is required (included by default with Docker Desktop and recent Docker Engine installs):

docker buildx version

Groups

Group Targets Description
default frontend, backend, backend-worker, tidal-downloader, ytmusic-streamer, audio-analyzer, audio-analyzer-clap All buildable split-stack images
core frontend, backend, backend-worker Application services only
db postgres, redis Tags upstream images locally (pgvector/pgvector:pg16, redis:7-alpine)
external tidal-downloader, ytmusic-streamer Streaming sidecars
analysis audio-analyzer, audio-analyzer-clap ML audio analysis services
aio soundspan-aio All-in-one container

Usage

Build a specific group:

# Build core application images (frontend + backend + worker)
docker buildx bake core

# Build streaming sidecars only
docker buildx bake external

# Build everything (all split-stack images)
docker buildx bake

# Build the all-in-one image
docker buildx bake aio

Build a single target:

docker buildx bake frontend
docker buildx bake backend
docker buildx bake audio-analyzer-clap

Build multiple groups at once:

docker buildx bake core external analysis

Override tags (useful for pushing to a registry):

docker buildx bake core --set '*.tags=ghcr.io/soundspan/soundspan-frontend:1.0.0' --set frontend.tags=ghcr.io/soundspan/soundspan-frontend:1.0.0

Override frontend build args:

docker buildx bake frontend \
  --set 'frontend.args.NEXT_PUBLIC_BUILD_TYPE=release' \
  --set 'frontend.args.NEXT_PUBLIC_LOG_LEVEL=warn'

Dry run (print resolved build configuration without building):

docker buildx bake --print core

Using bake with Compose

You can continue using docker compose up -d after building with bake — Compose will use the locally tagged images if they match. To ensure Compose uses your bake-built images instead of rebuilding, reference the image tags in your .env or compose override.

Notes

Updating a Deployment

docker compose pull
docker compose up -d

Maintainer Release Flow (Images + Helm Chart)

Use semantic versions without a v prefix (example: 1.6.0).

  1. Prepare and verify the release version surfaces in one command:
node scripts/release/version-sync.mjs --write --version 1.6.0

This updates and verifies:

It also validates the expected package names:

  1. Commit and push the release prep:
git add frontend/package.json frontend/package-lock.json backend/package.json backend/package-lock.json charts/soundspan/Chart.yaml charts/soundspan/values.yaml CHANGELOG.md
git commit -m "chore(release): prepare 1.6.0"
git push origin main
  1. Draft release notes from the previous release tag to the release commit. This renders the maintainer template in docs/maintainers/RELEASE_NOTES_TEMPLATE.md against the prepared changelog section:
node scripts/release/generate-notes.mjs \
  --version 1.6.0 \
  --from 1.5.0 \
  --to main \
  --output /tmp/soundspan-1.6.0-release-notes.md

Helm release reference for notes and operator docs:

  1. Publish the GitHub release with the same tag:
gh release create 1.6.0 --target main --notes-file /tmp/soundspan-1.6.0-release-notes.md

Publishing the release triggers:

The Helm chart workflow waits until all required release-tagged images exist in GHCR before publishing chart artifacts.

Release Channels

Stable (recommended)

docker pull ghcr.io/soundspan/soundspan:latest
# or specific version
docker pull ghcr.io/soundspan/soundspan:X.Y.Z

Main channel (development)

docker pull ghcr.io/soundspan/soundspan:main

Main-channel builds may be unstable and are not recommended for production. If you need deterministic rollbacks, use immutable main-<sha> tags.

Linux bind-mount note for /data

Named volumes are recommended. If you bind-mount /data, ensure required subdirectories exist and are writable:

mkdir -p /path/to/soundspan-data/postgres /path/to/soundspan-data/redis

If startup logs show permission errors, chown the host path to the UID/GID shown in logs.

What the AIO container includes


See also