Skip to content

Latest commit

 

History

104 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flowarr

Flowarr

Self-hosted media library file transformation automation

FeaturesQuickstartArchitectureContributing

Tests Docker License

Flowarr automates media file transformations for private media servers running Jellyfin/Plex. It watches filesystem directories, runs configurable jobs (transcoding, subtitle extraction), and pauses all processing when Jellyfin streams are active to avoid GPU contention.

Built for the Steam Deck media server problem: transcode h.264 → HEVC for ~60% space savings, extract ASS/SSA subtitles to SRT sidecars for direct-play compatibility.

Features

  • GPU-Accelerated Transcoding: HDR → SDR tonemapped HEVC encoding via NVENC (with libx265 fallback)
  • Subtitle Extraction: Extract text-based subtitles (SRT, ASS, SSA, WebVTT) to sidecar files, strip internal subtitle tracks
  • Subtitle Conversion: Convert non-SRT subtitle files (VTT, ASS) to SRT
  • Jellyfin Webhook Integration: Auto-pause all processing when Jellyfin streams are active, resume when they stop
  • Library Management: Multiple media directories with per-library job configuration and scan intervals
  • Execution Tracking: Full lifecycle tracking from queued → processing → completed/failed
  • Queue-backed Jobs: database-backed job queues with per-job-type routing
  • Web UI: Dashboard, library management, execution monitoring via Inertia + React
  • Authentication: Registration, login, passkeys (WebAuthn), email verification, 2FA/TOTP

Tech Stack

Layer Technology
Backend PHP 8.5, Laravel 13, Fortify
Frontend React 19, Inertia v3, TypeScript, Tailwind v4, shadcn/ui
Database PostgreSQL 18
Queue Database driver
Cache Redis
Containers Docker (single image: nginx + PHP-FPM, Alpine-based)

Quickstart

Self-Hosting (Docker)

Pull the prebuilt image from GHCR, configure environment, and start with Docker Compose.

Prerequisites
  • Docker Engine 24+ with Compose plugin
  • An APP_KEY (generate with openssl rand -base64 32)
  • A PostgreSQL 18-compatible password
  • (Optional) A running Traefik reverse proxy for TLS / homelab routing

PostgreSQL 18 note: The volume mounts at /var/lib/postgresql (not /var/lib/postgresql/data). PG 18+ requires a mount at the parent directory so it can create a version-specific subdirectory, which is needed for clean pg_upgrade later. See docker-library/postgres#1259.

Setup

# 1. Pull the compose file and env template
curl -O https://raw.githubusercontent.com/fais649/flowarr/main/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/fais649/flowarr/main/.env.prod.example

# 2. Configure
cp .env.prod.example .env.prod
#   Edit .env.prod — at minimum set:
#     APP_KEY=$(openssl rand -base64 32)
#     DB_PASSWORD=<your-password>
#     DOMAIN=<your-domain>          # only needed with Traefik
#     APP_URL=https://<your-domain> # only needed with Traefik

# 3. Start
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d

.env.prod serves double duty: it feeds Compose variable substitution via --env-file, and it's bind-mounted into the container (./.env.prod:/var/www/html/.env.prod) as the app's own config.

Visit http://localhost:8080/register (direct access) or https://<your-domain>/register (with Traefik) to create the first user.

Build from source instead of pulling: :latest only exists after a release tag is published — pushes to main publish :edge. Until then, build locally:

git clone https://github.com/Fais649/flowarr && cd flowarr && docker build -t ghcr.io/fais649/flowarr:latest .
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d

Compose reference

The stack runs three services: PostgreSQL 18, Redis 7, and the Flowarr app container.

services:
  flowarr-postgres:
    image: postgres:18-alpine
    volumes:
      - postgres-data:/var/lib/postgresql   # PG 18+ needs mount at /var/lib/postgresql, not /data
    healthcheck:
      test: ["CMD", "pg_isready", "-q", "-d", "flowarr", "-U", "flowarr"]
      start_period: 60s   # grace for first-start initdb
      retries: 5

  flowarr-redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]

  flowarr:
    image: ghcr.io/fais649/flowarr:latest
    ports:
      - "8080:8080"   # remove for Traefik-only access
    environment:
      APP_KEY:        # required — generate with `openssl rand -base64 32`
      APP_URL:        # https://<your-domain>
      DB_PASSWORD:    # required — matches POSTGRES_PASSWORD above
      RUN_MIGRATIONS: "true"
    volumes:
      - /path/to/media:/media          # writable — Flowarr writes transcoded files & sidecars here
      - flowarr-storage:/var/www/html/storage

The production compose file is at docker-compose.prod.yml in the repo root, and the Laravel Sail development compose file is at compose.yaml. Environment variables are documented in .env.prod.example.

Container details

The Docker image (ghcr.io/fais649/flowarr:latest) is a single Alpine-based container where supervisord manages nginx, PHP-FPM, and the queue worker processes.

  • Port: 8080 (internal), FastCGI proxy to 127.0.0.1:9000
  • PHP: 8.5, extensions: pgsql, pdo_pgsql, bcmath, zip, intl, pcntl, redis
  • System tools: ffmpeg 8.1, mkvtoolnix 99, bash, curl, postgresql-client
  • Build: 3-stage (composer → assets → runtime), ~465 MB compressed

First boot

On first start, PostgreSQL initializes its data directory (can take 30-60s). The healthcheck has start_period: 60s to allow for this — Docker won't mark it unhealthy during init.

Once Postgres is healthy, the Flowarr entrypoint runs in order:

  1. Requires .env.prod to be mounted at /var/www/html/.env.prod — if it's absent, the container exits with an error message (no .env is auto-created)
  2. Copies .env.prod to .env
  3. Clears the Laravel configuration, route, event, and view caches
  4. Runs database migrations if RUN_MIGRATIONS=true (default)
  5. Rebuilds the configuration, route, event, and view caches
  6. Starts supervisord, which runs PHP-FPM, nginx, the orchestration queue worker (queue:work --queue=orchestration), the task scheduler (schedule:work, which drives interval library scans), and a one-shot queue:orchestrate startup job

The transcode, subtitle-extraction, and subtitle-conversion worker pools (10 processes each) are defined with autostart=false and are started on demand by the orchestrator. No manual artisan commands required.

APP_KEY persistence

APP_KEY is required — it is never auto-generated. If it's missing or empty, the app cannot encrypt sessions, cookies, or other encrypted data. Generate one and set it in your .env.prod:

openssl rand -base64 32
# → SaN+R05iCUuA64GtMh579p/MdA5giQLJw1q8wYQ3oB8=
# Set APP_KEY=base64:SaN+R05iCUuA64GtMh579p/MdA5giQLJw1q8wYQ3oB8= in .env.prod

Traefik reverse proxy

The compose file includes Traefik v2/v3 discovery labels. To use them, run the stack with the docker-compose.traefik.yml override, which attaches the flowarr container to the external traefik network:

  1. Make sure Traefik is running with an traefik Docker network: docker network create traefik (if absent)
  2. Set DOMAIN in .env.prod — this becomes the Host() rule
  3. Set APP_URL to https://<your-domain>
  4. Optionally remove the ports: mapping (Traefik routes internally)
  5. Start the stack:
    docker compose --env-file .env.prod -f docker-compose.prod.yml -f docker-compose.traefik.yml up -d

Labels are pre-configured for TLS on the websecure entrypoint. The base compose file keeps the flowarr container on the flowarr internal network; the override additionally attaches it to the external traefik network.

Directory layout (container)

/var/www/html/
├── public/          ← Laravel public dir (index.php, build/ assets)
├── storage/         ← persistent (mount as volume)
├── vendor/          ← Composer deps
├── app/ config/ etc ← Laravel app

Mount your media library at /media (writable — Flowarr writes transcoded files and subtitle sidecars here):

volumes:
  - /path/to/your/media:/media

Development (Laravel Sail)

git clone https://github.com/fais649/flowarr
cd flowarr
cp .env.example .env
./vendor/bin/sail up -d
./vendor/bin/sail artisan key:generate
./vendor/bin/sail composer setup
./vendor/bin/sail artisan migrate
./vendor/bin/sail artisan db:seed

The app will be available at http://localhost.

./vendor/bin/sail composer run dev    # Starts application at localhost:80
./vendor/bin/sail artisan test   # Run tests

Jellyfin Webhook Integration

  1. Install the Webhook plugin in Jellyfin
  2. Add a webhook pointed at http://your-flowarr-host/webhooks/jellyfin
  3. Select events: Playback start and Playback stop
  4. (Optional) Set JELLYFIN_WEBHOOK_TOKEN in .env to secure the endpoint

When a stream starts, all running ffmpeg/mkvmerge processes are paused (SIGSTOP). They resume (SIGCONT) when the stream ends.

Architecture

See ARCHITECTURE.md for the full entity schema, data flow, and implementation plan.

Contributing

See CONTRIBUTING.md for setup instructions, test commands, and PR workflow.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages