Documentation | Screenshots | Videos
A communication portal for media server communities. Lobby gives Plex and Jellyfin users one place for announcements, chat, requests, feedback, shared links, digest emails, and media lifecycle updates.
- Multi-method authentication: Local, Plex, Jellyfin, and OIDC with cross-provider identity resolution
- Homepage modules: Announcements, requests, service status, feedback tickets, real-time chat, shared links, recently added media, and leaving soon
- Invitations: Shareable invite links that provision Plex/Jellyfin access (and a Lobby account) on redemption, with per-invite libraries, usage limits, and time-limited memberships
- Requests and discovery: Link users to a request portal and let admins connect Overseerr or Jellyseerr for lifecycle-aware request protection
- Media lifecycle management: Rule-based media aging, grace periods, watch-history checks, and automated cleanup via Radarr/Sonarr
- Feedback and notifications: User feedback tickets, in-app notifications, and email (SMTP) for announcements, chat mentions, feedback activity, and digests
- Internationalization: 16 languages (en, es, fr, de, it, pt, nl, sv, no, da, fi, pl, ru, ja, zh, ko)
- Admin dashboard: Full configuration UI with environment variable override visibility
- Help: Built-in answers and custom community documentation, with optional declarative management
docker run -d \
--name lobby \
-p 8080:8080 \
-e BASE_URL=http://localhost:8080 \
-v lobby_config:/config \
ghcr.io/rinseaid/lobby:latestOpen http://localhost:8080 and complete the bootstrap wizard to create the initial admin account.
git clone https://github.com/rinseaid/lobby.git
cd lobby
docker compose up -dThe default compose file builds the local source tree and starts Lobby with SQLite. Open http://localhost:8080 and complete the bootstrap wizard. To use PostgreSQL instead, see docker-compose.postgres.yml.
git clone https://github.com/rinseaid/lobby.git
cd lobby
docker compose -f docker-compose.postgres.yml up -dOn first launch with no users in the database, Lobby presents a bootstrap wizard. You can either:
- Web UI: Navigate to the app and complete the setup wizard
- Environment variables: Set
BOOTSTRAP_ADMIN_USERNAME,BOOTSTRAP_ADMIN_PASSWORD, and optionallyBOOTSTRAP_ADMIN_EMAILto auto-create the admin user
After bootstrap, the admin setup wizard guides you through media server connection, authentication, and module configuration.
Lobby uses a three-tier configuration system:
- Environment variables (highest priority)
- TOML file at
/config/lobby.toml - Hardcoded defaults (lowest priority)
The admin UI writes changes to the TOML file. Fields managed by environment variables appear greyed out with a "Managed by environment variable" badge.
Copy the example config to get started:
cp config/lobby.example.toml /config/lobby.tomlSee docs/configuration.md for the full config reference and docs/screenshots.md for generated UI screenshots.
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP listen port |
HOST |
0.0.0.0 |
HTTP listen address |
BASE_URL |
http://localhost:8080 |
Public-facing URL (used for OAuth callbacks, email links) |
TRUST_PROXY |
false |
Honor X-Forwarded-For/X-Real-IP for client IPs (rate limiting, audit log). Enable ONLY behind a trusted reverse proxy |
HIDE_HELP_LINKS |
false |
Hide help/documentation links in the UI |
SKIP_TLS_VERIFY |
false |
Insecure: disables TLS verification for ALL outbound requests (every integration), not per-service |
AUTH_RATE_LIMIT_PER_MINUTE |
10 |
Per-IP request cap for auth and invite-redeem endpoints. Raise only behind a shared egress IP or for test harnesses |
CONFIG_DIR |
/config |
Directory for TOML config and SQLite database |
TZ |
UTC |
Container timezone (e.g. America/New_York) |
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
(empty) | PostgreSQL connection string. When unset, SQLite is used at CONFIG_DIR/lobby.db |
| Variable | Default | Description |
|---|---|---|
BOOTSTRAP_ADMIN_USERNAME |
admin |
Auto-create admin with this username on first launch |
BOOTSTRAP_ADMIN_PASSWORD |
(empty) | Admin password (required with username) |
BOOTSTRAP_ADMIN_EMAIL |
(empty) | Admin email (optional) |
| Variable | Default | Description |
|---|---|---|
DISABLE_LOCAL_AUTH |
false |
Disable local auth. Safety lock: requires at least one other auth method enabled |
| Variable | Default | Description |
|---|---|---|
AUTH_PLEX_ENABLED |
false |
Enable Plex authentication |
AUTH_PLEX_SERVER_URL |
(empty) | Plex server URL (e.g. http://plex:32400) |
AUTH_PLEX_MACHINE_IDENTIFIER |
(empty) | Plex server machine identifier |
AUTH_PLEX_CLIENT_IDENTIFIER |
lobby |
Plex client identifier |
PLEX_TOKEN |
(empty) | Plex authentication token |
Plex requires interactive PIN-based OAuth setup via the admin UI. Environment variables are available for automated deployments.
| Variable | Default | Description |
|---|---|---|
AUTH_JELLYFIN_ENABLED |
false |
Enable Jellyfin authentication |
AUTH_JELLYFIN_SERVER_URL |
(empty) | Jellyfin server URL (e.g. http://jellyfin:8096) |
AUTH_JELLYFIN_API_KEY |
(empty) | Jellyfin API key |
| Variable | Default | Description |
|---|---|---|
AUTH_OIDC_ENABLED |
false |
Enable OIDC authentication |
AUTH_OIDC_DISPLAY_NAME |
(empty) | Display name on the login page |
AUTH_OIDC_ISSUER_URL |
(empty) | OIDC issuer URL (must support .well-known/openid-configuration) |
AUTH_OIDC_CLIENT_ID |
(empty) | OIDC client ID |
AUTH_OIDC_CLIENT_SECRET |
(empty) | OIDC client secret |
AUTH_OIDC_REDIRECT_URI |
(empty) | OAuth callback URL (e.g. https://lobby.example.com/api/auth/oidc/callback) |
AUTH_OIDC_SCOPES |
openid profile email |
OIDC scopes. groups is auto-appended when AUTH_OIDC_GROUP_CLAIM is set |
AUTH_OIDC_GROUP_CLAIM |
groups |
Token claim containing group membership |
AUTH_OIDC_ADMIN_GROUPS |
(empty) | Comma-separated group names that grant admin access |
| Variable | Default | Description |
|---|---|---|
TMDB_API_KEY |
(empty) | TMDB API key for direct CDN poster URLs (free at themoviedb.org) |
MEDIA_SELECTED_LIBRARIES |
(empty) | Comma-separated library IDs to display in Recently Added |
| Variable | Default | Description |
|---|---|---|
SMTP_ENABLED |
false |
Enable email sending |
SMTP_HOST |
(empty) | SMTP server hostname |
SMTP_PORT |
587 |
SMTP server port |
SMTP_SECURE |
false |
Use TLS (port 465). When false, uses STARTTLS if available |
SMTP_ALLOW_INSECURE_AUTH |
false |
Permit sending PLAIN/LOGIN credentials over an unencrypted connection (no TLS/STARTTLS). Leave off unless using a trusted local relay that requires auth without encryption |
SMTP_USER |
(empty) | SMTP username |
SMTP_PASSWORD |
(empty) | SMTP password |
SMTP_FROM_ADDRESS |
noreply@lobby.local |
Sender email address |
SMTP_FROM_NAME |
Lobby |
Sender display name |
| Variable | Default | Description |
|---|---|---|
CHAT_RETENTION_DAYS |
30 |
Delete messages older than this (0 = keep forever) |
CHAT_MAX_MESSAGE_LENGTH |
2000 |
Maximum characters per message |
CHAT_MENTION_EMAIL_COOLDOWN_HOURS |
1 |
Min hours between mention emails to the same user (rate limit); first is sent promptly, more within the window are batched. 0 = every mention |
CHAT_PROFANITY_FILTER_ENABLED |
false |
Enable word-based profanity filter |
CHAT_INACTIVITY_COLLAPSE_HOURS |
24 |
Auto-collapse chat after inactivity (0 = never) |
CHAT_GIFS_ENABLED |
false |
Enable GIPHY GIF search in chat |
CHAT_GIPHY_API_KEY |
(empty) | GIPHY API key (required when GIFs enabled) |
| Variable | Default | Description |
|---|---|---|
DEFAULT_LANGUAGE |
en |
Default UI language |
| Variable | Default | Description |
|---|---|---|
FEEDBACK_ENABLED |
true |
Enable user feedback tickets and admin feedback management |
FEEDBACK_RETENTION_DAYS |
90 |
Days to retain resolved/closed feedback before cleanup |
FEEDBACK_MAX_OPEN_ISSUES |
5 |
Maximum open feedback issues each user can have at one time |
FEEDBACK_PRE_CANNED_ISSUES |
built-in list | Comma-separated key:Label issue types shown in the feedback form |
FEEDBACK_PLAYBACK_SCAN_THRESHOLD_PCT |
15 |
Percent of runtime that counts as substantial playback when suggesting feedback prompts |
FEEDBACK_PLAYBACK_SCAN_MIN_PLAYS |
2 |
Minimum playback count considered by feedback prompt scanning |
| Variable | Default | Description |
|---|---|---|
PLAYBACK_DIAGNOSTICS_ENABLED |
true |
Enable log ingestion and scanner evidence correlation |
PLAYBACK_DIAGNOSTICS_INTERVAL_MINUTES |
15 |
Minutes between background log ingestion runs |
PLAYBACK_DIAGNOSTICS_RETENTION_DAYS |
30 |
Days to retain parsed playback log events |
PLAYBACK_DIAGNOSTICS_CORRELATION_WINDOW_MINUTES |
15 |
Minutes around a low-completion play to search for log evidence |
PLAYBACK_DIAGNOSTICS_LOCAL_PLEX_LOG_PATHS |
(empty) | Comma-separated Plex log files or directories mounted read-only into Lobby |
PLAYBACK_DIAGNOSTICS_LOCAL_JELLYFIN_LOG_PATHS |
(empty) | Comma-separated Jellyfin log files or directories mounted read-only into Lobby |
PLAYBACK_DIAGNOSTICS_LOKI_URL |
(empty) | Loki base URL, e.g. http://loki:3100 |
PLAYBACK_DIAGNOSTICS_LOKI_TENANT_ID |
(empty) | Optional Loki tenant header |
PLAYBACK_DIAGNOSTICS_LOKI_USERNAME |
(empty) | Optional Loki basic auth username |
PLAYBACK_DIAGNOSTICS_LOKI_PASSWORD |
(empty) | Optional Loki basic auth password |
PLAYBACK_DIAGNOSTICS_LOKI_BEARER_TOKEN |
(empty) | Optional Loki bearer token |
PLAYBACK_DIAGNOSTICS_LOKI_PLEX_SELECTOR |
{namespace="plex"} |
Loki selector for Plex logs |
PLAYBACK_DIAGNOSTICS_LOKI_JELLYFIN_SELECTOR |
{namespace="jellyfin"} |
Loki selector for Jellyfin logs |
PLAYBACK_DIAGNOSTICS_LOKI_LIMIT |
1000 |
Maximum Loki log entries fetched per source per ingest |
See Playback Diagnostics for Docker mount examples, Loki setup, sanitization, and testing.
| Variable | Default | Description |
|---|---|---|
LIFECYCLE_ENABLED |
false |
Enable the lifecycle management engine |
LIFECYCLE_SCHEDULE_HOUR |
2 |
Hour (0-23) for daily lifecycle evaluation |
LIFECYCLE_SCHEDULE_MINUTE |
30 |
Minute (0-59) for daily lifecycle evaluation |
LIFECYCLE_SCHEDULE_CRON |
(empty) | Full cron expression (overrides hour/minute when set) |
LIFECYCLE_DRY_RUN |
false |
Simulate evaluation, staging, and automatic actions without changing lifecycle item state or calling external actions |
LIFECYCLE_DELETE_FILES |
true |
Delete physical files when removing media from Radarr/Sonarr |
LIFECYCLE_ADD_IMPORT_EXCLUSION |
true |
Add import exclusion in Radarr/Sonarr when deleting media |
LIFECYCLE_LEAVING_SOON_ENABLED |
true |
Show Leaving Soon module on homepage |
LIFECYCLE_SAVE_TOKEN_SECRET |
(empty) | HMAC secret for magic save tokens. Generate with openssl rand -hex 32 |
LIFECYCLE_SAVE_TOKEN_TTL_HOURS |
72 |
Save token validity period |
LIFECYCLE_PROTECTED_REQUESTERS |
(empty) | Comma-separated Seerr usernames whose requests prevent deletion |
LIFECYCLE_USER_SYNC_INTERVAL_HOURS |
6 |
Hours between media server user/watch history syncs |
LIFECYCLE_RULES_SYNC_MODE |
merge |
How config-managed rules sync to DB: merge (add/update, keep removed) or replace (delete config-managed rules not in files) |
| Variable | Default | Description |
|---|---|---|
SEERR_URL |
(empty) | Seerr/Overseerr/Jellyseerr URL |
SEERR_API_KEY |
(empty) | Seerr API key |
SEERR_MAX_AUTO_APPROVE_SEASONS |
1 |
Maximum TV seasons Lobby will submit in one request. Higher-count requests are blocked in Lobby |
SEERR_PUBLIC_RECENT_REQUESTS |
false |
Allow non-admin users to see shared recent request titles and statuses |
SEERR_PUBLIC_RECENT_REQUEST_USERS |
false |
Show requester names in shared recent requests. Leave disabled for the private default |
SEERR_PUBLIC_URL |
(empty) | Public-facing Seerr URL for links in save emails |
RADARR_URL |
(empty) | Radarr URL |
RADARR_API_KEY |
(empty) | Radarr API key |
SONARR_URL |
(empty) | Sonarr URL |
SONARR_API_KEY |
(empty) | Sonarr API key |
LIFECYCLE_ARR_SAVE_TAG |
lobby-saved |
Tag applied in Radarr/Sonarr when items are saved. Empty to disable |
APPRISE_URL |
(empty) | Apprise notification endpoint for lifecycle events |
TAUTULLI_URL |
(empty) | Tautulli instance URL (overrides Plex watch history sync) |
TAUTULLI_API_KEY |
(empty) | Tautulli API key |
JELLYSTAT_URL |
(empty) | Jellystat instance URL (overrides Jellyfin watch history sync) |
JELLYSTAT_API_KEY |
(empty) | Jellystat API key |
| Variable | Default | Description |
|---|---|---|
PLEX_MOVIES_SECTION |
(empty) | Plex movies library section ID for lifecycle |
PLEX_TV_SECTION |
(empty) | Plex TV library section ID for lifecycle |
PLEX_MOVIES_KEEP_COLLECTION |
(empty) | Plex collection name to protect movies from deletion |
PLEX_TV_KEEP_COLLECTION |
(empty) | Plex collection name to protect TV shows from deletion |
PLEX_PUBLIC_URL |
(empty) | Public-facing Plex URL for email links |
JELLYFIN_MOVIES_SECTION |
(empty) | Jellyfin movies library ID for lifecycle |
JELLYFIN_TV_SECTION |
(empty) | Jellyfin TV library ID for lifecycle |
JELLYFIN_MOVIES_KEEP_COLLECTION |
(empty) | Jellyfin collection name to protect movies from deletion |
JELLYFIN_TV_KEEP_COLLECTION |
(empty) | Jellyfin collection name to protect TV shows from deletion |
JELLYFIN_PUBLIC_URL |
(empty) | Public-facing Jellyfin URL for browser and email links; defaults to AUTH_JELLYFIN_SERVER_URL |
LEAVING_BASE_URL |
(empty) | Base URL for "leaving soon" email links (defaults to BASE_URL) |
Lifecycle rules can be defined as YAML files in CONFIG_DIR/rules/ (default: /config/rules/). This provides a version-controllable, GitOps-friendly alternative to creating rules through the admin UI.
On startup, Lobby loads all .yaml and .yml files from the rules directory and syncs them to the database. Config-managed rules are marked with a lock icon in the admin UI and cannot be edited through the web interface.
Creating a rule file:
name: remove-unwatched-movies
media_type: movie
grace_days: 30
enabled: true
priority: 100
criteria:
never_watched:
enabled: true
check_plex_views: true
no_active_request:
enabled: true
recently_added:
enabled: true
days: 14
protections:
protected_users: [admin]
protected_tags: [keep-forever]
protected_collections: [Best Of 2024]
actions:
- type: delete
- type: delete_files
- type: import_exclusionSync modes (LIFECYCLE_RULES_SYNC_MODE):
| Mode | Behavior |
|---|---|
merge (default) |
Add new rules, update existing config-managed rules. Rules removed from files are kept in the DB |
replace |
Same as merge, but config-managed rules not present in files are deleted. UI-managed rules are never deleted |
See config/rules/example-remove-unwatched-movies.yaml and config/rules/example-cleanup-old-seasons.yaml for fully commented examples.
Admin-configurable modules displayed on the homepage. Each can be enabled, disabled, and reordered.
| Module | Description |
|---|---|
| Announcements | Scheduled messages with optional email notifications |
| Requests | Homepage search and request flow backed by Overseerr or Jellyseerr |
| Recent Requests | Recent request titles and statuses, with requester privacy controlled by the Seerr sharing settings |
| Service Status | Public health cards for Plex, Jellyfin, request portals, and other monitored services |
| Chatbox | Real-time WebSocket chat with @mentions and GIF support |
| Custom Links | Admin-defined links with icons, grouping, and access control |
| Feedback | User issue reports with admin triage and playback diagnostics |
| Recently Added | New media from Plex/Jellyfin, grouped by library |
| Leaving Soon | Media staged for removal with grace countdown and Save button |
The admin area is accessible to users with the admin flag. The sidebar is organized into:
Integrations (collapsible): Media Servers, Downloaders, Discovery, Service Status
Configuration (collapsible): Authentication, Users, Invitations, Notifications
Content (collapsible): Modules, Help, Announcements, Chat, Feedback, Digest, Lifecycle, Watch History, Links, Recently Added
Standalone: System
| Endpoint | Description |
|---|---|
GET /healthz |
Returns 200 if the server is running |
GET /readyz |
Returns 200 if the database is reachable |
| Layer | Technology |
|---|---|
| Backend | Go (chi, sqlx, gorilla/websocket) |
| Frontend | React, Vite, Tailwind CSS, React Query |
| Database | SQLite (default) or PostgreSQL |
| Deployment | Single binary, multi-stage Docker (~30MB) |
The Go binary embeds the built React SPA and serves it at /. API routes are at /api/. Non-API requests fall through to index.html for client-side routing.
| Document | Description |
|---|---|
| Configuration | Three-tier config system, full reference |
| Authentication | All auth methods, identity resolution, OIDC groups |
| Users | User database, local creation, Plex/Jellyfin import, sync |
| Invitations | Invite links, provisioning, expiration, onboarding |
| Media Servers | Connecting Plex and Jellyfin, libraries, tokens |
| Arr Instances | Multi-instance Radarr/Sonarr configuration |
| Watch History | Tautulli/Jellystat sync, native sources, lifecycle feed |
| Discovery | TMDB posters, rating sources, Overseerr/Jellyseerr |
| Modules | Homepage modules, layout, navigation tabs |
| Help | Built-in answers and custom community documentation |
| Recently Added | Recently added homepage module and tab |
| Leaving Soon | Homepage module, Save control, signed save tokens |
| Announcements | Time-bounded broadcasts, scheduling, templates |
| Service Status | Plex, Jellyfin, custom HTTP checks, incident announcements |
| Chat | WebSocket chat, mentions, profanity filter, GIFs |
| Feedback | Issue tickets, comments, attachments, scanner |
| Notifications | In-app and email notifications, SMTP, digests |
| Digest | Digest rules admin UI, templates, test email |
| Links | Custom navigation links, groups, visibility |
| Lifecycle | Lifecycle engine overview, staging, grace periods |
| Lifecycle Rules | Criteria types, protections, rule priority |
| Declarative Rules | YAML rule files, sync modes, GitOps workflow |
| Lifecycle Actions | Action pipeline, Radarr/Sonarr integration |
| Playback Diagnostics | Playback scan, issue detection, Docker mounts |
| Internationalization | 16 languages, locale detection, adding a language |
| Screenshots | Capturing and updating documentation screenshots |
| Development | Local dev setup, project structure, testing |
MIT
