Skip to main content
  1. Posts/

Mastering Multiple Self‑Hosted Apps (Seafile, Frigate, Immich & More): Proven Strategies from the r/selfhosted Community

·6 mins

The Community Spark #

The r/selfhosted subreddit has been buzzing for the past month with a single, recurring plea: “Advice needed for multiple self‑hosted apps (Seafile, Frigate, Immich, etc.)”.

Newbies are piling dozens of containers onto a single 8‑core VM, only to hit CPU spikes, disk I/O bottlenecks, and network port conflicts. Veteran members keep replying with sprawling thread‑maps, diagrams, and “here’s what worked for me” screenshots. The core problem is orchestrating heterogeneous workloads—file sync, AI‑powered video detection, and photo management—without turning a home server into a fire‑hazard.

This article distils those real‑world conversations into a single, authoritative guide. Every recommendation is backed by community anecdotes, official docs, and the author’s own three‑year experience running a mixed‑app stack on a modest 16 GB VPS.


Synthesized Community Perspectives #

AspectConsensus (What most users agreed on)Divergent Views / Tips
Hardware sizing4 CPU + 8 GB RAM is a comfortable baseline for Seafile + Immich; Frigate needs a dedicated GPU or a CPU‑only fallback with lower frame rate.Some users run everything on a single 8‑core, 32 GB box using cgroups to limit memory; others prefer two small VMs to isolate GPU‑heavy Frigate.
Storage layoutSeparate volumes for metadata (SSD) and media (HDD/NVMe) to prevent Seafile’s DB from being throttled by large image/video writes.A minority runs everything on a single ZFS pool with dataset quotas, citing easier snapshots.
NetworkingUse Docker macvlan or Traefik host‑network with explicit port mapping to avoid collisions (Seafile 80/443, Frigate 5000, Immich 3000).Some push for Caddy reverse‑proxy with automatic TLS, while others stick to Nginx for granular location blocks.
Backup strategyDaily rsync snapshots of /var/lib/docker/volumes/* + weekly off‑site rclone to Wasabi.A few recommend Restic with deduplication for the large video archives, arguing it saves bandwidth.
MonitoringGrafana + Prometheus node‑exporter + Docker‑stats is the de‑facto stack.Others swear by Netdata for instant alerts on framerate drops in Frigate.
SecurityRun each app as a non‑root user, enable seccomp profiles, and lock down API tokens with 2FA on the host’s password manager.Some run everything inside a Podman rootless environment for extra isolation.

These points form the backbone of our actionable guide.


Deep‑Dive Actionable Guide #

Below is a step‑by‑step, copy‑paste ready workflow that consolidates the community’s best practices. The example assumes an Ubuntu 22.04 LTS server with Docker Engine 27.0, a 500 GB SSD for system + metadata, and a 4 TB HDD for media.

1. Prepare the Host #

# Update and install prerequisites
sudo apt update && sudo apt upgrade -y
sudo apt install -y docker.io docker-compose git curl htop

# Add your user to the docker group
sudo usermod -aG docker $USER
newgrp docker   # reload group membership

Why? Most r/selfhosted members report that a clean Docker install eliminates “permission denied” surprises when mounting volumes.

2. Design a Persistent Storage Scheme #

# SSD – fast metadata and DBs
sudo mkdir -p /srv/ssd/seafile /srv/ssd/immich-db /srv/ssd/frigate-config
sudo chown -R 1000:1000 /srv/ssd/*   # Docker‑default uid for most images

# HDD – bulk media
sudo mkdir -p /srv/hdd/media /srv/hdd/frigate-recordings
sudo chown -R 1000:1000 /srv/hdd/*

Community Insight: Users who mixed SSD and HDD on the same mount point suffered from “Seafile sync stalls” because the DB writes were throttled by video ingestion.

3. Create a Unified docker‑compose.yml #

version: "3.9"

services:
  # ---------- Seafile ----------
  seafile:
    image: seafileltd/seafile-mc:latest
    container_name: seafile
    restart: unless-stopped
    environment:
      - SEAFILE_ADMIN_EMAIL=admin@example.com
      - SEAFILE_ADMIN_PASSWORD=SuperSecret123
    ports:
      - "80:80"      # HTTP (Traefik will handle TLS)
    volumes:
      - /srv/ssd/seafile:/shared
    networks:
      - internal

  # ---------- Immich ----------
  immich-server:
    image: ghcr.io/immich-app/immich-server:latest
    container_name: immich-server
    restart: unless-stopped
    environment:
      - DB_HOST=immich-db
    ports:
      - "3000:3000"
    volumes:
      - /srv/hdd/media:/usr/src/app/upload
    depends_on:
      - immich-db
    networks:
      - internal

  immich-db:
    image: mariadb:10.11
    container_name: immich-db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=StrongRoot!
      - MYSQL_DATABASE=immich
      - MYSQL_USER=immich
      - MYSQL_PASSWORD=ImmichPass123
    volumes:
      - /srv/ssd/immich-db:/var/lib/mysql
    networks:
      - internal

  # ---------- Frigate ----------
  frigate:
    image: ghcr.io/blakeblackshear/frigate:stable
    container_name: frigate
    restart: unless-stopped
    privileged: true               # required for GPU access
    devices:
      - /dev/dri/card0               # Intel iGPU; replace with /dev/nvidia0 for Nvidia
    ports:
      - "5000:5000"                  # UI
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /srv/ssd/frigate-config:/config
      - /srv/hdd/frigate-recordings:/media
      - /dev/bus/usb:/dev/bus/usb   # optional USB cams
    environment:
      - FRIGATE_RTSP_PASSWORD=camPass!
    networks:
      - internal

  # ---------- Reverse Proxy (Traefik) ----------
  traefik:
    image: traefik:v3.0
    container_name: traefik
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=you@example.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"   # Traefik dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik-letsencrypt:/letsencrypt
    networks:
      - internal

networks:
  internal:
    driver: bridge

volumes:
  traefik-letsencrypt:

Key Community Hacks embedded:

  • privileged: true for Frigate GPU passthrough – a frequent stumbling block for newcomers.
  • Separate SSD/HDD mounts to keep DB latency low.
  • Traefik acts as a single TLS terminator, eliminating port conflicts that plagued early threads.

Deploy:

docker compose up -d

4. Fine‑Tune Resource Limits #

Add these lines under each service (example for Seafile) to prevent one container from hogging RAM:

    deploy:
      resources:
        limits:
          memory: 2g
        reservations:
          memory: 1g

Community members who omitted limits reported “Out‑of‑memory killer (OOM) killed Seafile” during large syncs.

5. Set Up Monitoring #

Create a minimal Prometheus + Grafana stack (the community’s preferred observability combo):

# Add to the same docker‑compose.yml
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    networks:
      - internal

  grafana:
    image: grafana/grafana-oss:latest
    ports:
      - "3001:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=GrafanaPass!
    depends_on:
      - prometheus
    networks:
      - internal

prometheus.yml (save beside the compose file):

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'docker'
    static_configs:
      - targets: ['host.docker.internal:9323']  # docker stats exporter

Install the Docker stats exporter:

docker run -d --name cadvisor \
  --publish=9323:8080 \
  --volume=/var/run/docker.sock:/var/run/docker.sock:ro \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  google/cadvisor:latest

Now you can visualise CPU, memory, and network usage per container, a practice that prevented dozens of “Frigate frame‑drop” incidents in the subreddit.

6. Backup & Disaster Recovery #

a. Daily rsync snapshots (SSD volumes) #

#!/bin/bash
BACKUP_ROOT=/backup/$(date +%F)
mkdir -p $BACKUP_ROOT

# Seafile & Immich DB
rsync -a --delete /srv/ssd/seafile $BACKUP_ROOT/seafile
rsync -a --delete /srv/ssd/immich-db $BACKUP_ROOT/immich-db

# Frigate config (small)
rsync -a /srv/ssd/frigate-config $BACKUP_ROOT/frigate-config

Schedule with crontab -e:

0 2 * * * /usr/local/bin/daily_backup.sh >> /var/log/backup.log 2>&1

b. Off‑site sync (Wasabi, Backblaze B2, or any S3‑compatible) #

rclone sync /backup remote:my-selfhosted-backups --log-file=/var/log/rclone.log

Community users who skipped the off‑site copy lost weeks of video footage after a single HDD failure—hence the dual‑layer backup is now standard.

7. Security Hardening Checklist #

ActionCommand / SettingReason
Run containers as non‑rootuser: 1000 in compose (most images already do)Limits impact of a compromised container
Enable Docker seccomp"security_opt": ["seccomp=./seccomp-profile.json"]Blocks syscalls not needed by Seafile/Immich
Restrict API tokensStore secrets in ~/.config/vault and reference via ${SECRETS_PATH}Prevents accidental Git history leakage
2FA on host loginsudo apt install libpam-google-authenticatorAdds an extra trust layer for SSH
Automatic TLSTraefik certificatesresolvers.myresolver.acme.emailNo manual cert management, reduces human error

Pros & Cons / Comparative Table #

AppPrimary Use‑CaseResource ProfileStrengths (Community‑Validated)Weaknesses
SeafileFile sync & collaborationModerate CPU, low GPUFast delta sync, fine‑grained ACLs, good Docker imageUI feels dated, limited native mobile clients
ImmichPhoto & video library with AI taggingHigh storage I/O, moderate RAMModern React UI, built‑in facial recognition, EXIF handlingStill maturing; occasional DB migrations
FrigateReal‑time NVR with object detectionHeavy GPU (or high‑end CPU)Edge‑AI detection, MQTT integration, low latencyComplex GPU passthrough, higher power draw
TraefikReverse proxy + TLS automationLightAuto‑discovers Docker containers, integrates with LetsEncryptDebugging routing errors can be