From 57139f0f54035541e0abc079a392033ea7323876 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sun, 19 Apr 2026 12:09:42 +0200 Subject: [PATCH] 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) --- stacks/appwrite.yml | 670 ++++++++++++++++++++++++++++++++++++++++++++ stacks/media.yml | 353 ++++++++++++----------- 2 files changed, 857 insertions(+), 166 deletions(-) create mode 100644 stacks/appwrite.yml diff --git a/stacks/appwrite.yml b/stacks/appwrite.yml new file mode 100644 index 0000000..cb3ae90 --- /dev/null +++ b/stacks/appwrite.yml @@ -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 diff --git a/stacks/media.yml b/stacks/media.yml index fdb7072..7ed6558 100644 --- a/stacks/media.yml +++ b/stacks/media.yml @@ -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