Jump to content

Aperture - AI-Powered Recommendations for Emby


Recommended Posts

TheGru
Posted

What It Does

Aperture is a self-hosted recommendation engine that creates personalized "For You" libraries in Emby. It analyzes your watch history and uses AI to suggest movies and TV series you'll actually want to watch—complete with explanations for why each title was picked for you.

Features

🎬 Personalized Recommendations

  • Per-user recommendation libraries - Each enabled user gets their own "Recommendations" library

  • Movies and TV Series - Separate recommendation pipelines for each media type

  • AI-generated explanations - Every recommendation includes a personalized "why you'll like this" explanation

  • Ranked posters - Custom poster overlays showing rank and match percentage

  • Exclude watched content - Option to only recommend unwatched titles

📊 Top Picks (Global)

  • Popularity-based libraries - "Top 10 Movies" and "Top 10 Series" based on what's trending across all users

  • Configurable popularity formula - Weighted combination of unique viewers, play count, and completion rate

  • Time window - Only count watches from the last X days

  • Auto-enabled for all users - Everyone sees the same Top Picks

👤 User Management

  • Import users from Emby - One-click import of all Emby users

  • Per-user controls - Enable/disable AI recommendations for movies and/or series independently

  • Taste profiles - AI-generated synopsis of each user's viewing preferences (e.g., "You gravitate toward cerebral sci-fi and morally complex antiheroes...")

  • Watch history sync - Delta sync support to only fetch new watches

⚙️ Algorithm Settings (Separate for Movies & Series)

  • Recommendation count - How many titles to recommend per user

  • Candidate pool size - How many candidates to consider before final selection

  • Watch history depth - How many recent watches to use for taste profile

  • Scoring weights:

  • Similarity weight - How much to favor titles similar to user's taste

  • Novelty weight - How much to favor titles different from what they've seen

  • Rating weight - How much to factor in critic/audience ratings

  • Popularity weight - How much to favor widely-watched titles

  • Diversity factor - Ensure variety in genres/styles

🤖 AI Configuration

  • Embedding model - Uses OpenAI text-embedding-3-large for semantic understanding

  • Text generation model - Selectable GPT model (gpt-4o-mini, gpt-4.1-mini, etc.) for explanations and taste synopses

  • Cost estimator - Real-time cost estimates based on your library size and settings

📚 Library Configuration

  • Source library selection - Choose which Emby movie/TV libraries to include

  • STRM output path - Configure where recommendation libraries are written

  • Library naming - Customize the names of generated libraries

⏰ Job Scheduling

All jobs support cron scheduling with these operations:

  • Sync Movies/Series - Pull metadata from Emby

  • Generate Embeddings - Create vector embeddings for new content

  • Sync Watch History - Update user watch data (with delta sync)

  • Generate Recommendations - Run the recommendation pipeline

  • Sync STRM Files - Write recommendation libraries to disk

  • Refresh Top Picks - Recalculate popularity rankings

📈 Monitoring

  • Job run history - Track when each job ran, duration, and success/failure

  • Real-time job progress - Live streaming logs while jobs run

  • Database statistics - View counts of movies, series, episodes, embeddings, etc.

How It Works

  1. Library Sync - Aperture syncs movie/series metadata from your Emby libraries

  2. Embedding Generation - Each title is converted into a vector embedding capturing its "essence"

  3. Watch History Sync - Tracks what each user has watched

  4. Taste Profile - Builds a vector representing each user's preferences

  5. Candidate Generation - Finds unwatched titles similar to user's taste

  6. Scoring & Selection - Ranks candidates and selects diverse top picks

  7. Explanation Generation - GPT writes personalized "why you'll like this" for each pick

  8. STRM Library Creation - Generates virtual library with STRM files, custom posters, and NFO metadata

Tech Stack

  • Backend: Node.js/TypeScript with Fastify

  • Frontend: React with Material UI

  • Database: PostgreSQL with pgvector for similarity search

  • AI: OpenAI for embeddings and text generation

  • Deployment: Docker

The Result

Users see personalized recommendation libraries right in their Emby home screen with suggestions that actually make sense—no more endless scrolling wondering what to watch next.

 

Screenshot 2026-01-03 at 4.45.12 PM.png

Screenshot 2026-01-03 at 4.57.02 PM.png

Screenshot 2026-01-03 at 4.56.48 PM.png

Screenshot 2026-01-03 at 4.56.39 PM.png

Screenshot 2026-01-03 at 4.56.31 PM.png

Screenshot 2026-01-03 at 4.56.08 PM.png

Screenshot 2026-01-03 at 4.55.54 PM.png

Screenshot 2026-01-03 at 4.55.25 PM.png

Screenshot 2026-01-03 at 4.59.39 PM.png

Screenshot 2026-01-03 at 4.59.52 PM.png

Screenshot 2026-01-03 at 5.00.02 PM.png

Screenshot 2026-01-03 at 5.00.22 PM.png

Screenshot 2026-01-03 at 5.03.23 PM.png

  • Like 6
Posted

Thanks for sharing.

horstepipe
Posted

Hey @TheGru

This isn't ready for testing yet or am I missing any download info in your post? I was hoping for a similar plugin since awhile and would be happy to help testing / fixing bugs.
BR

PuffyToesToo
Posted

Are you planning to release this? 

TheGru
Posted
16 minutes ago, PuffyToesToo said:

Are you planning to release this? 

I am planning on releasing it yes!

I have only been working on it for 2 days, so it is not quite ready.

what I would love is some conceptual feedback and any other possibly related idea/enhancement that I could include.

Be advised this is not a plugin in the traditional Emby sense. It is an external application which will run inside a Docker Container, that interfaces with Emby via the API to create libraries/collections/playlists and setup the correct library/user associations.

  • Like 2
  • Thanks 1
PuffyToesToo
Posted
41 minutes ago, TheGru said:

Be advised this is not a plugin in the traditional Emby sense. It is an external application which will run inside a Docker Container, that interfaces with Emby via the API to create libraries/collections/playlists and setup the correct library/user associations.

Am I to assume then, that it will not port over to usage through smart TVs, Roku etc? It looks like very interesting work to me, just that it would not be feasible if I cannot use it through TV. 

I think adding the option to ignore/replace/remove a suggestion and renew it would be a great addition in user experience. Additionally, hiding content including the 'Why we suggested this' are good ideas IMO. Many folks do not want to know why, they just want it to work. My .02

Great work. Thank you.

TheGru
Posted
3 minutes ago, PuffyToesToo said:

Am I to assume then, that it will not port over to usage through smart TVs, Roku etc? It looks like very interesting work to me, just that it would not be feasible if I cannot use it through TV. 

I think adding the option to ignore/replace/remove a suggestion and renew it would be a great addition in user experience. Additionally, hiding content including the 'Why we suggested this' are good ideas IMO. Many folks do not want to know why, they just want it to work. My .02

Great work. Thank you.

Aperture uses the Emby API to create libraries on the server, which are then associated with specific users. 

The end result is the libraries show up on any connected device that user is logged in to. 

  • Thanks 1
PuffyToesToo
Posted
10 minutes ago, TheGru said:

Aperture uses the Emby API to create libraries on the server, which are then associated with specific users. 

The end result is the libraries show up on any connected device that user is logged in to. 

That's great, thank you.

TheGru
Posted
1 minute ago, PuffyToesToo said:

That's great, thank you.

I am working on your request now to allow a admin/user to decide if they want to see the "AI WHY"

I found it helpful in testing, and assume it will reduce ambiguity. In my case my kids often just watch stuff in my profile, and I was wondering how these suggestions could possibly be accurate while building the algorithm.

  • Thanks 1
PuffyToesToo
Posted

To further my suggestion - the 'Why Aperture Chose This' is a great function for Admins and maybe the occasional nerdstat user. The option to hide from an end user by Admin or End User is valuable.

akacharos
Posted (edited)

This is awesome, thanks TheGru!
I’ll try to test it as soon as I get some free time.

A few feature ideas that could make Aperture even more powerful:

  • Add the option to generate collections with the same logic you currently use for playlists. I understand the value of personalized, generated content, but I really miss the social/discovery aspect of recommendations via collections (and I personally don’t love the playlist layout).
  • Add collaborative filtering with embeddings to improve recommendations by learning from viewing patterns across Emby users. This would greatly enhance discovery by suggesting items you wouldn’t match via metadata alone and enable suggestions like: “Users with similar watch histories to yours enjoyed [Movie/TV list].”
  • Add optional metadata enrichment via OMDb (omdbapi.com). I’ve used this before when experimenting with LLMs and the Emby API to get  values for fields like “language”, “country”, “awards”, and additional ratings.
  • Add optional similarity enrichment using TMDb’s “similar” endpoint. For example,https://api.themoviedb.org/3/movie/27205/similar?api_key=[API_KEY]&language=en-US&page=1 returns 20 similar movies with their TMDb IDs. That similarity signal could become another weight/feature in the recommendation algorithm, combined with what Aperture already does.
  • Wild one: add LLM! Imagine it something like Movie/TV Show recommender agent that can suggest lists, add to user's favorites or generate a collection/playlist out of it.  I did that in a hacky way in diffy two yeasr ago and was planning to start in far/new feature something from scratch in n8n. But I just saw Aperture and I have to reconsider! Maybe use Aperture API endpoints in n8n instead?

Even without these whacky ideas of mine, Aperture already looks fantastic! Great stuff!

Edited by akacharos
TheGru
Posted
35 minutes ago, akacharos said:

This is awesome, thanks TheGru!
I’ll try to test it as soon as I get some free time.

A few feature ideas that could make Aperture even more powerful:

  • Add the option to generate collections with the same logic you currently use for playlists. I understand the value of personalized, generated content, but I really miss the social/discovery aspect of recommendations via collections (and I personally don’t love the playlist layout).
  • Add collaborative filtering with embeddings to improve recommendations by learning from viewing patterns across Emby users. This would greatly enhance discovery by suggesting items you wouldn’t match via metadata alone and enable suggestions like: “Users with similar watch histories to yours enjoyed [Movie/TV list].”
  • Add optional metadata enrichment via OMDb (omdbapi.com). I’ve used this before when experimenting with LLMs and the Emby API to get  values for fields like “language”, “country”, “awards”, and additional ratings.
  • Add optional similarity enrichment using TMDb’s “similar” endpoint. For example,https://api.themoviedb.org/3/movie/27205/similar?api_key=[API_KEY]&language=en-US&page=1 returns 20 similar movies with their TMDb IDs. That similarity signal could become another weight/feature in the recommendation algorithm, combined with what Aperture already does.
  • Wild one: add LLM! Imagine it something like Movie/TV Show recommender agent that can suggest lists, add to user's favorites or generate a collection/playlist out of it.  I did that in a hacky way in diffy two yeasr ago and was planning to start in far/new feature something from scratch in n8n. But I just saw Aperture and I have to reconsider! Maybe use Aperture API endpoints in n8n instead?

Even without these whacky ideas of mine, Aperture already looks fantastic! Great stuff!

Wow, thank you for the thoughtful feedback! 🙏
First, a bit of context, Aperture is only 3 days old and was built completely off the cuff, so it's wild to people interested in it. 

What Aperture Does Today (v0.1.6) - Made significant updates today.

AI Recommendations

  • Personalized "AI Picks" libraries created per-user using OpenAI embeddings + pgvector similarity search

  • Configurable algorithm weights: similarity, novelty, rating, diversity

  • Works for both Movies and TV Series

  • AI-generated explanations in NFO files ("Why we picked this for you")

10-Heart Rating System

  • Rate content 1-10 hearts from any poster in the app

  • Fully compatible with Trakt.tv (bidirectional sync)

  • Ratings influence future recommendations (boost liked content, exclude/penalize disliked)

Top Picks (Global Trending)

  • Popularity-based recommendations across all users

  • Already supports Libraries, Collections, AND Playlists — you can enable any combination!

  • Rank badges (gold/silver/bronze) on posters

AI Taste Profiles

  • Natural language descriptions of each user's preferences

  • Match scores showing why each recommendation fits

  • Evidence trail ("Because you watched X, Y, Z...")

Watch Stats Dashboard

  • Genre breakdown, watch timeline, top actors/directors

  • Favorite decades, ratings distribution

Channels (Custom AI Collections)

  • User-created collections with custom criteria + AI generation

  • Sync as playlists to media server

Admin Features

  • Web-based setup (no env vars required)

  • Real-time job progress, scheduling, history

  • Per-user settings, cost estimator

Responding to Your Ideas

1. Collections instead of playlists
Great news — this already exists! Top Picks supports three output modes: Library, Collection (Emby Box Set), and Playlist. You can enable all three simultaneously. Collections are rank-ordered so they appear properly sorted. The AI personalized recommendations currently use Libraries, but adding Collection output as an option would be straightforward.

2. Collaborative filtering
This is definitely on my radar! Right now, recommendations are content-based (embedding similarity to what you've watched). Adding collaborative filtering ("users like you also enjoyed...") would be a powerful complement. The Top Picks feature already aggregates across all users — extending that with user-similarity clustering would be the next evolution.

3. OMDb metadata enrichment
Interesting idea. Currently, I pull metadata from Emby/Jellyfin (which typically sources from TMDB/TVDB). Adding OMDb could fill gaps like awards data and additional ratings. I'd want to make it optional since it requires another API key.

4. TMDB "similar" endpoint
This could be a nice secondary signal! Right now, the "similar" functionality is entirely embedding-based (semantic meaning), which often finds connections that metadata can't. TMDB's similar data could be weighted alongside embeddings for a hybrid approach.

5. LLM agent for conversational recommendations
You're thinking exactly where I want to go! The Channels feature is a baby step — you describe what you want in natural language and AI populates it. A full conversational agent ("find me something like Inception but with more humor") is definitely the vision. And yes, all Aperture functionality is exposed via REST API, so integrating with n8n or building custom workflows is totally possible today.

 

  • Thanks 1
TheGru
Posted

Ok it does AI Chat now against your taste profile and all known embeddings in the vector db. 

Screenshot 2026-01-06 at 9.47.46 PM.png

TheGru
Posted

some more AI Assistant updates. I need to merge this code in to main and build a new docker package still.

Screenshot 2026-01-06 at 9.53.47 PM.png

akacharos
Posted

wow, that was fast! amazing...

TheGru
Posted
13 minutes ago, akacharos said:

wow, that was fast! amazing...

Code with AI as your assistant and coding is fast. Anyone coding without leveraging AI is just wasting time at this point.

  • Facepalm 1
  • Agree 1
akacharos
Posted

So true. I am not really a coder myself, but with aider and now with aider-desk I can generate codebase better and way faster than most junior devs -which is scary tbh.

TheGru
Posted

I use Cursor, mostly with Anthropic Claude Opus 4.5. 

TheGru
Posted (edited)

Aperture Update: What's New v0.1.7 🎬

This update brings a major new feature plus several quality-of-life improvements.


🤖 Introducing Encore, Your AI Assistant

The headline feature of this update is Encore, a conversational AI assistant built right into Aperture. Think of it as having a knowledgeable friend who knows your entire library and can help you find exactly what you're in the mood for.

Getting Started

Click the sparkle button (✨) in the bottom-right corner of any page to open Encore. Just type what you're looking for in plain English:

  • "What should I watch tonight?"
  • "Find me something like Inception but darker"
  • "Show me 90s action movies"
  • "Movies with Tom Hanks"
  • "What have I watched recently?"

Encore responds with beautiful content cards showing posters, ratings, and one-click buttons to view details or play directly in Emby/Jellyfin.

Smart Discovery

Encore uses the same AI embeddings that power your personalized recommendations, so when you ask for "mind-bending sci-fi" or "cozy mysteries", it actually understands what you mean — not just keyword matching.

You can also ask about your watch history, ratings, library stats, actor/director filmographies, and more.

Conversation History

Your chats are saved automatically. Expand to fullscreen mode to see your conversation sidebar where you can switch between past conversations, start new chats, or rename/delete old ones.

❤️ Rate from Any Poster

You can now rate movies and series directly from their posters — no need to open the detail page. Just click the heart icon in the corner of any poster and select your rating (1-10 hearts).

🖼️ Library Image Management

Admins can now upload custom banner images for Aperture-created libraries (AI Recommendations and Top Picks). Images sync automatically to your media server.

🎨 Series Poster Overlays

Series recommendations now get the same rank badge overlays as movies — Gold for #1, Silver for #2, Bronze for #3.

📁 Smarter Symlinks

When using symlinks for virtual libraries, Aperture now automatically links artwork files, subtitle files (renamed to match the video), and season folders for series.

📺 Emby Series Sorting Fix

Series in your AI Picks library now appear in the correct rank order on your Emby home screen (previously they sorted randomly based on episode dates, yes this is hacky workaround).
 

I hope you enjoy these updates! Give Encore a try and let me know what you think. 🍿

Screenshot 2026-01-08 at 2.54.59 PM.png

Screenshot 2026-01-08 at 2.55.13 PM.png

Screenshot 2026-01-08 at 2.56.03 PM.png

Screenshot 2026-01-08 at 2.56.34 PM.png

Screenshot 2026-01-08 at 2.56.51 PM.png

Screenshot 2026-01-08 at 2.57.27 PM.png

Screenshot 2026-01-08 at 2.59.43 PM.png

Screenshot 2026-01-08 at 3.04.29 PM.png

Screenshot 2026-01-08 at 3.17.56 PM.png

Edited by TheGru
  • Like 1
akacharos
Posted

It's been a bumpy start, but getting there! So here are the issues I faced so far with docker:
 

Issue 1: Media Server Configuration Required for Startup (Documentation/Logic Conflict)
The documentation suggests that the Media Server (Emby/Jellyfin) configuration might be optional or configurable later. However, the container fails to initialize properly if the MEDIA_SERVER_BASE_URL and related variables are omitted or incorrect. The application appears to have a strict startup dependency on these values being present immediately.

Recommendation: Update documentation to list these variables as required for the initial Docker Compose setup, or handle the missing configuration gracefully during the first run.
 

Issue 2: EACCES: permission denied on /app/uploads (Windows Bind Mounts)
When attempting to bind mount the ./uploads directory for persistence on Windows, the Node.js process inside the container crashes with an EACCES error. This occurs because the default user (likely node or UID 1000) does not have write permissions to the Windows-mounted NTFS volume.

Logs:

Error: EACCES: permission denied, mkdir '/app/uploads'


Fix: force the container to run as root to bypass the Windows/Linux permission mismatch (Added user: "0:0" to the app service in docker-compose.yml.)

Issue 3: Application Defaults to localhost for Database (Ignoring Environment Variables)
The application persistently attempts to connect to the database on localhost (::1:5432 or 127.0.0.1:5432), even when DATABASE_URL, PGHOST, and POSTGRES_HOST are explicitly set in docker-compose.yml. This results in a loop of ECONNREFUSED errors because the database is running in a separate container, not inside the app container.

Logs:

AggregateError [ECONNREFUSED]: 
    at .../pg-pool/index.js:45:11
    ...
    errors: [
        Error: connect ECONNREFUSED ::1:5432,
        Error: connect ECONNREFUSED 127.0.0.1:5432
    ]


Fix:I had to explicitly force the container hostname resolution and flood the environment variables to ensure the driver picked up the correct host:

  • Set container_name: aperture-db for the database.
  • Explicitly set POSTGRES_HOST: aperture-db (and PGHOST) in the app service.
  • Use a custom bridge network to ensure DNS resolution works immediately on startup.
  • Like 1
TheGru
Posted
1 minute ago, akacharos said:

It's been a bumpy start, but getting there! So here are the issues I faced so far with docker:
 

Issue 1: Media Server Configuration Required for Startup (Documentation/Logic Conflict)
The documentation suggests that the Media Server (Emby/Jellyfin) configuration might be optional or configurable later. However, the container fails to initialize properly if the MEDIA_SERVER_BASE_URL and related variables are omitted or incorrect. The application appears to have a strict startup dependency on these values being present immediately.

Recommendation: Update documentation to list these variables as required for the initial Docker Compose setup, or handle the missing configuration gracefully during the first run.
 

Issue 2: EACCES: permission denied on /app/uploads (Windows Bind Mounts)
When attempting to bind mount the ./uploads directory for persistence on Windows, the Node.js process inside the container crashes with an EACCES error. This occurs because the default user (likely node or UID 1000) does not have write permissions to the Windows-mounted NTFS volume.

Logs:

Error: EACCES: permission denied, mkdir '/app/uploads'


Fix: force the container to run as root to bypass the Windows/Linux permission mismatch (Added user: "0:0" to the app service in docker-compose.yml.)

Issue 3: Application Defaults to localhost for Database (Ignoring Environment Variables)
The application persistently attempts to connect to the database on localhost (::1:5432 or 127.0.0.1:5432), even when DATABASE_URL, PGHOST, and POSTGRES_HOST are explicitly set in docker-compose.yml. This results in a loop of ECONNREFUSED errors because the database is running in a separate container, not inside the app container.

Logs:

AggregateError [ECONNREFUSED]: 
    at .../pg-pool/index.js:45:11
    ...
    errors: [
        Error: connect ECONNREFUSED ::1:5432,
        Error: connect ECONNREFUSED 127.0.0.1:5432
    ]


Fix:I had to explicitly force the container hostname resolution and flood the environment variables to ensure the driver picked up the correct host:

  • Set container_name: aperture-db for the database.
  • Explicitly set POSTGRES_HOST: aperture-db (and PGHOST) in the app service.
  • Use a custom bridge network to ensure DNS resolution works immediately on startup.

Thanks for the feedback. I will take a look next I get back to the project.

akacharos
Posted (edited)

Last important bit: I get 

{"level":40,"time":1768010709146,"pid":1,"hostname":"57b843a85f2f","name":"api","path":"/web/dist","msg":"Web dist folder not found, skipping static file serving"} 



Not sure if it's due to my workarounds to make it start, but I get no webui (browser http://localhost:3456/ -> {"message":"Route GET:/ not found","error":"Not Found","statusCode":404})
It appears the Docker build pipeline is not correctly copying the compiled frontend assets (from apps/web/)

For what it's worth , here's my docker-compose.yaml:
 

services:
  db:
    image: pgvector/pgvector:pg16
    container_name: aperture-db
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: <mypass>
      POSTGRES_DB: aperture
    volumes:
      - ./pgdata:/var/lib/postgresql/data
    networks:
      - aperture-net
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U app -d aperture']
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  app:
    image: ghcr.io/dgruhin-hrizn/aperture:latest
    container_name: aperture
    # Run as root to fix the "EACCES: permission denied" upload error
    user: "0:0"
    networks:
      - aperture-net
    environment:
      # --- DATABASE CONNECTION ---
      # Using 'aperture-db' as the host forces DNS to resolve to the container
      PGHOST: aperture-db
      PGPORT: 5432
      PGUSER: app
      PGPASSWORD: <mydbpass>
      PGDATABASE: aperture
      
      # Just in case the app uses different variable names
      POSTGRES_HOST: aperture-db
      POSTGRES_PORT: 5432
      POSTGRES_USER: app
      POSTGRES_PASSWORD: (mypass))
      POSTGRES_DB: aperture
      
      # Full Connection String
      DATABASE_URL: postgresql://app:mydbpassaperture-db:5432/aperture
      
      # --- APP CONFIG ---
      NODE_ENV: production
      PORT: 3456
      SESSION_SECRET: <32char>
      APP_BASE_URL: http://localhost:3456
      RUN_MIGRATIONS_ON_START: 'true'
      OPENAI_API_KEY: "sk-proj-" # 
      MEDIA_SERVER_STRM_ROOT: /strm
      AI_LIBRARY_PATH_PREFIX: /strm/aperture/
      MEDIA_SERVER_BASE_URL: "http://host.docker.internal:8096"

    ports:
      - '3456:3456'
    volumes:
      - ./strm:/strm
      - ./uploads:/app/uploads
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

networks:
  aperture-net:
    driver: bridge

 

Edited by akacharos
TheGru
Posted (edited)

Aperture Docker Deployment - Setup & Troubleshooting

Overview

This post documents the Docker deployment process for Aperture and the issues we've encountered and resolved.

Quick Start (Unraid / Production)

1. Download the production docker-compose file:

curl -O https://raw.githubusercontent.com/dgruhin-hrizn/aperture/main/docker-compose.prod.yml

2. Edit APP_BASE_URL to match your server:

APP_BASE_URL: http://192.168.1.8:3456  # Change to your server IP

3. Start the containers:

docker compose -f docker-compose.prod.yml up -d

4. Complete the Setup Wizard:

Navigate to http://your-server-ip:3456 and the setup wizard will guide you through:

  • Connecting to your Emby/Jellyfin server
  • Configuring your OpenAI API key (optional)

5. Log in with your media server credentials and you're ready to go!


Full docker-compose.prod.yml

# =============================================================================
# Aperture - Production Docker Compose
# =============================================================================
# Use this for deployment on Unraid, servers, or any production environment.
# This uses pre-built images from GitHub Container Registry.
#
# Quick start:
#   docker-compose -f docker-compose.prod.yml up -d
#
# Update to latest version:
#   docker-compose -f docker-compose.prod.yml pull
#   docker-compose -f docker-compose.prod.yml up -d
# =============================================================================

services:
  db:
    image: pgvector/pgvector:pg16
    container_name: aperture-db
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: aperture
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U app -d aperture']
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  app:
    image: ghcr.io/dgruhin-hrizn/aperture:latest
    container_name: aperture
    environment:
      NODE_ENV: production
      APP_BASE_URL: http://192.168.1.8:3456  # SET THIS TO YOUR SERVER IP AND PORT
      PORT: 3456
      DATABASE_URL: postgres://app:app@db:5432/aperture
      RUN_MIGRATIONS_ON_START: 'true'
      TZ: America/New_York
    ports:
      - '3456:3456'
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ./data/strm:/strm
      - ./uploads:/app/uploads
    restart: unless-stopped

volumes:
  pgdata:

Unraid Installation

Option 1: Compose Manager (Recommended)

  • Install Compose Manager plugin from Community Applications
  • Create a new stack named "Aperture"
  • Paste the docker-compose.prod.yml content above
  • Important: Change APP_BASE_URL to your Unraid IP (e.g., http://192.168.1.8:3456)
  • Click "Compose Up"
  • Access at http://your-unraid-ip:3456

Option 2: Manual Docker Commands

# Create directory
mkdir -p /mnt/user/appdata/aperture
cd /mnt/user/appdata/aperture

# Download compose file
curl -O https://raw.githubusercontent.com/dgruhin-hrizn/aperture/main/docker-compose.prod.yml

# Edit APP_BASE_URL
nano docker-compose.prod.yml

# Start
docker compose -f docker-compose.prod.yml up -d

Configuration

All settings are configured via the Setup Wizard and UI — no environment variables needed for most settings!

Variable Required Description
DATABASE_URL Yes PostgreSQL connection string (set in compose file)
APP_BASE_URL Yes Your server's URL (e.g., http://192.168.1.8:3456)
PORT No Server port (default: 3456)
TZ No Timezone (default: UTC)

Configured via UI (Settings pages):

  • Media Server (Emby/Jellyfin) connection
  • OpenAI API key
  • Library settings
  • All other preferences

Issues Fixed

✅ Issue 1: Setup Wizard Required

Problem: First-time users couldn't log in because media server wasn't configured.

Solution: Added a setup wizard that guides users through initial configuration before requiring login.


✅ Issue 2: Settings Not Persisting After Setup

Problem: After completing setup, login failed with "Media server not configured" because settings were read from environment variables instead of the database.

Solution: Made all configuration read from database first (with env fallback). Updated 40+ files to use async database queries.


✅ Issue 3: EACCES Permission Denied on /app/uploads

Problem: Container crashed with EACCES: permission denied when trying to create the uploads directory.

Solution: Dockerfile now pre-creates /app/uploads with correct ownership.


✅ Issue 4: Database Defaulting to localhost

Problem: Application connected to localhost:5432 instead of db:5432.

Solution: Added DOCKER_ENV=true to Dockerfile to disable development localhost override.


✅ Issue 5: Web UI 404 / "Web dist folder not found"

Problem: Static assets not served, resulting in 404 errors.

Solution: Fixed path calculation in static file plugin.


✅ Issue 6: OpenAI Client Crash on Startup

Problem: Container crashed if OpenAI API key wasn't set, even though it's optional.

Solution: Refactored OpenAI client to use lazy initialization.


Troubleshooting

Container won't start?

docker logs aperture

Database connection issues?

  • Ensure the db container is healthy before app starts
  • Check DATABASE_URL format: postgres://user:pass@host:5432/dbname

"Media server not configured" after setup?

  • Make sure you're running the latest image: docker compose -f docker-compose.prod.yml pull
  • Restart: docker compose -f docker-compose.prod.yml up -d

Updating to latest version:

docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d

Version History

  • v0.1.9 (Latest)

    Setup wizard for first-time configuration

    All UI-configurable settings now read from database

    Production docker-compose file (docker-compose.prod.yml)

    Fixed auth flow to use database-stored media server config

    Fixed OpenAI lazy initialization


Let me know if you encounter any other issues! 🎬

Edited by TheGru
  • Thanks 1
TheGru
Posted

I will probably work on a full getting started wizard now that I have ironed out the docker issues. 

I am thinking initial config of your server and open ai key then walk you through setting up which Emby libraries you want to include, and then run you through all the Jobs that need to be triggered in order to sync the libraries, watch history, run the embeddings, create recommendations and create the new AI Recommendation libraries per user in Emby.

I realize there is a lot you have to setup to get started, trying to do it all from the start vs as I was building!

  • Like 1

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...