Configuration
bitwerk runs without a configuration file: the server sets up the admin password and the secret for the stream links itself during the initial setup; everything else has sensible defaults. You only need to change what depends on your environment – usually the music folder, and the installer sets that.
Everything you may want to change while bitwerk runs is set by an admin in the web UI (Settings) and stored in the database; a value saved there wins over its environment variable, which wins over the default. Environment variables are for what has to be known before the web UI runs (database, ports, folders) and for developers:
- on the server in the optional
.envnext to thedocker-compose.yml(/opt/bitwerk/.envafter the installer; template with all variables:.env.example). After a change:cd /opt/bitwerk && sudo docker compose up -d. - on the Pis in
/etc/hifi/player.env;hifi-setupwrites it for you (Set up a Raspberry Pi).
Every variable is also available as a CLI flag (--help). The complete list is in the
README.md of the repository (section “Configuration”). The most important ones are here, sorted by service.
Server: installation and images
Section titled “Server: installation and images”| Variable | Meaning | Default |
|---|---|---|
MUSIC_HOST_DIR | Music folder on the host (contains uploads/ and library/) | /srv/bitwerk/music |
MUSIC_READ_ONLY | true mounts the music folder read-only (a library bitwerk only reads, e.g. on a NAS): no uploads, no sorting | false |
BITWERK_IMAGES | Registry prefix of the prebuilt images (set by the installer) | ghcr.io/arteiii |
BITWERK_VERSION | Image version, e.g. 1.2 or 1.2.0 | latest |
BITWERK_SOURCE | Build context if an image is missing (Git URL or checkout) | GitHub repository |
COMPOSE_PROFILES | https also starts the HTTPS proxy (set by install.sh --https) | – |
DRIVE_SETUP | false = the installer leaves the host’s drive mounting alone (set by install.sh --no-drives, --drives sets it back to true); read by the installer only | – |
BACKUP_TIMER | false = no scheduled backups (set by install.sh --no-backup-timer); read by the installer only | – |
BACKUP_KEEP | Database backups kept by the scheduled backup and updates; read by the installer only | 5 |
BACKUP_STATE_DIR | control-server: folder shared with the host’s backup job, for Settings → Backups; set in the Compose files | /var/lib/hifi/backup |
The BITWERK_* variables only apply to the installer’s Compose file
(docker-compose.release.yml); the docker-compose.yml in the repository always builds from source. The backup
interval (every 72 hours by default) is set in the web UI under Settings → Backups
(Operations).
| Port | Service | Who connects |
|---|---|---|
| 8080/tcp, 80/tcp | control-server: web UI, REST API, WebSocket (80 only when it is free) | Browsers, players, displays, Stream Deck |
| 8443/tcp, 443/tcp | control-server: the same over HTTPS with certificates from its own certificate authority (Spotify redirect URI, passkeys; HTTPS_BIND=off turns it off) | Browsers |
| 53535/udp | control-server: discovery | Players (broadcast) |
| 53536/udp | control-server: shared clock of the synchronous rooms | Players |
| 9000/udp | control-server: OSC | Faders, TouchOSC |
| 8082/tcp | media-service: streams, cover art, playback-info | Players |
| 8083/tcp, 1900/udp | control-server: UPnP renderer and SSDP (off by default) | UPnP control point apps |
| 8084/tcp | ai-service (only on 127.0.0.1) | only the control-server |
| 5432/tcp | Postgres (only on 127.0.0.1) | only the server services |
| 3000/tcp | Kiosk on the Pi | local only (127.0.0.1) |
The server’s firewall must allow 8080, 8082, 53535/udp, 53536/udp and, if needed, 80, 443, 8443, 9000/udp and (with the UPnP renderer) 8083 and 1900/udp from the LAN. Postgres needs no access from outside.
Server: access and security
Section titled “Server: access and security”| Variable | Meaning | Default |
|---|---|---|
ADMIN_PASSWORD_HASH | Admin password as an argon2 hash (control-server --hash-password), instead of setting it in the browser | – (initial setup in the browser) |
ADMIN_PASSWORD | Plain-text alternative (≥ 8 characters), for testing only: it gets a new hash at every start, so its logins end with each restart. Use ADMIN_PASSWORD_HASH or your own user account (Settings → Access) instead | – |
API_TOKENS | Fixed API tokens (each ≥ 32 characters) for the control-server API; tokens for the media API (port 8082) and more come from the web UI under Access | – |
MEDIA_URL_SECRET | Secret for signed stream links (≥ 32 characters); set identically on control-server and media-service | – (generated by the server, stored in the database) |
SETUP_CODE_TTL_SECS | Validity of the setup code of the initial setup; afterwards the next attempt generates a new one | 86400 (24 h) |
SESSION_TTL_HOURS | How long a login lasts without use; every visit renews it, and restarts and updates do not log you out | 720 (30 days) |
PASSKEY_ORIGIN | The HTTPS address you open bitwerk at, e.g. https://hifi.example; user accounts can then sign in with a passkey (fingerprint, face, device PIN or security key). Passkeys work only at exactly this address, never at an IP address | – (off) |
SESSION_COOKIE_SECURE | true behind an HTTPS reverse proxy | false |
LOGIN_MAX_ATTEMPTS, LOGIN_LOCKOUT_SECS | Protection against password guessing per IP | 5, 300 |
TRUSTED_PROXIES | Reverse proxies on another machine whose X-Forwarded-For names the browser for that protection, IP addresses, comma-separated (a proxy on the same machine is always trusted) | – |
OSC_ALLOWED_IPS | Allowed OSC senders (IPs/CIDR, * = all) | loopback only |
OSC_FEEDBACK | Controllers that get the rooms’ state over OSC, IP:port, comma-separated (controllers can also subscribe with /hifi/feedback) | – |
AUTH_DISABLED | Everything without login (for tests only!) | false |
Server: library
Section titled “Server: library”| Variable | Meaning | Default |
|---|---|---|
MUSIC_DIR | Root with uploads/ and library/ (inside the container) | /data/music |
UPLOAD_DEBOUNCE_MS | Quiet time before an upload is processed | 2500 |
UPLOAD_MAX_FILE_MB | Largest file the web upload takes, in MiB; 0 turns it off | 4096 |
UPLOAD_STAGING_TTL_HOURS | Hours after which an abandoned web upload (closed tab, lost network) is removed; 0 keeps it until the next restart | 24 |
SKIP_LOUDNESS_ANALYSIS | No R128 measurement when ReplayGain tags are missing; only a fallback, the web UI’s server setting analysis.loudness wins | false |
SKIP_QUALITY_ANALYSIS | No file quality analysis (band edge, effective bits, clipping, defects); fallback for analysis.quality | false |
SKIP_LIBRARY_SCAN | No full scan on start | false |
SCAN_CONCURRENCY | Files the library scan reads and measures in parallel; 0 = number of CPU cores (lower on a slow NAS disk) | 0 |
FULL_LIBRARY_SCAN | Re-read every file on start, even unchanged ones (measurements are kept) | false |
RESCAN_INTERVAL_SECS | Seconds between periodic rescans (missed uploads, changed and deleted files); 0 = only with Rescan | 600 |
DRIVE_MOUNT_DIRS | Folders (comma-separated) below which mounted USB drives are offered as external libraries (External drives); empty = off | /media |
DRIVES_PROPAGATION | Mount propagation of the host’s /media in the media-service: rslave shows drives mounted later; rprivate on hosts where /media is on a private mount (the installer sets it there) | rslave |
WRITE_GUARD | Let the kernel refuse every write of the media-service outside the music folder and the cover cache (Landlock), so external libraries stay read-only even on a writable mount; false = off | true |
COVER_LOOKUP | Look up missing covers online (MusicBrainz + Cover Art Archive, no API key); false = no cover requests. Usually switched in the web UI’s server settings, which take precedence | true |
METADATA_LOOKUP | Artist photos and bios, album background, “About this song” next to the lyrics (MusicBrainz, Wikidata, Wikipedia, Commons; no API key, sources and licenses shown in the UI); false = off. Both false = no internet requests. Also in the web UI’s server settings | true |
METADATA_LANGUAGE | Wikipedia language of the bios (en, de, …); English when the article is missing. Also in the web UI’s server settings | en |
LYRICS_LOOKUP | Song lyrics from LRCLIB (no API key) for tracks without a sidecar .lrc file or lyrics tag, and for Spotify and radio; false = local lyrics only | true |
LRCLIB_URL | Address of LRCLIB (or a self-hosted mirror) | https://lrclib.net/ |
MEDIA_PUBLIC_URL | Base URL for stream links if the Host header does not match (proxy) | – |
Server: integrations
Section titled “Server: integrations”| Variable | Meaning |
|---|---|
SPOTIFY_CLIENT_ID, SPOTIFY_REDIRECT_URI | Spotify link, if not set under Settings → Spotify (which takes precedence) |
MQTT_URL | MQTT broker for triggers, e.g. mqtt://user:pass@broker:1883 (do not set to empty), if not set in the web UI’s server settings (which take precedence) |
HA_DISCOVERY, HA_DISCOVERY_PREFIX, HA_BASE_TOPIC | Home Assistant entities per room over the MQTT broker (on by default, prefix homeassistant, topics under bitwerk), if not set in the web UI |
LISTENBRAINZ_API_URL | ListenBrainz API for scrobbling (a self-hosted ListenBrainz works too) |
LASTFM_API_KEY, LASTFM_API_SECRET | Your Last.fm API account (create one at last.fm/api/account/create), if not entered under Settings → Listening (which takes precedence); with both set you can connect Last.fm for scrobbling |
ALLOWED_MACS | Clients that are approved on start (pairing is still required) |
Player (/etc/hifi/player.env)
Section titled “Player (/etc/hifi/player.env)”| Variable | Meaning | Default |
|---|---|---|
CLIENT_ID | Name of the room in the player selection; a name assigned in the web UI (“Rename”) takes precedence | client-<MAC> |
HIFI_MAC | MAC with which the player registers (special cases only) | MAC of the network card with the route to the server |
ALSA_DEVICE | Output device, always hw: for bit-perfect playback; empty: the first USB or HAT card the player finds | empty |
HIFI_RATES | Rates the DAC can do natively | 44.1–192 kHz |
HIFI_MAX_BITS | Highest bit depth (16, 24, 32) | 24 |
HIFI_DSD | none, dop, native | none |
HIFI_VOLUME | software, hardware (DAC mixer), fixed | software |
HIFI_MIXER | Mixer element for hardware | automatic |
HIFI_BUFFER_MS, HIFI_PREFILL_MS | Jitter buffer and prefill | 2000, 500 |
HIFI_PAIRING_CODE | One-time pairing code (alternative to --mode pair) | – |
HIFI_SERVER, HIFI_PLAYER_ID | Set the server explicitly instead of discovery | – |
KIOSK_SESSION, KIOSK_ROTATE | Display: wayland/x11, rotation 0/90/180/270 | auto, 0 |
DAC-specific values for HiFiBerry, Allo, IQaudIO and USB DACs: Set up a Raspberry Pi.
Recommendations
Section titled “Recommendations”- Set the password in the browser (initial setup) or as a hash in
ADMIN_PASSWORD_HASH, not in plain text in the.env. - HTTPS via a reverse proxy (Compose profile
httpswith Caddy, see Operations) if the web UI is reachable over Wi-Fi with guests. Without TLS an attacker on the same network can read cookies. Then setSESSION_COOKIE_SECURE=true. - Approve OSC only for known devices.
- Backups: the installer backs up the database every 72 hours; back up the music folder and, if needed, the
.envyourself (Operations). The cover cache can be regenerated at any time.