Configuration Overview
You configure Sovereign’s server through environment variables on the container. There’s no settings page for them in the app. Values that aren’t sensitive go in your compose file or manifest, and sensitive ones go in through your platform’s secret mechanism.
How Configuration Works
Section titled “How Configuration Works”Every setting is an environment variable. Its name is a path through the settings, with a double underscore (__) between each part:
Sovereign__Authentication__AdminPasswordA setting you don’t set keeps the default listed in the reference below, so you only set the ones your deployment needs.
Sensitive Values
Section titled “Sensitive Values”Settings marked sensitive are redacted from log output. The one exception is the first-run admin username and password, which are printed once to the console when the account is created and never reach the log files. Sensitive settings must never be passed as plain inline environment variables, which land in your shell history and in any compose file you commit. Pass them through your platform’s own secret mechanism instead:
| Platform | Recommended approach |
|---|---|
| Docker | --env-file pointing to a file outside version control |
| Docker Compose | env_file: key pointing to the same kind of file |
| Kubernetes | Secret resource with secretKeyRef |
See the individual platform pages for examples. Sovereign reads its settings from environment variables, so a secret mounted as a file (a Compose or Swarm secrets: entry under /run/secrets/) is never read.
Environment Variable Reference
Section titled “Environment Variable Reference”| Environment Variable | Description | Default | Sensitive |
|---|---|---|---|
Sovereign__EnableSensitiveDataLogging |
Log database values and credentials. Never enable in production. | false |
|
Sovereign__Authentication__Issuer |
Token issuer URI, used as both the token issuer and the registered audience, and the public origin clients use to reach the server, so it must be reachable by all clients. A localhost address only works from the server’s own machine, and logs a warning at startup. An https address also turns on Strict-Transport-Security (see Production Hardening). See Authentication |
http://localhost:5000/ |
|
Sovereign__Authentication__ClientId |
OAuth client ID for the web app. If unset, one is generated on first start and stored on the data volume, so it and existing sessions survive restarts. Setting a value overrides the stored id | (generated once, then stored) | |
Sovereign__Authentication__Tokens__Web__AccessTokenMinutes |
Lifetime of an access token issued to the web app, in minutes. Access tokens aren’t re-checked against the database on every call, so a role change (for example moving someone back to Unapproved) can take up to this long to apply, while a deleted account stops working on its next request. See Session Length | 15 |
|
Sovereign__Authentication__Tokens__Web__RefreshTokenDays |
How long a browser may sit unused before it asks for a sign-in, in days. Refresh tokens slide as they rotate, so this is an idle timeout | 30 |
|
Sovereign__Authentication__Tokens__Web__MaxSessionDays |
The longest one web sign-in may last, in days, however often it’s used. 0 turns the cap off |
90 |
|
Sovereign__Authentication__Tokens__Device__AccessTokenMinutes |
As the web app’s, for the Roku and Android apps | 15 |
|
Sovereign__Authentication__Tokens__Device__RefreshTokenDays |
As the web app’s, for the Roku and Android apps. Raise it for a television that gets used sporadically | 90 |
|
Sovereign__Authentication__Tokens__Device__MaxSessionDays |
As the web app’s, for the Roku and Android apps | 0 (no cap) |
|
Sovereign__Authentication__AdminUsername |
Username for the default admin account seeded on first run | (random 12-char string) | ✓ |
Sovereign__Authentication__AdminPassword |
Password for the default admin account seeded on first run | (random 12-char string) | ✓ |
Sovereign__Authentication__Google__ClientId |
OAuth client ID enabling “Sign in with Google”. Google sign-in activates only when both this and the secret are set | (none) | |
Sovereign__Authentication__Google__ClientSecret |
OAuth client secret for Google sign-in | (none) | ✓ |
Sovereign__Authentication__Entra__ClientId |
Application (client) ID enabling “Sign in with Microsoft”. Entra sign-in activates only when both this and the secret are set | (none) | |
Sovereign__Authentication__Entra__ClientSecret |
Client secret for Microsoft Entra sign-in | (none) | ✓ |
Sovereign__Authentication__Entra__TenantId |
Directory (tenant) ID, or common to allow sign-ins from any tenant |
common |
|
Sovereign__DataSources__Radarr__N__Url |
Base URL of a Radarr instance (repeat for multiple, starting at 0) |
(none) | |
Sovereign__DataSources__Radarr__N__ApiKey |
API key for the Radarr instance | (none) | ✓ |
Sovereign__DataSources__Sonarr__N__Url |
Base URL of a Sonarr instance (repeat for multiple, starting at 0) |
(none) | |
Sovereign__DataSources__Sonarr__N__ApiKey |
API key for the Sonarr instance | (none) | ✓ |
Sovereign__DataSources__Filesystem__N__Path |
Absolute path, inside the container, of a directory of media files to import directly, with no Radarr or Sonarr in front of it (repeat for multiple, starting at 0). Scanned recursively; titles are identified on TMDb, with no key to set |
(none) | |
Sovereign__DataSources__Filesystem__N__MediaType |
What the directory holds: Movie or Television. A Mixed or Unknown entry is skipped at startup |
Movie |
|
Sovereign__DataSources__Filesystem__N__Description |
Optional label shown on the admin Media Sources page | (none) | |
Sovereign__DataSources__Tmdb__MaxMoviesPerRun |
Maximum movies to fetch credits for in a single job run; the backlog drains across consecutive runs | 500 |
|
Sovereign__DataSources__Tmdb__MaxPeoplePerRun |
Maximum people to fetch biographies for in a single job run. Each person costs one request and a large library holds tens of thousands, so the backlog drains across consecutive runs, most-credited people first | 2000 |
|
Sovereign__DataSources__Tmdb__SendCensus |
Whether the server sends Sovereign’s metadata service an anonymous daily token and its license standing, so the service can count servers once a day. The token changes every day and can’t be linked to your server. Set to false to send neither, and the server isn’t counted. See the Privacy Policy |
true |
|
Sovereign__Obfuscation__Alphabet |
Character set used to write the IDs in links, as a reordering of 0-9a-zA-Z. If unset, one is generated on first start and stored on the data volume, so those IDs survive restarts. Setting a value overrides the stored alphabet |
(generated once, then stored) | |
Sovereign__Obfuscation__AssetSecuritySecretKey |
Secret that protects the media and artwork links Sovereign hands out. If unset, one is generated on first start and stored on the data volume, so those links survive a restart. Changing this value invalidates every outstanding link | (generated once, then stored) | ✓ |
Sovereign__AssetWork__MaxConcurrentJobs |
Maximum heavy asset jobs running at once across every queue. 0 derives it from the host as half the processor count, clamped to between 1 and 4 |
0 |
|
Sovereign__AssetWork__ReservedInteractiveJobs |
Jobs held back from background work so something a viewer is waiting on can start. Capped at one below the total | 1 |
|
Sovereign__Trickplay__TimeoutMinutes |
Minutes before a trickplay generation job is cancelled and marked as an error | 30 |
|
Sovereign__Trickplay__TimeoutMinutesPerGigabyte |
Extra minutes of budget added per gigabyte of the source file, on top of TimeoutMinutes. 0 keeps a flat budget. Trickplay seeks between keyframes rather than reading the file through, so this is a backstop for an unusually slow source |
2 |
|
Sovereign__Images__MaxConcurrentDownloads |
Maximum parallel image download and conversion workers | 2 |
|
Sovereign__Images__TimeoutMinutes |
Minutes before an image download job is cancelled | 1 |
|
Sovereign__Images__TimeoutMinutesPerGigabyte |
Has no effect. An image download has no source file to scale against, so its budget is always flat | 0 |
|
Sovereign__Subtitles__TimeoutMinutes |
Minutes before a subtitle extraction job is cancelled and marked as an error | 30 |
|
Sovereign__Subtitles__TimeoutMinutesPerGigabyte |
Extra minutes of budget added per gigabyte of the source file. Bitmap subtitle extraction falls back to reading the whole file where the source carries no index to seek by, so its budget scales with the source | 2 |
|
Sovereign__Subtitles__BitmapCueCanvasMaxLongEdge |
Cap on the longer edge, in pixels, of the plane bitmap (PGS) subtitles are drawn onto. The video’s aspect is kept and the plane is never scaled up; 0 draws at the video’s own size. PGS is authored at 1080p, so an uncapped plane on a 2160p source costs four times the pixels for detail the source doesn’t carry |
1920 |
|
Sovereign__Trickster__ExecutablePath |
Advanced. Path to the bundled trickster binary, which generates both trickplay sprite sheets and bitmap subtitle cues. It loads its FFmpeg libraries from beside itself, so repointing this means moving the whole directory rather than the one file |
/opt/trickster/trickster |
|
Sovereign__Transcoding__VideoEncoderPreset |
FFmpeg encoder preset (speed/quality tradeoff): Fast, Medium, or Slow. These are the only values accepted, because they’re the only ones safe across every encoder: the x264-only presets fail hardware transcodes |
Medium |
|
Sovereign__Transcoding__MaxVideoBitrateBps |
Maximum video bitrate ceiling (bits per second) applied to transcoded streams. A user preference may request a lower value but never a higher one | 30000000 |
|
Sovereign__Transcoding__VideoQualityTier |
Quality target for transcoded video: Efficient, Balanced, or HighQuality. Drives how much detail is preserved, bounded by the bitrate ceiling. See Video Quality. A user preference may request an equal-or-lower tier but never a higher one |
HighQuality |
|
Sovereign__Transcoding__HlsSegmentDurationSeconds |
Advanced. Duration of each HLS segment in seconds, passed to FFmpeg. A latency-against-playlist-size trade; rarely worth changing | 6 |
|
Sovereign__Transcoding__MinCopySegmentDurationSeconds |
Advanced. Shortest segment a direct-played copy may be cut into when the default would produce segments too large for a client’s media buffer. Only ever applies to a copy that would otherwise be re-encoded. Can’t be longer than HlsSegmentDurationSeconds |
1 |
|
Sovereign__Transcoding__ProgressReportPeriodSeconds |
Advanced. How often, in seconds, a playback session’s FFmpeg reports its progress to the server. The reports let Sovereign fail a start whose FFmpeg has stuck well before the 30-second limit, and record the rate each session encoded at. Every report is held in memory until the session’s FFmpeg exits, so a very long session at a very short period costs a few megabytes | 1 |
|
Sovereign__Transcoding__VaapiToneMapBrightness |
Advanced, VAAPI only. Brightness offset, from -100 to 100, applied to the HDR picture going into the driver’s fixed tone curve rather than to the SDR picture coming out of it. A value outside the range is clamped into it. Ignored by every other encoder. See HDR Tone Mapping | 19 |
|
Sovereign__Transcoding__VaapiToneMapContrast |
Advanced, VAAPI only. Luma gain, from 0 to 10, applied ahead of the tone-map and paired with the brightness offset. A value below 1.0 is normal, because it acts on the HDR input, and a value outside the range is clamped into it. Setting this to 1.0 alongside a brightness of 0 turns the correction off entirely |
0.95 |
|
Sovereign__StreamLifecycle__HeartbeatIdleTimeoutSeconds |
Seconds a stream may go without a client heartbeat before it’s evicted (FFmpeg killed, stream directory deleted). The heartbeat is how Sovereign tells a stream is still being watched, so a watched stream is never evicted, even after its transcode finishes | 60 |
|
Sovereign__StreamLifecycle__EvictionScanIntervalSeconds |
How often the background scan runs to reap idle streams | 20 |
|
Sovereign__Playback__DefaultAudioLanguage |
System-wide default audio language as an ISO-639 code. Used when a stream has a matching track and the user has no preference; set to empty to use the media’s own default track instead | eng |
|
Sovereign__Playback__DefaultSubtitleLanguage |
System-wide default subtitle language as an ISO-639 code. When a stream has a matching subtitle track and the user has no preference, that track is marked the stream’s default so a client can auto-select it. Unset by default, unlike the audio equivalent: audio plays whether or not anyone chose it, so a default only picks which track, whereas defaulting subtitles would turn them on for every viewer who never asked | (none) | |
Sovereign__ContinueWatching__ResumeWindowDays |
Days a paused title keeps being offered in the Continue Watching row after it was last watched. A title left untouched for longer drops off the row. This only hides it: the saved position is kept, so the title still resumes from where it was left if you open it from its own page. Set to 0 (or any non-positive value) to keep offering in-progress titles indefinitely |
90 |
|
Sovereign__ActivityLog__RetentionDays |
Days to retain user activity log entries before the daily maintenance job prunes them. Set to 0 (or any non-positive value) to retain indefinitely |
90 |
|
Sovereign__ClientDevices__RetentionDays |
Days of inactivity after which a client device drops off the device list, pruned by the daily maintenance job. Set to 0 (or any non-positive value) to retain indefinitely |
90 |
|
Sovereign__ClientDevices__UserPickerAfterMinutes |
Minutes the Android app has to spend in the background before it asks “Who’s watching?” again on its return, on a device where two or more people are signed in. It never asks during playback. The Roku channel asks every time it starts, so this setting doesn’t change it. Set to 0 to ask only when the app starts |
60 |
|
Sovereign__ClientErrors__RetentionDays |
Days to retain client-reported errors before the daily maintenance job prunes them. Set to 0 (or any non-positive value) to retain indefinitely |
30 |
|
Sovereign__Storage__RetentionDays |
Days to retain the hourly storage samples behind the usage chart on the admin Overview page. Set to 0 (or any non-positive value) to retain indefinitely |
400 |
|
Sovereign__Storage__WarningPercentFree |
Free space, as a percentage of a monitored volume’s capacity, at or below which it’s reported as a warning. Set to 0 to grade the volume on free bytes alone |
10 |
|
Sovereign__Storage__WarningBytesFree |
Free bytes at or below which a monitored volume is a warning. This is a floor under the percentage, so a small volume is caught before the proportion starts to look alarming, and whichever of the two trips first wins. Set to 0 to grade the volume on the percentage alone |
5368709120 (5 GiB) |
|
Sovereign__Storage__CriticalPercentFree |
Percentage at or below which a monitored volume is critical. Must be no greater than the warning percentage when both are set | 5 |
|
Sovereign__Storage__CriticalBytesFree |
Free bytes at or below which a monitored volume is critical, roughly what one in-flight transcode needs to keep buffering ahead. Must be no greater than the warning figure when both are set | 1073741824 (1 GiB) |
|
Sovereign__Network__TrustedProxyNetwork__N |
Trusted proxy IP address or CIDR range for X-Forwarded-For resolution (repeat for multiple, starting at 0) |
(none) |
You can register more than one Radarr instance, Sonarr instance or trusted proxy network by incrementing the index (N), starting at 0.
Two groups of settings are documented on their own pages rather than repeated here: the Sovereign__Notification__* settings for outbound mail and the daily digest are on Email, and the Sovereign__Authentication__Certificates__* settings for supplying your own token certificates are on Authentication.
The container’s TZ variable is a standard Linux one rather than a Sovereign setting. It sets the time zone the server log’s timestamps are written in, and the image defaults it to America/Chicago, so set it to your own zone (for example TZ=Europe/London). The startup log reports which zone took effect.
Startup Validation
Section titled “Startup Validation”Sovereign checks its configuration as it starts, before it serves any requests. If any value is invalid, the server refuses to start and reports the problems it finds together in the logs. Each message names the environment variable and how to correct it, so most can be fixed in one pass.
Array Configuration Examples
Section titled “Array Configuration Examples”Multiple Radarr or Sonarr Instances
Section titled “Multiple Radarr or Sonarr Instances”Repeat the variable with an incrementing index to register more than one instance:
# Two Radarr instancesSovereign__DataSources__Radarr__0__Url=http://radarr-4k:7878Sovereign__DataSources__Radarr__0__ApiKey=<api-key-1>Sovereign__DataSources__Radarr__1__Url=http://radarr-sd:7878Sovereign__DataSources__Radarr__1__ApiKey=<api-key-2>
# Two Sonarr instancesSovereign__DataSources__Sonarr__0__Url=http://sonarr-hd:8989Sovereign__DataSources__Sonarr__0__ApiKey=<api-key-3>Sovereign__DataSources__Sonarr__1__Url=http://sonarr-4k:8989Sovereign__DataSources__Sonarr__1__ApiKey=<api-key-4>Filesystem Media Directories
Section titled “Filesystem Media Directories”A directory of films or of series can be imported with no Radarr or Sonarr in front of it. Point an entry at the directory as the container sees it, and say what it holds. Titles are named from TMDb through Sovereign’s metadata service, so there’s no API key to set.
A film is identified from its folder or file name: a {tmdb-1234}, [tmdbid-1234] or tt1234567 tag wins outright, otherwise a Title (Year) folder or a Title.Year... release name is searched on TMDb. A series is identified from its folder, where a [tvdbid-1234] or {tmdb-1234} tag wins outright, and the season and episode numbers come from each file name (S01E02, 1x02, S05E15E16). Either way a match is taken only when the title and year agree, and anything less confident is logged and skipped rather than guessed at. -sample and -trailer files and extras folders are ignored.
Sovereign__DataSources__Filesystem__0__Path=/media/MoviesSovereign__DataSources__Filesystem__0__MediaType=MovieSovereign__DataSources__Filesystem__0__Description=Films on the NASSovereign__DataSources__Filesystem__1__Path=/media/TelevisionSovereign__DataSources__Filesystem__1__MediaType=TelevisionSovereign__DataSources__Filesystem__1__Description=Series on the NASA title the directory and an Arr both supply is one entry with two files, with the Arr’s metadata taking precedence, so the two kinds of source can coexist. One directory holds one kind of media, so keep films and series in separate trees. See Filesystem Media Sources for the file and folder naming rules.
Trusted Proxy Networks
Section titled “Trusted Proxy Networks”Specify one or more IP addresses or CIDR ranges to trust for X-Forwarded-For header resolution. This is required when Sovereign runs behind a reverse proxy, so that logging and the sign-in rate limit see each client’s real IP instead of the proxy’s:
# Single proxy (exact IP)Sovereign__Network__TrustedProxyNetwork__0=192.168.1.1
# Multiple proxies / subnetsSovereign__Network__TrustedProxyNetwork__0=10.0.0.0/8Sovereign__Network__TrustedProxyNetwork__1=172.16.0.0/12Sovereign__Network__TrustedProxyNetwork__2=192.168.0.0/16Behind a proxy that isn’t listed, every request appears to come from the proxy. The activity log, the Clients page and each device’s last-seen address all show the proxy, and every user shares one sign-in rate limit, so one device retrying a sign-in can stop everyone else signing in, or renewing their session, for up to a minute. Nothing else breaks, which makes it easy to miss. To make it visible, Sovereign writes one warning to its log the first time a request arrives with an X-Forwarded-For header from an address it doesn’t trust:
A request from 172.18.0.1 carried X-Forwarded-For, but no trusted proxy network is configured, so the header was ignored. ...The address in that line is your proxy, as Sovereign sees it, and is usually what to trust. Otherwise, it depends on where the proxy runs:
- In Docker, or on the same host as a Sovereign container - Docker’s own network.
172.16.0.0/12covers the networks Docker creates by default, anddocker network inspect <network>shows the exact subnet of the one the proxy and Sovereign share. - On another machine - that machine’s address on your network, such as
192.168.1.10. - In Kubernetes - the cluster’s pod network, where the ingress controller connects from.
10.244.0.0/16is a common default, and k3s uses10.42.0.0/16.
Keep the range as narrow as your setup allows. Sovereign believes whatever client address a trusted address passes along, so a range that also covers your users’ own devices lets any of them claim to be someone else in the log and the rate limit. If your devices on 192.168.1.0/24 reach Sovereign directly and a proxy on 192.168.1.10 handles everything from outside, trust 192.168.1.10 and not the whole subnet.
After a restart, sign in from another device and open the Clients page. That device should show its own address rather than the proxy’s, and the warning shouldn’t come back. If Sovereign is reached directly with no proxy in front of it, leave the setting empty: a device that sends the header itself triggers the warning once, and it can be ignored.
Titles, artwork, people and descriptions shown in screenshots are fictional and were created for illustration. Sovereign doesn't include or supply any media: it plays the library you bring.