Your personal media companion, built with Phoenix LiveView
A modern, self-hosted media management platform for tracking, organizing, and monitoring your media library.
- πΊ Smart Media Library β Track TV shows, movies, and episodes with rich metadata from TMDB/TVDB
- π Media Discovery β Search and add content with automatic metadata matching and disambiguation
- π Library Scanner β Automatic scanning and import of existing media files
- π¬ Detailed Media Pages β View comprehensive information including cast, crew, seasons, and episodes
- π Quality Profiles β Customizable quality preferences for automated downloads
- β¬οΈ Download Client Integration β Seamless connectivity with torrent clients (qBittorrent, Transmission) and Usenet clients (SABnzbd, NZBGet)
- π Indexer Support β Integrated search via Prowlarr and Jackett for finding releases
- π€ Automatic Search & Download β Background jobs to automatically find and download monitored content
- π― Smart Release Ranking β Pluggable scoring system to select the best matching releases
- π₯ Manual Search β Browse and select specific releases from the UI
- π Download Queue β Real-time monitoring of active downloads with progress tracking
- π Usenet Support β Native support for Usenet downloads with SABnzbd and NZBGet clients
- π Release Calendar β Track upcoming and past releases with timeline view
- ποΈ Episode Monitoring β Monitor individual episodes, seasons, or entire series
- π Missing Episodes β Identify gaps in your library
- β±οΈ Background Jobs β Automated scanning, searching, and importing with Oban
- π₯ Role-Based Access β Admin and guest user roles with appropriate permissions
- π« Guest Request System β Guests can browse media and submit requests for new content
- β Request Approval Workflow β Admins review and approve/reject guest requests with notes
- π Request Tracking β Users can monitor the status of their media requests
- π Multi-Auth Support β Local authentication and OIDC/OpenID Connect integration
- π Auto-Promotion β First OIDC user automatically promoted to admin role
- βοΈ Admin Dashboard β System status, configuration management, and health monitoring
- π§ Flexible Configuration β Environment variables, YAML files, or database settings with clear precedence
- π¨ Modern UI β Built with Phoenix LiveView, Tailwind CSS, and DaisyUI
- π³ Docker Ready β Pre-built images for amd64 and arm64 platforms
| Dashboard | Movies |
| TV Shows | Calendar View |
| Search |
Multi-platform images are available for the following architectures:
| Architecture | Available | Tag |
|---|---|---|
| x86-64 | β | amd64-latest |
| arm64 | β | arm64-latest |
The multi-arch image ghcr.io/getmydia/mydia:latest will automatically pull the correct image for your architecture.
- Generate required secrets:
# Generate SECRET_KEY_BASE
openssl rand -base64 48
# Generate GUARDIAN_SECRET_KEY
openssl rand -base64 48- Set up your container using Docker Compose (recommended) or Docker CLI
- Access the web interface at
http://your-server:4000 - On first startup, a default admin user is automatically created:
- Check the container logs for the generated password
- Default username:
admin(configurable viaADMIN_USERNAME) - Or set
ADMIN_PASSWORD_HASHto use a pre-hashed password
- Configure download clients and indexers in the Admin section
Here are some example snippets to help you get started creating a container.
---
services:
mydia:
image: ghcr.io/getmydia/mydia:latest
container_name: mydia
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
- SECRET_KEY_BASE=your-secret-key-base-here # Required: generate with openssl rand -base64 48
- GUARDIAN_SECRET_KEY=your-guardian-secret-key-here # Required: generate with openssl rand -base64 48
- PHX_HOST=localhost # Change to your domain
- PORT=4000
- MOVIES_PATH=/media/library/movies
- TV_PATH=/media/library/tv
volumes:
- /path/to/mydia/config:/config
- /path/to/your/media:/media # Single mount enables hardlinks between downloads and libraries
ports:
- 4000:4000
restart: unless-stoppedNote: This example uses a single
/mediamount to enable hardlink support. Organize your host directory with subdirectories like/path/to/your/media/downloads,/path/to/your/media/library/movies, and/path/to/your/media/library/tv. Configure your download client to save to/media/downloads.
docker run -d \
--name=mydia \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=America/New_York \
-e SECRET_KEY_BASE=your-secret-key-base-here \
-e GUARDIAN_SECRET_KEY=your-guardian-secret-key-here \
-e PHX_HOST=localhost \
-e PORT=4000 \
-e MOVIES_PATH=/media/library/movies \
-e TV_PATH=/media/library/tv \
-p 4000:4000 \
-v /path/to/mydia/config:/config \
-v /path/to/your/media:/media \
--restart unless-stopped \
ghcr.io/getmydia/mydia:latestNote: This example uses a single
/mediamount to enable hardlink support. Configure your download client to save to/media/downloads.
Container images are configured using parameters passed at runtime. These parameters are separated by a colon and indicate external:internal respectively.
| Parameter | Function |
|---|---|
4000:4000 |
Web interface |
| Env | Function |
|---|---|
PUID=1000 |
User ID for file permissions - see User / Group Identifiers below |
PGID=1000 |
Group ID for file permissions - see User / Group Identifiers below |
TZ=UTC |
Timezone (e.g., America/New_York) |
SECRET_KEY_BASE |
Required - Phoenix secret key (generate with: openssl rand -base64 48) |
GUARDIAN_SECRET_KEY |
Required - JWT signing key (generate with: openssl rand -base64 48) |
PHX_HOST=localhost |
Public hostname for the application |
PORT=4000 |
Web server port |
MOVIES_PATH=/media/movies |
Movies directory path |
TV_PATH=/media/tv |
TV shows directory path |
See the Environment Variables Reference section below for complete configuration options including download clients, indexers, and authentication.
| Volume | Function |
|---|---|
/config |
Application data, database, and configuration files |
/media/movies |
Movies library location |
/media/tv |
TV shows library location |
/media/downloads |
Download client output directory (optional) |
π‘ Hardlink Support for Efficient Storage
For optimal storage efficiency, mydia uses hardlinks by default when importing media. Hardlinks allow the same file to appear in both your download folder and library folder without consuming additional disk space.
To enable hardlinks, ensure your downloads and library directories are on the same filesystem by using a single parent volume mount:
volumes: - /path/to/mydia/config:/config - /path/to/your/media:/media # Single mount for downloads AND librariesThen organize your host directory structure like:
/path/to/your/media/ βββ downloads/ # Download client output βββ library/ β βββ movies/ # Movies library β βββ tv/ # TV libraryAnd configure environment variables:
environment: - MOVIES_PATH=/media/library/movies - TV_PATH=/media/library/tvConfigure your download client (qBittorrent, Transmission, etc.) to save files to
/media/downloads.Benefits:
- Instant file operations (no data copying)
- Zero duplicate storage space
- Files remain seeding in your download client while available in your library
Note: If your downloads and libraries must be on different filesystems, mydia will automatically fall back to copying files. This works fine but uses more storage space and takes longer.
When using volumes (-v flags), permissions issues can arise between the host and container. To avoid this, specify the user PUID and group PGID to ensure files created by the container are owned by your user.
Finding your IDs:
id your_userExample output: uid=1000(your_user) gid=1000(your_user)
Use these values for PUID and PGID in your container configuration.
Conflict Handling:
The entrypoint script automatically handles conflicts when the specified PUID/PGID is already in use by another user or group in the container. For example, GID 100 is typically used by the "users" group in Alpine Linux. The container will automatically resolve these conflicts by removing the conflicting user/group and applying your specified IDs.
Common scenarios:
- Using
PGID=100(conflicts with "users" group) - automatically handled β - Using
PUID=99(conflicts with "nobody" user on some systems) - automatically handled β - Any custom UID/GID that matches your host system - automatically handled β
The container logs will show which conflicts were resolved during startup.
docker compose pull
docker compose up -ddocker stop mydia
docker rm mydia
docker pull ghcr.io/getmydia/mydia:latest
# Run your docker run command againNote: Migrations run automatically on startup. Your data in /config is preserved across updates.
See DEPLOYMENT.md for advanced deployment topics.
| Variable | Description | Example |
|---|---|---|
SECRET_KEY_BASE |
Phoenix secret key for cookies/sessions | Generate with: openssl rand -base64 48 |
GUARDIAN_SECRET_KEY |
JWT signing key for authentication | Generate with: openssl rand -base64 48 |
| Variable | Description | Default |
|---|---|---|
PUID |
User ID for file permissions | 1000 |
PGID |
Group ID for file permissions | 1000 |
TZ |
Timezone (e.g., America/New_York, Europe/London) |
UTC |
DATABASE_PATH |
Path to SQLite database file | /config/mydia.db |
| Variable | Description | Default |
|---|---|---|
PHX_HOST |
Public hostname for the application | localhost |
PORT |
Web server port | 4000 |
HOST |
Server binding address | 0.0.0.0 |
URL_SCHEME |
URL scheme for external links (http/https) | http |
PHX_CHECK_ORIGIN |
WebSocket origin checking. Set to false to allow all origins (useful for IP-based access), or comma-separated list of allowed origins |
Allows PHX_HOST with any scheme |
| Variable | Description | Default |
|---|---|---|
MOVIES_PATH |
Movies directory path | /media/movies |
TV_PATH |
TV shows directory path | /media/tv |
MEDIA_SCAN_INTERVAL_HOURS |
Hours between library scans | 1 |
| Variable | Description | Default |
|---|---|---|
LOCAL_AUTH_ENABLED |
Enable local username/password auth | true |
ADMIN_USERNAME |
Default admin username (created on first startup) | admin |
ADMIN_EMAIL |
Default admin email (created on first startup) | [email protected] |
ADMIN_PASSWORD_HASH |
Pre-hashed admin password (bcrypt). If not set, a random password is generated and logged | - |
OIDC_ENABLED |
Enable OIDC/OpenID Connect auth | false |
OIDC_DISCOVERY_DOCUMENT_URI |
OIDC discovery endpoint URL | - |
OIDC_CLIENT_ID |
OIDC client ID | - |
OIDC_CLIENT_SECRET |
OIDC client secret | - |
OIDC_REDIRECT_URI |
OIDC callback URL | Auto-computed |
OIDC_SCOPES |
Space-separated scope list | openid profile email |
User Roles:
- Admin: Full access to all features including media management, downloads, configuration, and request approval
- Guest: Can browse media library and submit requests for new content that require admin approval
OIDC Auto-Promotion: The first user to log in via OIDC is automatically promoted to admin role. Subsequent OIDC users are assigned the guest role by default.
Admin User Creation:
On first startup, if no admin user exists, Mydia automatically creates one:
- Random Password (default): A secure random password is generated and displayed in the container logs
- Pre-set Password: Use
ADMIN_PASSWORD_HASHwith a bcrypt hash for production deployments
Generate a bcrypt hash:
# Using Elixir/Mix (if available)
mix run -e "IO.puts Bcrypt.hash_pwd_salt(\"your_secure_password\")"
# Using Python
python3 -c "import bcrypt; print(bcrypt.hashpw(b'your_secure_password', bcrypt.gensalt()).decode())"Configure multiple download clients using numbered environment variables (<N> = 1, 2, 3, etc.):
| Variable Pattern | Description | Example |
|---|---|---|
DOWNLOAD_CLIENT_<N>_NAME |
Client display name | qBittorrent |
DOWNLOAD_CLIENT_<N>_TYPE |
Client type (qbittorrent, transmission, sabnzbd, nzbget, http) | qbittorrent |
DOWNLOAD_CLIENT_<N>_ENABLED |
Enable this client | true |
DOWNLOAD_CLIENT_<N>_PRIORITY |
Client priority (higher = preferred) | 1 |
DOWNLOAD_CLIENT_<N>_HOST |
Client hostname or IP | qbittorrent |
DOWNLOAD_CLIENT_<N>_PORT |
Client port | 8080 |
DOWNLOAD_CLIENT_<N>_USE_SSL |
Use SSL/TLS connection | false |
DOWNLOAD_CLIENT_<N>_USERNAME |
Authentication username (Transmission, qBittorrent, NZBGet) | - |
DOWNLOAD_CLIENT_<N>_PASSWORD |
Authentication password (Transmission, qBittorrent, NZBGet) | - |
DOWNLOAD_CLIENT_<N>_API_KEY |
API key (SABnzbd) | - |
DOWNLOAD_CLIENT_<N>_CATEGORY |
Default download category | - |
DOWNLOAD_CLIENT_<N>_DOWNLOAD_DIRECTORY |
Download output directory | - |
Supported Download Clients:
- Torrent Clients: qBittorrent, Transmission
- Usenet Clients: SABnzbd, NZBGet
Example configurations:
# qBittorrent
DOWNLOAD_CLIENT_1_NAME=qBittorrent
DOWNLOAD_CLIENT_1_TYPE=qbittorrent
DOWNLOAD_CLIENT_1_HOST=qbittorrent
DOWNLOAD_CLIENT_1_PORT=8080
DOWNLOAD_CLIENT_1_USERNAME=admin
DOWNLOAD_CLIENT_1_PASSWORD=adminpass
# Transmission
DOWNLOAD_CLIENT_2_NAME=Transmission
DOWNLOAD_CLIENT_2_TYPE=transmission
DOWNLOAD_CLIENT_2_HOST=transmission
DOWNLOAD_CLIENT_2_PORT=9091
DOWNLOAD_CLIENT_2_USERNAME=admin
DOWNLOAD_CLIENT_2_PASSWORD=adminpass
# SABnzbd (Usenet)
DOWNLOAD_CLIENT_3_NAME=SABnzbd
DOWNLOAD_CLIENT_3_TYPE=sabnzbd
DOWNLOAD_CLIENT_3_HOST=sabnzbd
DOWNLOAD_CLIENT_3_PORT=8080
DOWNLOAD_CLIENT_3_API_KEY=your-sabnzbd-api-key
# NZBGet (Usenet)
DOWNLOAD_CLIENT_4_NAME=NZBGet
DOWNLOAD_CLIENT_4_TYPE=nzbget
DOWNLOAD_CLIENT_4_HOST=nzbget
DOWNLOAD_CLIENT_4_PORT=6789
DOWNLOAD_CLIENT_4_USERNAME=nzbget
DOWNLOAD_CLIENT_4_PASSWORD=tegbzn6789Configure multiple indexers using numbered environment variables (<N> = 1, 2, 3, etc.):
| Variable Pattern | Description | Example |
|---|---|---|
INDEXER_<N>_NAME |
Indexer display name | Prowlarr |
INDEXER_<N>_TYPE |
Indexer type (prowlarr, jackett, public) | prowlarr |
INDEXER_<N>_ENABLED |
Enable this indexer | true |
INDEXER_<N>_PRIORITY |
Indexer priority (higher = preferred) | 1 |
INDEXER_<N>_BASE_URL |
Indexer base URL | http://prowlarr:9696 |
INDEXER_<N>_API_KEY |
Indexer API key | - |
INDEXER_<N>_INDEXER_IDS |
Comma-separated indexer IDs | 1,2,3 |
INDEXER_<N>_CATEGORIES |
Comma-separated categories | movies,tv |
INDEXER_<N>_RATE_LIMIT |
API rate limit (requests/sec) | - |
Example for Prowlarr:
INDEXER_1_NAME=Prowlarr
INDEXER_1_TYPE=prowlarr
INDEXER_1_BASE_URL=http://prowlarr:9696
INDEXER_1_API_KEY=your-prowlarr-api-key-hereExample for Jackett:
INDEXER_2_NAME=Jackett
INDEXER_2_TYPE=jackett
INDEXER_2_BASE_URL=http://jackett:9117
INDEXER_2_API_KEY=your-jackett-api-key-here| Variable | Description | Default |
|---|---|---|
LOG_LEVEL |
Application log level (debug, info, warning, error) | info |
Configuration is loaded in this order (highest to lowest priority):
- Environment Variables - Override everything
- Database Settings - Configured via Admin UI
- YAML File - From
config/config.yml - Schema Defaults - Built-in defaults
With Docker (Recommended):
# Start everything
./dev up -d
# Run migrations
./dev mix ecto.migrate
# View at http://localhost:4000
# Check logs for the auto-generated admin password:
./dev logs | grep "DEFAULT ADMIN USER CREATED" -A 10See all commands with ./dev
Without Docker:
mix setup
mix phx.serverVisit localhost:4000
All pull requests and commits to the main branch automatically run:
- β Code compilation with warnings as errors
- β Code formatting checks
- β Static analysis with Credo
- β Full test suite
- β Docker build verification
Run these checks locally before committing:
mix precommitCreate compose.override.yml to add services like Transmission, Prowlarr, Jackett, or custom configurations:
cp compose.override.yml.example compose.override.yml
# Edit and uncomment services you need
./dev up -dCapture automated screenshots for documentation:
./take-screenshotsSee assets/SCREENSHOTS.md for configuration options.
- Phoenix 1.8 + LiveView
- Ecto + SQLite
- Oban (background jobs)
- Tailwind CSS + DaisyUI
- Req (HTTP client)
Built with Elixir & Phoenix