feat(stack): add Appwrite BaaS stack for dev/testing

Self-contained stack with 25 containers: API server, console, realtime
WebSocket server, 10 workers, 3 schedulers, maintenance task, browser,
OpenRuntimes executor, plus internal MariaDB 10.11 and Redis 7.4.
Exposed on port 8093 via bundled Traefik. Watchtower disabled for
deliberate upgrades.

Also includes media.yml reformatting and Jellyfin GPU deploy block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 12:09:42 +02:00
co-authored by Claude Opus 4.6
parent 4520f627ba
commit 57139f0f54
2 changed files with 857 additions and 166 deletions
+670
View File
@@ -0,0 +1,670 @@
version: '3.8'
# Appwrite - Backend-as-a-Service Platform
# Purpose: Self-hosted BaaS for development and testing (auth, database, storage, functions)
# Port: 8093 (console + API via Traefik), 8094 (HTTPS)
# GPU: No
# External: LAN only (dev/testing)
# Storage: SSD (all data), internal MariaDB + Redis
# Containers: ~20 (API, console, realtime, workers, schedulers, executor, MariaDB, Redis)
x-logging: &x-logging
logging:
driver: 'json-file'
options:
max-file: '5'
max-size: '10m'
x-appwrite-env: &appwrite-env
_APP_ENV: production
_APP_WORKER_PER_CORE: 6
_APP_LOCALE: en
_APP_DOMAIN: 192.168.86.149
_APP_DOMAIN_TARGET_CNAME: 192.168.86.149
_APP_DOMAIN_TARGET_A: 192.168.86.149
_APP_DOMAIN_FUNCTIONS: functions.localhost
_APP_DOMAIN_SITES: sites.localhost
_APP_CONSOLE_WHITELIST_ROOT: enabled
_APP_OPENSSL_KEY_V1: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163
_APP_EXECUTOR_SECRET: gbnq4JwfnXBm4J7h7kUNEWt+OhwV3agu
_APP_EXECUTOR_HOST: http://openruntimes-executor/v1
_APP_DB_ADAPTER: mariadb
_APP_DB_HOST: appwrite-mariadb
_APP_DB_PORT: '3306'
_APP_DB_SCHEMA: appwrite
_APP_DB_USER: appwrite
_APP_DB_PASS: ML8QbYgD05+7ZDHuRfmSTgDBr5VusxLi87ifyO5RlJg=
_APP_DB_ROOT_PASS: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163
_APP_REDIS_HOST: appwrite-redis
_APP_REDIS_PORT: '6379'
_APP_REDIS_USER: ''
_APP_REDIS_PASS: ''
_APP_STORAGE_LIMIT: '30000000'
_APP_STORAGE_PREVIEW_LIMIT: '20000000'
_APP_STORAGE_ANTIVIRUS: disabled
_APP_STORAGE_DEVICE: local
_APP_USAGE_STATS: enabled
_APP_OPTIONS_ABUSE: enabled
_APP_OPTIONS_FORCE_HTTPS: disabled
_APP_OPTIONS_ROUTER_PROTECTION: disabled
_APP_OPTIONS_ROUTER_FORCE_HTTPS: disabled
_APP_SYSTEM_EMAIL_NAME: Appwrite
_APP_SYSTEM_EMAIL_ADDRESS: appwrite@schweitz.net
_APP_EMAIL_SECURITY: ''
_APP_SYSTEM_RESPONSE_FORMAT: ''
_APP_GRAPHQL_INTROSPECTION: disabled
_APP_GRAPHQL_MAX_BATCH_SIZE: '10'
_APP_GRAPHQL_MAX_COMPLEXITY: '250'
_APP_GRAPHQL_MAX_DEPTH: '3'
_APP_MAINTENANCE_INTERVAL: '86400'
_APP_MAINTENANCE_RETENTION_EXECUTION: '1209600'
_APP_MAINTENANCE_RETENTION_CACHE: '2592000'
_APP_MAINTENANCE_RETENTION_ABUSE: '86400'
_APP_MAINTENANCE_RETENTION_AUDIT: '1209600'
_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE: '1209600'
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY: '8640000'
_APP_MAINTENANCE_RETENTION_SCHEDULES: '86400'
_APP_FUNCTIONS_TIMEOUT: '900'
_APP_SITES_TIMEOUT: '900'
_APP_COMPUTE_BUILD_TIMEOUT: '900'
_APP_COMPUTE_CPUS: '0'
_APP_COMPUTE_MEMORY: '0'
_APP_COMPUTE_SIZE_LIMIT: '30000000'
_APP_FUNCTIONS_RUNTIMES: node-22.0,python-3.12,php-8.3,dart-3.5,bun-1.1
_APP_SITES_RUNTIMES: static-1.0
_APP_COMPUTE_RUNTIMES_NETWORK: runtimes
_APP_COMPUTE_INACTIVE_THRESHOLD: '60'
_APP_COMPUTE_MAINTENANCE_INTERVAL: '60'
_APP_LOGGING_CONFIG: ''
_APP_DNS: ''
_APP_COMPRESSION_MIN_SIZE_BYTES: '1024'
TZ: Europe/Amsterdam
services:
# ============================================
# Traefik - Internal Reverse Proxy
# Port: 8093 (HTTP), 8094 (HTTPS)
# Routes /console to console, /v1/realtime to realtime, everything else to API
# ============================================
appwrite-traefik:
image: traefik:3.6
container_name: appwrite-traefik
<<: *x-logging
restart: unless-stopped
command:
- --providers.file.directory=/storage/config
- --providers.file.watch=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --entrypoints.appwrite_web.address=:80
- --entrypoints.appwrite_websecure.address=:443
ports:
- "8093:80"
- "8094:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /home/jpmschweitzer/docker-data/appwrite/config:/storage/config:ro
- /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:ro
depends_on:
- appwrite
networks:
- appwrite
- docker-dataplane
# ============================================
# Appwrite API Server
# Internal only - served via Traefik
# ============================================
appwrite:
image: appwrite/appwrite:1.9.0
container_name: appwrite
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
labels:
- traefik.enable=true
- traefik.constraint-label-stack=appwrite
- traefik.docker.network=appwrite
- traefik.http.services.appwrite_api.loadbalancer.server.port=80
- traefik.http.routers.appwrite_api_http.entrypoints=appwrite_web
- traefik.http.routers.appwrite_api_http.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_http.service=appwrite_api
- traefik.http.routers.appwrite_api_https.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_api_https.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_https.service=appwrite_api
- traefik.http.routers.appwrite_api_https.tls=true
- "com.centurylinklabs.watchtower.enable=false"
volumes:
- /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw
- /home/jpmschweitzer/docker-data/appwrite/imports:/storage/imports:rw
- /home/jpmschweitzer/docker-data/appwrite/cache:/storage/cache:rw
- /home/jpmschweitzer/docker-data/appwrite/config:/storage/config:rw
- /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:rw
- /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw
- /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw
- /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Appwrite Console - Web UI
# Internal only - served via Traefik at /console
# ============================================
appwrite-console:
image: appwrite/console:7.8.26
container_name: appwrite-console
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
labels:
- traefik.enable=true
- traefik.constraint-label-stack=appwrite
- traefik.docker.network=appwrite
- traefik.http.services.appwrite_console.loadbalancer.server.port=80
- traefik.http.routers.appwrite_console_http.entrypoints=appwrite_web
- traefik.http.routers.appwrite_console_http.rule=PathPrefix(`/console`)
- traefik.http.routers.appwrite_console_http.service=appwrite_console
- traefik.http.routers.appwrite_console_https.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_console_https.rule=PathPrefix(`/console`)
- traefik.http.routers.appwrite_console_https.service=appwrite_console
- traefik.http.routers.appwrite_console_https.tls=true
- "com.centurylinklabs.watchtower.enable=false"
# ============================================
# Appwrite Realtime - WebSocket Server
# Internal only - served via Traefik at /v1/realtime
# ============================================
appwrite-realtime:
image: appwrite/appwrite:1.9.0
entrypoint: realtime
container_name: appwrite-realtime
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
labels:
- traefik.enable=true
- traefik.constraint-label-stack=appwrite
- traefik.docker.network=appwrite
- traefik.http.services.appwrite_realtime.loadbalancer.server.port=80
- traefik.http.routers.appwrite_realtime_ws.entrypoints=appwrite_web
- traefik.http.routers.appwrite_realtime_ws.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_ws.service=appwrite_realtime
- traefik.http.routers.appwrite_realtime_wss.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_realtime_wss.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_wss.service=appwrite_realtime
- traefik.http.routers.appwrite_realtime_wss.tls=true
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Workers - Background Processing
# ============================================
appwrite-worker-audits:
image: appwrite/appwrite:1.9.0
entrypoint: worker-audits
container_name: appwrite-worker-audits
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-webhooks:
image: appwrite/appwrite:1.9.0
entrypoint: worker-webhooks
container_name: appwrite-worker-webhooks
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-deletes:
image: appwrite/appwrite:1.9.0
entrypoint: worker-deletes
container_name: appwrite-worker-deletes
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw
- /home/jpmschweitzer/docker-data/appwrite/cache:/storage/cache:rw
- /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw
- /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw
- /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw
- /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-databases:
image: appwrite/appwrite:1.9.0
entrypoint: worker-databases
container_name: appwrite-worker-databases
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-builds:
image: appwrite/appwrite:1.9.0
entrypoint: worker-builds
container_name: appwrite-worker-builds
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw
- /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw
- /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw
- /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-certificates:
image: appwrite/appwrite:1.9.0
entrypoint: worker-certificates
container_name: appwrite-worker-certificates
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/config:/storage/config:rw
- /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-functions:
image: appwrite/appwrite:1.9.0
entrypoint: worker-functions
container_name: appwrite-worker-functions
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
openruntimes-executor:
condition: service_started
environment:
<<: *appwrite-env
appwrite-worker-mails:
image: appwrite/appwrite:1.9.0
entrypoint: worker-mails
container_name: appwrite-worker-mails
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-messaging:
image: appwrite/appwrite:1.9.0
entrypoint: worker-messaging
container_name: appwrite-worker-messaging
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-migrations:
image: appwrite/appwrite:1.9.0
entrypoint: worker-migrations
container_name: appwrite-worker-migrations
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/imports:/storage/imports:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Stats Workers
# ============================================
appwrite-task-stats-resources:
image: appwrite/appwrite:1.9.0
entrypoint: stats-resources
container_name: appwrite-task-stats-resources
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-stats-resources:
image: appwrite/appwrite:1.9.0
entrypoint: worker-stats-resources
container_name: appwrite-worker-stats-resources
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-stats-usage:
image: appwrite/appwrite:1.9.0
entrypoint: worker-stats-usage
container_name: appwrite-worker-stats-usage
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Task Schedulers
# ============================================
appwrite-task-scheduler-functions:
image: appwrite/appwrite:1.9.0
entrypoint: schedule-functions
container_name: appwrite-task-scheduler-functions
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-task-scheduler-executions:
image: appwrite/appwrite:1.9.0
entrypoint: schedule-executions
container_name: appwrite-task-scheduler-executions
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-task-scheduler-messages:
image: appwrite/appwrite:1.9.0
entrypoint: schedule-messages
container_name: appwrite-task-scheduler-messages
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Maintenance Task
# ============================================
appwrite-task-maintenance:
image: appwrite/appwrite:1.9.0
entrypoint: maintenance
container_name: appwrite-task-maintenance
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Browser Service (SSR rendering)
# ============================================
appwrite-browser:
image: appwrite/browser:0.3.2
container_name: appwrite-browser
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
# ============================================
# OpenRuntimes - Serverless Function Executor
# Requires Docker socket for spawning runtime containers
# ============================================
openruntimes-executor:
image: openruntimes/executor:0.7.22
container_name: openruntimes-executor
hostname: exc1
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
networks:
- appwrite
- runtimes
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw
- /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw
- /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw
- /tmp:/tmp:rw
environment:
- OPR_EXECUTOR_INACTIVE_TRESHOLD=${_APP_COMPUTE_INACTIVE_THRESHOLD:-60}
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=${_APP_COMPUTE_MAINTENANCE_INTERVAL:-60}
- OPR_EXECUTOR_NETWORK=runtimes
- OPR_EXECUTOR_ENV=production
- OPR_EXECUTOR_RUNTIMES=node-22.0,python-3.12,php-8.3,dart-3.5,bun-1.1,static-1.0
- OPR_EXECUTOR_SECRET=gbnq4JwfnXBm4J7h7kUNEWt+OhwV3agu
- OPR_EXECUTOR_RUNTIME_VERSIONS=v5
- OPR_EXECUTOR_LOGGING_CONFIG=
- OPR_EXECUTOR_STORAGE_DEVICE=local
# ============================================
# MariaDB - Internal Database (Appwrite core component)
# NOT exposed to host - Appwrite-only
# ============================================
appwrite-mariadb:
image: mariadb:10.11
container_name: appwrite-mariadb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/mariadb:/var/lib/mysql:rw
environment:
MYSQL_ROOT_PASSWORD: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163
MYSQL_DATABASE: appwrite
MYSQL_USER: appwrite
MYSQL_PASSWORD: ML8QbYgD05+7ZDHuRfmSTgDBr5VusxLi87ifyO5RlJg=
MARIADB_AUTO_UPGRADE: '1'
TZ: Europe/Amsterdam
command: 'mysqld --innodb-flush-method=fsync'
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 10s
retries: 10
start_period: 30s
# ============================================
# Redis - Internal Cache/Queue (Appwrite core component)
# NOT exposed to host - lives and dies with the stack
# ============================================
appwrite-redis:
image: redis:7.4-alpine
container_name: appwrite-redis
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--maxmemory-samples 5
volumes:
- /home/jpmschweitzer/docker-data/appwrite/redis:/data:rw
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
networks:
appwrite:
name: appwrite
runtimes:
name: runtimes
docker-dataplane:
external: true
name: docker-dataplane
# =============================================================================
# DEPLOYMENT INSTRUCTIONS
# =============================================================================
#
# Phase 1: Data Directories (DONE)
# Created at ~/docker-data/appwrite/{uploads,imports,cache,config,certificates,
# functions,sites,builds,mariadb,redis}
#
# Phase 2: Deploy Stack
# Deploy via Portainer as stack "appwrite"
# All environment variables are inline - no .env file needed
#
# Phase 3: Initial Setup
# 1. Access http://192.168.86.149:8093/console
# 2. Create admin account (first user becomes admin)
# 3. Create a project for development
#
# Phase 4: (Optional) NPM Proxy Configuration
# If external access is needed later:
# 1. Create proxy host: appwrite.schweitz.net -> 192.168.86.149:8093
# 2. Enable: Block Common Exploits, Websockets Support
# 3. SSL: Let's Encrypt, Force SSL, HTTP/2
#
# =============================================================================
# CREDENTIALS
# =============================================================================
# MariaDB:
# Host: appwrite-mariadb:3306 (internal only)
# Database: appwrite
# User: appwrite
# Password: ML8QbYgD05+7ZDHuRfmSTgDBr5VusxLi87ifyO5RlJg=
# Root Password: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163
#
# Redis:
# Host: appwrite-redis:6379 (internal only, no password)
#
# OpenSSL Key: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163
# Executor Secret: gbnq4JwfnXBm4J7h7kUNEWt+OhwV3agu
#
# =============================================================================
# ARCHITECTURE NOTES
# =============================================================================
# - MariaDB and Redis are internal to the stack (not exposed to host)
# - Traefik handles routing: /console -> console, /v1/realtime -> realtime, / -> API
# - OpenRuntimes executor needs Docker socket to spawn function containers
# - The 'runtimes' network is used for executor <-> function container communication
# - Watchtower is disabled (label) - Appwrite upgrades should be deliberate
# - All 20+ containers share the same appwrite/appwrite:1.9.0 image with different entrypoints
+187 -166
View File
@@ -1,4 +1,4 @@
version: '3.8'
version: "3.8"
# Media Stack - Unified Media Management & Streaming
# Services: Jellyfin, Sonarr, Radarr, Prowlarr, SABnzbd
@@ -8,177 +8,198 @@ version: '3.8'
# Database: PostgreSQL (postgres-shared) for Sonarr, Radarr, Prowlarr
services:
# ============================================================
# JELLYFIN - Media Server with GPU Transcoding
# ============================================================
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
user: 1000:1000
restart: unless-stopped
ports:
- "8096:8096" # HTTP web interface
- "8920:8920" # HTTPS web interface
- "7359:7359/udp" # Auto-discovery
- "1900:1900/udp" # DLNA
volumes:
# Config and cache on SSD (performance-critical)
- /home/jpmschweitzer/docker-data/jellyfin/config:/config
- /home/jpmschweitzer/docker-data/jellyfin/cache:/cache
# Media files on HDD (read-only for safety)
- /mnt/media/jellyfin/movies:/media/movies:ro
- /mnt/media/jellyfin/series:/media/series:ro
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=all
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8096/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- docker-dataplane
# ============================================================
# JELLYFIN - Media Server with GPU Transcoding
# ============================================================
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
user: 1000:1000
restart: unless-stopped
ports:
- "8096:8096" # HTTP web interface
- "8920:8920" # HTTPS web interface
- "7359:7359/udp" # Auto-discovery
- "1900:1900/udp" # DLNA
volumes:
# Config and cache on SSD (performance-critical)
- /home/jpmschweitzer/docker-data/jellyfin/config:/config
- /home/jpmschweitzer/docker-data/jellyfin/cache:/cache
# Media files on HDD (read-only for safety)
- /mnt/media/jellyfin/movies:/media/movies:ro
- /mnt/media/jellyfin/series:/media/series:ro
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=all
- TZ=Europe/Amsterdam
healthcheck:
test:
[
"CMD-SHELL",
"curl -fSs http://localhost:8096/health || exit 1",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
memory: 16G
reservations:
memory: 8G
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
networks:
- docker-dataplane
# ============================================================
# SONARR - TV Show Management & Automation
# ============================================================
sonarr:
image: linuxserver/sonarr:latest
container_name: sonarr
restart: unless-stopped
ports:
- "8989:8989"
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/sonarr:/config
# Media and downloads on HDD
- /mnt/media/jellyfin/series:/tv
- /mnt/media/downloads:/downloads
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
# PostgreSQL (postgres-shared)
- SONARR__POSTGRES__HOST=postgres-shared
- SONARR__POSTGRES__PORT=5432
- SONARR__POSTGRES__USER=media_user
- SONARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD}
- SONARR__POSTGRES__MAINDB=sonarr
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8989/ping || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
depends_on:
- sabnzbd
- prowlarr
networks:
- docker-dataplane
# ============================================================
# SONARR - TV Show Management & Automation
# ============================================================
sonarr:
image: linuxserver/sonarr:latest
container_name: sonarr
restart: unless-stopped
ports:
- "8989:8989"
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/sonarr:/config
# Media and downloads on HDD
- /mnt/media/jellyfin/series:/tv
- /mnt/media/downloads:/downloads
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
# PostgreSQL (postgres-shared)
- SONARR__POSTGRES__HOST=postgres-shared
- SONARR__POSTGRES__PORT=5432
- SONARR__POSTGRES__USER=media_user
- SONARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD}
- SONARR__POSTGRES__MAINDB=sonarr
healthcheck:
test:
["CMD-SHELL", "curl -fSs http://localhost:8989/ping || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
depends_on:
- sabnzbd
- prowlarr
networks:
- docker-dataplane
# ============================================================
# RADARR - Movie Management & Automation
# ============================================================
radarr:
image: linuxserver/radarr:latest
container_name: radarr
restart: unless-stopped
ports:
- "7878:7878"
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/radarr:/config
# Media and downloads on HDD
- /mnt/media/jellyfin/movies:/movies
- /mnt/media/downloads:/downloads
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
# PostgreSQL (postgres-shared)
- RADARR__POSTGRES__HOST=postgres-shared
- RADARR__POSTGRES__PORT=5432
- RADARR__POSTGRES__USER=media_user
- RADARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD}
- RADARR__POSTGRES__MAINDB=radarr
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:7878/ping || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
depends_on:
- sabnzbd
- prowlarr
networks:
- docker-dataplane
# ============================================================
# RADARR - Movie Management & Automation
# ============================================================
radarr:
image: linuxserver/radarr:latest
container_name: radarr
restart: unless-stopped
ports:
- "7878:7878"
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/radarr:/config
# Media and downloads on HDD
- /mnt/media/jellyfin/movies:/movies
- /mnt/media/downloads:/downloads
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
# PostgreSQL (postgres-shared)
- RADARR__POSTGRES__HOST=postgres-shared
- RADARR__POSTGRES__PORT=5432
- RADARR__POSTGRES__USER=media_user
- RADARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD}
- RADARR__POSTGRES__MAINDB=radarr
healthcheck:
test:
["CMD-SHELL", "curl -fSs http://localhost:7878/ping || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
depends_on:
- sabnzbd
- prowlarr
networks:
- docker-dataplane
# ============================================================
# PROWLARR - Indexer Management
# ============================================================
prowlarr:
image: linuxserver/prowlarr:latest
container_name: prowlarr
restart: unless-stopped
ports:
- "9696:9696"
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/prowlarr:/config
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
# PostgreSQL (postgres-shared)
- PROWLARR__POSTGRES__HOST=postgres-shared
- PROWLARR__POSTGRES__PORT=5432
- PROWLARR__POSTGRES__USER=media_user
- PROWLARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD}
- PROWLARR__POSTGRES__MAINDB=prowlarr
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:9696/ping || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- docker-dataplane
# ============================================================
# PROWLARR - Indexer Management
# ============================================================
prowlarr:
image: linuxserver/prowlarr:latest
container_name: prowlarr
restart: unless-stopped
ports:
- "9696:9696"
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/prowlarr:/config
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
# PostgreSQL (postgres-shared)
- PROWLARR__POSTGRES__HOST=postgres-shared
- PROWLARR__POSTGRES__PORT=5432
- PROWLARR__POSTGRES__USER=media_user
- PROWLARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD}
- PROWLARR__POSTGRES__MAINDB=prowlarr
healthcheck:
test:
["CMD-SHELL", "curl -fSs http://localhost:9696/ping || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- docker-dataplane
# ============================================================
# SABNZBD - Usenet Download Client
# ============================================================
sabnzbd:
image: linuxserver/sabnzbd:latest
container_name: sabnzbd
restart: unless-stopped
ports:
- "8880:8080" # Web UI (remapped to avoid Portainer conflict)
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/sabnzbd:/config
# Downloads on HDD
- /mnt/media/downloads:/downloads
- /mnt/media/downloads/incomplete:/incomplete-downloads
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- HOST_WHITELIST=sabnzbd,localhost,192.168.86.149,tower-of-joy
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8080/api?mode=version || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- docker-dataplane
# ============================================================
# SABNZBD - Usenet Download Client
# ============================================================
sabnzbd:
image: linuxserver/sabnzbd:latest
container_name: sabnzbd
restart: unless-stopped
ports:
- "8880:8080" # Web UI (remapped to avoid Portainer conflict)
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/sabnzbd:/config
# Downloads on HDD
- /mnt/media/downloads:/downloads
- /mnt/media/downloads/incomplete:/incomplete-downloads
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- HOST_WHITELIST=sabnzbd,localhost,192.168.86.149,tower-of-joy
healthcheck:
test:
[
"CMD-SHELL",
"curl -fSs http://localhost:8080/api?mode=version || exit 1",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
docker-dataplane:
external: true
name: docker-dataplane
# ============================================================
# SETUP GUIDE