1.6 Shapps

A list of installation instructions, tips, gotchas and tricks for all my self hosted applications (Shapps) that I run on my home lab.

Source_1: https://techhut.tv/must-have-home-server-services-2025#hoarder


Introduction

1.6.1 Docker VM Preperation

1.6.1 Docker VM Preperation

1.6.1.1 Docker VM Installation

The first VM we will create is the Docker VM. This VM will serve for all our docker compose files and applications that are not mission critical and are light weight applications. What is and what is not mission critical is of course for up to debate ;) Personally I have three dedicated VM's

  1. One for Ente Photos
  2. One for my Servarr Stack
  3. My Docker VM for all my lightweight applications

In this section we will setup a Docker VM in proxmox i.e most likely your first VM. Luckily this will be extremely ease due to Proxmox Helper scripts. Proxmox Helper scripts walks you extremely easy through the whole installation process of any LXC container and also certain VM's. In this case we will make use of it as well to create our Docker VM. Super easy, no configuration necessary from your end. Just hit the following link in your PVE Shell and you can start 

bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/docker-vm.sh)"

1. Choose Install Docker Vm

2. Choose Advanced Setup 

3. Select the newest form of Debian as of writing that is Debian 13

4. Enable Cloud Init:  Yes

5. Provision SSH keys for Cloud-Init VM: -> Choose "manual Paste a single public key  "

6. Copy paste in your SSH key from the machine you are planning to access the Docker VM from.

7. Set a Docker VM ID: Choose what fits for you. Most often I start with 1001

8. Choose type: q35

9. Choose the amount of Storage: I would recommend to start with 16G and then you can always add more over time. It is easy to add but hard to remove storage from your VM. Hence, start low and add more if necessary.

10.Disk Cache: None

11. Choose hostname: hostname

12. Choose CPU model: host

13. Allocate CPU cores: Start with 4 and you can always add more if necessary

14. RAM: start with 4096 

15. Interface: Choose the interface on which the VLAN sits that you want to use for your docker VM.

16. Set a MAC address: Leave as is

17. Set VLAN: The VLAN you want to host the docker VM on.

18. Finally choose the storage pool: Recommended on your VM mirrored SSD ZFS pool if you have that configured

1.6.1 Docker VM Preperation

1.6.1.2 Dockge Introduction

As we will see we can either be in the terminal all the time editing docker compose files for our applications or we can attach a GUI to it. 

  1. Website
  2. Github Page

image.png

So yeahh that is basically what Dockge is including many other features such as

So in this Book we will also make sure to use Dockge for all our installations. It is for sure not necessary but it is so easy and comfortable. In the next page we will do the installation. 

1.6.1 Docker VM Preperation

1.6.1.3 Dockge Installation

Assuming you already have installed your Docker VM it is time to install Dockge on top of it to handle all your docker compose files ;)

1. Go to the official dockge page and basically copy the docker compose file that looks like this

services:
  dockge:
    image: louislam/dockge:1
    restart: unless-stopped
    ports:
      - 5001:5001
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/app/data
      # Stacks Directory
      # ⚠️ READ IT CAREFULLY. If you did it wrong, your data could end up writing into a WRONG PATH.
      # ⚠️ 1. FULL path only. No relative path (MUST)
      # ⚠️ 2. Left Stacks Path === Right Stacks Path (MUST)
      - /opt/stacks:/opt/stacks
    environment:
      # Tell Dockge where to find the stacks
      - DOCKGE_STACKS_DIR=/opt/stacks
      # This is great for entering the Docker VM console from the WebGUI
      - DOCKGE_ENABLE_CONSOLE=true

2. Then open your terminal with your access machine and SSH into it. You added the SSH key of your machine in the Cloud Init section so you should be entering without any interruptions. Simply use

ssh username@IP-Address-Docker-VM

3. Once inside we will create a new dockge folder with

mkdir docgke

4. Then we will enter the folder with

cd dockge

5. Inside this folder we will create the docker compose file by copying and pasting in the docker compose content from above

nano compose.yml

6. Then save it with 

CTRL X and hit Y

7. Then we will start the dockge container with

docker compose up -d

8. If everything is green and alright we should be able to access the WebGui at 

IP-Address-DockerVM:5001

9. Then create an account with a username and password. Now, normally I would say you would save the credentials in your Bitwarden account but we have not yet setup that container. However, choose a non typical username and a password of 20 characters and copy paste it somewhere safe s.t you can find it. After we have created a vaultwarden container you will be able to copy paste it inside of it with all your other future credentials.

 

1.6.1.2 Dockhand

screenshot1.webp

Note! At this moment you can manage remote machines via one central Dockhand instance. However, the compose files live on the Dockhand instance instead of the remote machine. This means if the central Dockhand instance would go down you would also loose the compose files on your remote machine. This ain't great and hence I prefer to have a Dockhand instance on each machine until this problem is solved. 

Our Stack will be using docker compose files for all its applications in this docker VM. Now, without any GUI tools this would mean that we need to edit, stop, start, and configure all applications through the terminal. Now, this is great for learning purposes and gets you to the real docker commands. However, after understanding that you can make your life easier by using a GUI that does all the docker commands in the background. Now, there are many solutions for this such as Portainer, Dockge, and others. However, the most recent kid on the block is Dockhand. The amazing thing about this solutions is that it allows you to monitor your docker containers, send you alerts if something is up and send you alerts when new updates for containers are available. That last one is huge!!! It is so important to keep your containers up to date and have someone looking out for your when a new update is available is such a game changer. Instead of manually checking every week for new updates (which I always forget) it is just great that you get an email in your inbox with updates if they are available. Next to this a summary of Dockhead capabilities is.

Installation

The installation of Dockhand is fairly straightforward, the only thing you should think about is where you would like to save most of your docker compose files. This could be for instance just in /home/user/dockhand/ and you could also give it access to multiple locations inside of your VM. Personally I save everything underneath /opt/stacks simply because I am used to that due to Dockge that I was mainly using before Dockhand. If that's figured out you can find the docker compose on their website  here

1. Create the Dockhand directory with

mkdir -p /opt/stacks/dockhand

2. Then go inside the directory and create the following docker compose file with nano compose.yaml and copy paste the following

compose.yaml
services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: dockhand
      POSTGRES_PASSWORD: changeme
      POSTGRES_DB: dockhand
    volumes:
      - ./postgres_data:/var/lib/postgresql/data

  dockhand:
    image: fnsys/dockhand:latest
    ports:
      - 3000:3000
    environment:
      DATABASE_URL: postgres://dockhand:changeme@postgres:5432/dockhand
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./dockhand_data:/app/data
      - /opt/stacks:/opt/stacks # Directory in which you plan to host docker compose files 
    depends_on:
      - postgres
    restart: unless-stopped

 

3. Sweet then what is left is 

docker compose up -d

4. You can then reach your Dockhand instance on http://IP-Docker-VM:3000 or the port you specified for your instance. 

Dockhand Configuration

1. The first thing to do when everything is up and running is creating an account for obvious security purposes

2. Simply go to Settings > Authentication > Users > + Add User

3. Create a user and make sure that you check that the Authentication toggle is turned ON


Remote Machines

As stated in the warning above, today it is still recommended to 

Not all your docker containers will necessarily run on  the same virtual machine. However, that would also mean that you need to setup Dockhand on all these VM's. Luckily this is not necessary and you can have one glass connecting to all your docker containers on multiple VM's. 

https://github.com/Finsys/hawser

1.5.2 Docusaurus

1.5.3 InvoiceShelf

1.5.3 InvoiceShelf

1.5.2.1 Installation

  1. Github Invoiceshelf
  2. Invoice Shelf PVE community script

We will install Invoiceshelf as a docker compose file but it can also be installed as proxmox LXC container. At the time of writing invoiceshelf is still in heavy development and the full functionality is not added yet. However, I try to keep it as up to date as possible ;)

1. Create a folder called invoiceshelf with

mkdir invoiceshelf

2. Then the first thing we need to do is get the docker compose files by using the following commands according to the documentation page

git clone https://github.com/InvoiceShelf/docker

2. This will download a collection of docker compose files and some other files. Now you can choose 3 different databases and I chose for postgresql for no particular reason

3. In my case I like to deploy my docker compose files with Dockge and hence I will copy the contents of the docker-compose.pgsql.yml file and paste it into my dockge dashboard for a new container. However, you can also grab the file rename it to compose.yml and then save it in your preferred location. 

4. Next up is the docker compose file. What is important to change here are 4 things out of which three are already obvious the mysql database, user and password fields. This is clearly added as a note in the compose file

5. Next to that you need to have a public domain name s.t customers can actually access your invoice platform and pay their bills. This domain name needs to be added 3 times at the following places

  1. APP_URL=https://invoice.example.com
  2. SESSION_DOMAIN=invoice.example.com
  3. SANCTUM_STATEFUL_DOMAINS=invoice.example.com

6. Finally we will add mail to it as well with the following environment variables

      - MAIL_DRIVER=SMTP
      - MAIL_HOST=smtp.example.com
      - MAIL_PORT=587
      - MAIL_USERNAME=email@email.org
      - MAIL_PASSWORD=emailpassword
      - MAIL_ENCRYPTION=starttls

7. Finally, you can decide if you keep docker volumes or mounts. I did not get it to work with mounts and hence I stayed with volumes. Since, the data quantity is pretty low it did not really matter anyways since we are not attaching an nfs share or something to it. 

8. Then it is ready to sping up the docker compose file

9. The installation wizard is pretty simple however you need to make sure you get one thing right at the SITE URL & DATABASE section. When it asks for the DATABASE HOST. In this case you need to fill in the name you gave the database container. If you have not changed anything this will be invoiceshelf-pgsql.

10. Finally, also make sure you provide the right details for your email address. This will be important since from that email emails will be send to customers. 

#-------------------------------------------
# InvoiceShelf PostgresSQL docker-compose variant
# Repo : https://github.com/InvoiceShelf/docker
#-------------------------------------------

services:
  database:
    container_name: invoiceshelf-pgsql
    image: postgres:15
    environment:
      - POSTGRES_PASSWORD=sdfij2309rrjlkkfj0ejfsjdflkjsdfojwefkjHD # ⚠️ Change DB password before proceeding
      - POSTGRES_USER=invoiceshelf # ⚠️ Change DB username before proceeding
      - POSTGRES_DB=invoiceshelf # 🚨 Change DB name before proceeding
    volumes:
      - invoiceshelf_postgres:/var/lib/postgresql/data
    networks:
      - invoiceshelf
    restart: unless-stopped
  webapp:
    image: invoiceshelf/invoiceshelf:nightly
    container_name: invoiceshelf-variant-pgsql
    environment:
      - APP_NAME=InvoiceShelf
      - APP_ENV=production
      - APP_DEBUG=false
      # APP_URL should have:
      # 1. Protocol http or https if you are accessing it via reverse proxy with
      #    SSL, then it will be https, if via local IP, then http.
      # 2. IP address of the server or the domain name
      # 3. (optional) If you are using a custom port such as :8080 or another.
      # REF: https://github.com/InvoiceShelf/InvoiceShelf/issues/213#issuecomment-3026790543

      - APP_URL=https://invoice.rfeyn.org
      # SESSION_DOMAIN should have:
      # 1. IP address of the server or the domain name
      # 2. (optional) If you are using a custom port such as :8080 or another.
      # REF: https://github.com/InvoiceShelf/InvoiceShelf/issues/213#issuecomment-3026790543
      - SESSION_DOMAIN=invoice.rfeyn.org
      # SANCTUM_STATEFUL_DOMAINS should have:
      # 1. IP address of the server or the domain name
      #    1.1 (optional) If you are using a custom port such as :8080 or another.
      # 2. (optional) If you are planning to access the application from different
      #    domains, you must add a comma between each domain
      # REF: https://github.com/InvoiceShelf/InvoiceShelf/issues/213#issuecomment-3026790543
      - SANCTUM_STATEFUL_DOMAINS=invoice.rfeyn.org
      - DB_CONNECTION=pgsql
      - DB_HOST=database
      - DB_PORT=5432
      - DB_DATABASE=invoiceshelf # ⚠️ Change DB name before proceeding
      - DB_USERNAME=invoiceshelf # ⚠️ Change DB username before proceeding
      - DB_PASSWORD=sdfij2309rrjlkkfj0ejfsjdflkjsdfojwefkjHD # ⚠️ Change DB password before proceeding
      - DB_PASSWORD_FILE=
      - CACHE_STORE=file
      - SESSION_DRIVER=file
      - SESSION_LIFETIME=240
      - AUTORUN_ENABLED=true
      - AUTORUN_LARAVEL_MIGRATION=false
      - AUTORUN_LARAVEL_OPTIMIZE=false
      - PHP_OPCACHE_ENABLE=1
      - MAIL_DRIVER=SMTP
      - MAIL_HOST=smtp.porkbun.com
      - MAIL_PORT=587
      - MAIL_USERNAME=dp@p2pnode.org
      - MAIL_PASSWORD=PuV9#@pUca49C70e2V46&keg
      - MAIL_ENCRYPTION=starttls
    volumes:
      - invoiceshelf_storage:/var/www/html/storage/
      - invoiceshelf_modules:/var/www/html/Modules/
    ports:
      - 8090:8080 # 8090 is the public port.
    networks:
      - invoiceshelf
    restart: unless-stopped
    depends_on:
      - database
networks:
  invoiceshelf: null
volumes:
  invoiceshelf_postgres: null
  invoiceshelf_storage: null
  invoiceshelf_modules: null

drawing-1-1779878580.png

Edit this Page >>

1.5.3 InvoiceShelf

1.5.2.3 Email Templates

Nederlands


 
Factuur

Beste {BILLING_ADDRESS_NAME},

Met vriendelijke groet,

DPlancer
Dennis Paassen
KVK: {CUSTOM_INVOICE_KVK}

Betaling

🚀💪Wohoooo!! Bedankt voor je betaling!!

U kunt uw betalings bevestiging vinden door op onderstaande knop te drukken

Met vriendelijke groet,

Dennis Paassen
🐶DPlancer

English

Invoice

Dear {BILLING_ADDRESS_NAME}

Kind regards,

DPlancer
Dennis Paassen
KVK: {CUSTOM_INVOICE_KVK}

Payment 

🚀💪Wohoooo!! Thanks for your payment

You can find your payment confirmation by clicking on the lick below

Kind regards,

Dennis Paassen
🐶DPlancer

1.5.3 InvoiceShelf

01. Installation

services:
  app:
    build:
      context: .
    image: invoiceninja/invoiceninja-debian:latest
    restart: unless-stopped
    env_file:
      - ./.env
    volumes:
      - ./app_public:/var/www/html/public
      - ./app_storage:/var/www/html/storage
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - 8333:80
      - 6443:443
    volumes:
      - ./nginx:/etc/nginx/conf.d:ro
      - ./app_public:/var/www/html/public:ro
      - ./app_storage:/var/www/html/storage:ro
    depends_on:
      app:
        condition: service_healthy
  mysql:
    image: mysql:8
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    volumes:
      - ./mysql_data:/var/lib/mysql
    healthcheck:
      test:
        - CMD
        - mysqladmin
        - ping
        - -h
        - localhost
        - -u${MYSQL_USER}
        - -p${MYSQL_PASSWORD}
  redis:
    image: redis:alpine
    restart: unless-stopped
    volumes:
      - ./redis_data:/data
    healthcheck:
      test:
        - CMD
        - redis-cli
        - ping
networks: {}
# IN application vars
APP_URL=https://invoice.rfeyn.org
APP_KEY=base64:oUr5FRUWNrM42GeyksHF33ntNtlh29KW5vg2J2brnJ4=
APP_ENV=production
APP_DEBUG=true
REQUIRE_HTTPS=false
PHANTOMJS_PDF_GENERATION=true
PDF_GENERATOR=snappdf
TRUSTED_PROXIES='*'


CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

FILESYSTEM_DISK=debian_docker

# DB connection
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=ninjadplancer
DB_USERNAME=ninja
DB_PASSWORD=sf9823fsjlfkjwioefjlskfh0923jfklsdjf
DB_ROOT_PASSWORD=sdpoifj293fj09030rjnsnedfo3498t
DB_CONNECTION=mysql

# Create initial user
# Default to these values if empty
IN_USER_EMAIL=dp@p2pnode.org
IN_PASSWORD=nvQi5W14blOnpv
# IN_USER_EMAIL=
# IN_PASSWORD=

# Mail options
MAIL_MAILER=log
MAIL_HOST=smtp.porkbun.com
MAIL_PORT=587
MAIL_USERNAME=dp@p2pnode.org
MAIL_PASSWORD=PuV9#@pUca49C70e2V46&keg
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS='dp@p2pnode.org'
MAIL_FROM_NAME="DPLancer Invoices"

# MySQL
MYSQL_ROOT_PASSWORD=sdpoifj293fj09030rjnsnedfo3498t
MYSQL_USER=ninja
MYSQL_PASSWORD=sf9823fsjlfkjwioefjlskfh0923jfklsdjf
MYSQL_DATABASE=ninjadplancer

# GoCardless/Nordigen API key for banking integration
NORDIGEN_SECRET_ID=
NORDIGEN_SECRET_KEY=

IS_DOCKER=true
SCOUT_DRIVER=null
#SNAPPDF_CHROMIUM_PATH=/usr/bin/google-chrome-stable

1.5.4 Nextcloud

1.5.4 Nextcloud

1. Installation

Now, you can install nextcloud on the same docker VM that you install all other applications on as well. However, I personally have a dedicated VM for nextcloud since it is quite a heavy piece of software and if something goes wrong with nextcloud that affects the VM then it will not affect the other applications. Hence, what I first do is create again another Docker VM with the Docker VM Proxmox Helper Script. If you choose to host it on the same VM with Dockhand then you can also do this of course 

ludicrous exemption constant humility trapper hardening deviancy ferocity

passphrase

ludicrous exemption constant humility trapper hardening deviancy ferocity

10.69.1.107:11000

The permissions should be 

Permissions for NFS share

Right now I have 1000:1000 for and read and write permissions for everyone. Hence, that 30:00 works. But we need to figure this out for later to set the right permissions in TrueNas. 

2026-07-08T22:31:45.606690513Z The www-data user does not appear to have access rights to the data directory.
2026-07-08T22:31:45.606704904Z It is possible that the files are on a filesystem that does not support standard Linux permissions,
2026-07-08T22:31:45.606720217Z or the permissions simply need to be adjusted. Please change the permissions as described below.
2026-07-08T22:31:45.606723395Z Current permissions are:
2026-07-08T22:31:45.609071140Z 1000:1000 770
2026-07-08T22:31:45.609597690Z (userID:groupID permissions)
2026-07-08T22:31:45.609623100Z They should be:
2026-07-08T22:31:45.609626113Z 33:0 750
2026-07-08T22:31:45.609628621Z (userID:groupID permissions)
2026-07-08T22:31:45.609630879Z Also, ensure that all parent directories on the host of your chosen data directory are publicly readable.
1.5.4 Nextcloud

4. Restore From Internal Nextcloud Backup

Now, this is not necessary if you have a VM backup of your Nextcloud instance including its storage. However, if this is not the case, or you crashed your VM for whatever reason and have no backup then your can create a new nextcloud instance and import the files from a backup you must have created previously 

 

1.5.5 Seafile

File Server which is dedicated for serving files! It works great for fast file synchronization due to block storage instead of file storage on Nextcloud. It is not bloated with things you don't need as it is on Nextcloud

1.5.5 Seafile

1. Installation Seafile 13

We will install Seafile again in our docker vm by using Dockhand. For that we need to make sure that we have a place for all the data, which in our case will be an NFS share hosted on our TrueNas VM. Then the next step is of course to look up the docker compose file and deploy it to our needs. The docker compose file can be obtained by downloading it to your machine via Seafile 13. People are working on seafile 14 as we speak. So this documentation might be outdated again on the moment you are reading this. Just make sure you check the general website for the latest edition. So this one right now is focused on Seafile 13. Once 14 will be available we will update the instructions below.  

Now! at this moment I don't have a running seafile 13 yet! So I still need to work on this as a project in the future. What I do realize is that you need to have root running for the storage folder. You can also do this without root but also I don't know yet how to do that. For now make sure your TrueNas NFS share has root as the owner. 

  1. Make sure you have compose.yml
  2. Also you need to run seadoc if you want to have the wiki function. Make sure you copy paste the values from the seafile mysql database 
  3. Create an .env file that serves  compose.yml and the seadoc file 

compose.yml
services:
  db:
    image: ${SEAFILE_DB_IMAGE:-mariadb:10.11}
    container_name: seafile-mysql
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=${INIT_SEAFILE_MYSQL_ROOT_PASSWORD:-}
      - MYSQL_LOG_CONSOLE=true
      - MARIADB_AUTO_UPGRADE=1
    volumes:
      - "${SEAFILE_MYSQL_VOLUME:-/opt/seafile-mysql/db}:/var/lib/mysql"
    networks:
      - seafile-net
    healthcheck:
      test:
        [
          "CMD",
          "/usr/local/bin/healthcheck.sh",
          "--connect",
          "--mariadbupgrade",
          "--innodb_initialized",
        ]
      interval: 20s
      start_period: 30s
      timeout: 5s
      retries: 10

  redis:
    image: ${SEAFILE_REDIS_IMAGE:-redis}
    container_name: seafile-redis
    restart: unless-stopped
    command:
      - /bin/sh
      - -c
      - redis-server --requirepass "$$REDIS_PASSWORD"
    environment:
      - REDIS_PASSWORD=${REDIS_PASSWORD:-}
    networks:
      - seafile-net

  seafile:
    image: ${SEAFILE_IMAGE:-seafileltd/seafile-mc:13.0-latest}
    container_name: seafile
    restart: unless-stopped
    # ports:
    #   - "80:80"
    volumes:
      - ${SEAFILE_VOLUME:-/opt/seafile-data}:/shared
    environment:
      - SEAFILE_MYSQL_DB_HOST=${SEAFILE_MYSQL_DB_HOST:-db}
      - SEAFILE_MYSQL_DB_PORT=${SEAFILE_MYSQL_DB_PORT:-3306}
      - SEAFILE_MYSQL_DB_USER=${SEAFILE_MYSQL_DB_USER:-seafile}
      - SEAFILE_MYSQL_DB_PASSWORD=${SEAFILE_MYSQL_DB_PASSWORD:?Variable is not set or empty}
      - INIT_SEAFILE_MYSQL_ROOT_PASSWORD=${INIT_SEAFILE_MYSQL_ROOT_PASSWORD:-}
      - SEAFILE_MYSQL_DB_CCNET_DB_NAME=${SEAFILE_MYSQL_DB_CCNET_DB_NAME:-ccnet_db}
      - SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME:-seafile_db}
      - SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME:-seahub_db}
      - TIME_ZONE=${TIME_ZONE:-Etc/UTC}
      - INIT_SEAFILE_ADMIN_EMAIL=${INIT_SEAFILE_ADMIN_EMAIL:-me@example.com}
      - INIT_SEAFILE_ADMIN_PASSWORD=${INIT_SEAFILE_ADMIN_PASSWORD:-asecret}
      - SEAFILE_SERVER_HOSTNAME=${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}
      - SEAFILE_SERVER_PROTOCOL=${SEAFILE_SERVER_PROTOCOL:-http}
      - SITE_ROOT=${SITE_ROOT:-/}
      - NON_ROOT=${NON_ROOT:-false}
      - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?Variable is not set or empty}
      - SEAFILE_LOG_TO_STDOUT=${SEAFILE_LOG_TO_STDOUT:-false}
      - ENABLE_GO_FILESERVER=${ENABLE_GO_FILESERVER:-true}
      - ENABLE_SEADOC=${ENABLE_SEADOC:-true}
      - SEADOC_SERVER_URL=${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}/sdoc-server
      - CACHE_PROVIDER=${CACHE_PROVIDER:-redis}
      - REDIS_HOST=${REDIS_HOST:-redis}
      - REDIS_PORT=${REDIS_PORT:-6379}
      - REDIS_PASSWORD=${REDIS_PASSWORD:-}
      - MEMCACHED_HOST=${MEMCACHED_HOST:-memcached}
      - MEMCACHED_PORT=${MEMCACHED_PORT:-11211}
      - ENABLE_NOTIFICATION_SERVER=${ENABLE_NOTIFICATION_SERVER:-false}
      - INNER_NOTIFICATION_SERVER_URL=${INNER_NOTIFICATION_SERVER_URL:-http://notification-server:8083}
      - NOTIFICATION_SERVER_URL=${NOTIFICATION_SERVER_URL:-${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}/notification}
      - ENABLE_SEAFILE_AI=${ENABLE_SEAFILE_AI:-false}
      - ENABLE_FACE_RECOGNITION=${ENABLE_FACE_RECOGNITION:-false}
      - SEAFILE_AI_SERVER_URL=${SEAFILE_AI_SERVER_URL:-http://seafile-ai:8888}
      - SEAFILE_AI_SECRET_KEY=${JWT_PRIVATE_KEY:?Variable is not set or empty}
      - MD_FILE_COUNT_LIMIT=${MD_FILE_COUNT_LIMIT:-100000}
    labels:
      caddy: ${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}
      caddy.reverse_proxy: "{{upstreams 80}}"
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - seafile-net

networks:
  seafile-net:
    name: seafile-net

 

.env
#################################
# Docker compose configurations #
#################################
COMPOSE_FILE='seafile-server.yml,caddy.yml,seadoc.yml'
COMPOSE_PATH_SEPARATOR=','

## Images
SEAFILE_IMAGE=seafileltd/seafile-mc:13.0-latest
SEAFILE_DB_IMAGE=mariadb:10.11
SEAFILE_REDIS_IMAGE=redis
SEAFILE_CADDY_IMAGE=lucaslorentz/caddy-docker-proxy:2.12-alpine
SEADOC_IMAGE=seafileltd/sdoc-server:2.0-latest
NOTIFICATION_SERVER_IMAGE=seafileltd/notification-server:13.0-latest
MD_IMAGE=seafileltd/seafile-md-server:13.0-latest

## Persistent Storage
BASIC_STORAGE_PATH=/opt
SEAFILE_VOLUME=$BASIC_STORAGE_PATH/seafile-data
SEAFILE_MYSQL_VOLUME=$BASIC_STORAGE_PATH/seafile-mysql/db
SEAFILE_CADDY_VOLUME=$BASIC_STORAGE_PATH/seafile-caddy
SEADOC_VOLUME=$BASIC_STORAGE_PATH/seadoc-data

#################################
#      Startup parameters       #
#################################
SEAFILE_SERVER_HOSTNAME=seafile.example.com
SEAFILE_SERVER_PROTOCOL=http
TIME_ZONE=Etc/UTC
JWT_PRIVATE_KEY=

#####################################
# Third-party service configuration #
#####################################

## Database
SEAFILE_MYSQL_DB_HOST=db
SEAFILE_MYSQL_DB_USER=seafile
SEAFILE_MYSQL_DB_PASSWORD=PASSWORD
SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_db

## Cache
CACHE_PROVIDER=redis # or memcached


### Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=

### Memcached
MEMCACHED_HOST=memcached
MEMCACHED_PORT=11211

######################################
#        Initial variables           #
# (Only valid in first-time startup) #
######################################

## Database root password, Used to create Seafile users
INIT_SEAFILE_MYSQL_ROOT_PASSWORD=ROOT_PASSWORD

## Seafile admin user
INIT_SEAFILE_ADMIN_EMAIL=me@example.com
INIT_SEAFILE_ADMIN_PASSWORD=asecret

############################################
# Additional configurations for extensions #
############################## Seafile AI
ENABLE_SEAFILE_AI=false
ENABLE_FACE_RECOGNITION=false
SEAFILE_AI_LLM_TYPE=openai
SEAFILE_AI_LLM_URL=
SEAFILE_AI_LLM_KEY= # your llm key
SEAFILE_AI_LLM_MODEL=gpt-4o-mini

## Metadata server
MD_FILE_COUNT_LIMIT=100000
################

## SeaDoc service
ENABLE_SEADOC=true

## Notification
ENABLE_NOTIFICATION_SERVER=false
NOTIFICATION_SERVER_URL=

 

compose.yml
services:
  db:
    image: mariadb:10.11
    container_name: seafile-db
    environment:
      - MYSQL_ROOT_PASSWORD=${SEAFILE_PASSWORD}
      - MYSQL_LOG_CONSOLE=true
      - MARIADB_AUTO_UPGRADE=1
    volumes:
      - ./mysql:/var/lib/mysql
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 20s
      start_period: 30s
      timeout: 5s
      retries: 10

  redis:
    image: redis:7
    container_name: seafile-redis
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 20s
      timeout: 5s
      retries: 5

  seafile:
    image: seafileltd/seafile-mc:13.0-latest
    container_name: seafile
    ports:
      - "1026:80"
    volumes:
      - /mnt/seafile:/shared
    environment:
      # Core
      - SEAFILE_SERVER_HOSTNAME=${SEAFILE_SERVER_HOSTNAME}
      - SEAFILE_SERVER_PROTOCOL=${SEAFILE_SERVER_PROTOCOL}
      - TIME_ZONE=${TIME_ZONE}
      - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY}

      # Database
      - DB_HOST=db
      - DB_ROOT_PASSWD=${SEAFILE_PASSWORD}
      - SEAFILE_MYSQL_DB_HOST=db
      - SEAFILE_MYSQL_DB_USER=seafile
      - SEAFILE_MYSQL_DB_PASSWORD=${SEAFILE_PASSWORD}
      - SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
      - SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
      - SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_db

      # Cache
      - CACHE_PROVIDER=redis
      - REDIS_HOST=redis
      - REDIS_PORT=6379

      # First-run admin bootstrap (ignored on subsequent starts)
      - INIT_SEAFILE_ADMIN_EMAIL=${SEAFILE_ADMIN_EMAIL}
      - INIT_SEAFILE_ADMIN_PASSWORD=${SEAFILE_PASSWORD}
      - INIT_SEAFILE_MYSQL_ROOT_PASSWORD=${SEAFILE_PASSWORD}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

 

.env
# ==== Hostname ====
# Bake-in on first run - the domain or IP:port you will actually use to reach Seafile.
# Do NOT change after first boot without also editing conf/seahub_settings.py.
SEAFILE_SERVER_HOSTNAME=10.69.1.106
SEAFILE_SERVER_PROTOCOL=http
TIME_ZONE=Europe/Amsterdam

# ==== Secrets ====
# Paste the output of: openssl rand -base64 40
JWT_PRIVATE_KEY=m41h+mivVsScLtvik+ebyiPvyjlhiZ1HgeIAjkp5J92aQqnda7uLkQ==

# ONE password used for everything: MariaDB root, Seafile's DB user, and the Seafile web admin.
# Pick a strong unique value.
SEAFILE_PASSWORD=NKetpcGDPqCXGlBYATTTZ26E438Ns1FOPoqlUX0QeOGmgsEYSBToAg==

# Admin email for the first-boot web UI login (only used on first startup).
SEAFILE_ADMIN_EMAIL=dp@p2pnode.org

 

1.5.6 Penpot

1.5.7 Vaultwarden

1.5.8 Jellyfin

1.5.8 Jellyfin

1. Installation

For Jellyfin we wil install it in the following way

 

 

1.5.9 Emby

1.5.10 Karakeep

1.5.11 Linkwarden

1.5.12 Penpot

Penpot is the open-source design platform for teams that need scalable collaboration.

1.5.12 Penpot

01. Installation

The installation of Penpot is quite straightforward. 

According to its own official documentation the first step we need to do is obtaining all the resources necessary to run Penpot as a docker container with the following command

wget https://raw.githubusercontent.com/penpot/penpot/main/docker/images/docker-compose.yaml

If you follow along with the Dockge installations it is recommended to run this inside the /opt/stacks directory such that it sits directly in the directory that Dockge can access it. 

 

1.5.13 Nexterm

1.5.13 Nexterm

1. Installation

Source: https://nexterm.dev/install

 

1.5.14 Invoice Ninja

Invoice Ninja is a free, open-source invoicing, expenses, and time-tracking application for freelancers and small businesses, offering features like custom-branded invoices, recurring billing, online payments via 40+ gateways, project and task management, and expense tracking

Invoice Ninja is by far the most comfortable invoice system that I have used so far for my small freelance business in the hospitality industry. It has more features that I will ever need but it makes invoicing employers just so streamlined and easy. Instead of manually making all your invoices, sending emails, keeping track of payments, due dates, manual payments and more. Invoice Ninja makes this all a smooth experience for both you and the person that needs to pay you. 

1.5.14 Invoice Ninja

01. Docker Installation

Alright, so if you have a small business or you are simply a freelancer in any field then Invoice Ninja might be for you. It is quite an easy install but you need to know a couple of gotcha's to get things rolling. I show you here my method that works for me, and you can just copy paste that. However, there are of course other workflows possible as well.

According to its official documentation 

Docker Compose File
services:
  app:
    build:
      context: .
    image: invoiceninja/invoiceninja-debian:${TAG:-latest}
    restart: unless-stopped
    env_file:
      - ./.env
    volumes:
      # - ./php/php.ini:/usr/local/etc/php/conf.d/invoiceninja.ini:ro
      # - ./php/php-fpm.conf:/usr/local/etc/php-fpm.d/invoiceninja.conf:ro
      # - ./supervisor/supervisord.conf:/etc/supervisor/conf.d/supervisord.conf:ro
      - app_public:/var/www/html/public
      - app_storage:/var/www/html/storage
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - 8333:80
    volumes:
      - ./nginx:/etc/nginx/conf.d:ro
      - app_public:/var/www/html/public:ro
      - app_storage:/var/www/html/storage:ro
    depends_on:
      app:
        condition: service_healthy
  mysql:
    image: mysql:8
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test:
        - CMD
        - mysqladmin
        - ping
        - -h
        - localhost
        - -u${MYSQL_USER}
        - -p${MYSQL_PASSWORD}
  redis:
    image: redis:alpine
    restart: unless-stopped
    volumes:
      - redis_data:/data
    healthcheck:
      test:
        - CMD
        - redis-cli
        - ping
volumes:
  app_public:
    driver: local
  app_storage:
    driver: local
  mysql_data:
    driver: local
  redis_data:
    driver: local
networks: {}

 

 

.env
# IN application vars
TAG=latest
APP_URL=https://invoice.rfeyn.org
APP_KEY=base64:gDCwop+h+/KcHsrCzc7tt4lbqcqH8hzMnm3x3+8oGnM=
API_SECRET=Kikkers49$91
APP_ENV=production
APP_DEBUG=true
REQUIRE_HTTPS=false
PHANTOMJS_PDF_GENERATION=false #Turn this function off to generate pdf with snappdf
PDF_GENERATOR=snappdf
TRUSTED_PROXIES='*'


CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

FILESYSTEM_DISK=debian_docker

# DB connection
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=ninjadplancer
DB_USERNAME=ninja
DB_PASSWORD=sf9823fsjlfkjwioefjlskfh0923jfklsdjf
DB_ROOT_PASSWORD=sdpoifj293fj09030rjnsnedfo3498t
DB_CONNECTION=mysql

# Create initial user
# Default to these values if empty
IN_USER_EMAIL=dp@p2pnode.org
IN_PASSWORD=nvQi5W14blOnpv
# IN_USER_EMAIL=
# IN_PASSWORD=

# Mail options
MAIL_MAILER=log
MAIL_HOST=smtp.porkbun.com
MAIL_PORT=587
MAIL_USERNAME=dp@p2pnode.org
MAIL_PASSWORD=PuV9#@pUca49C70e2V46&keg
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS='dp@p2pnode.org'
MAIL_FROM_NAME="DPLancer Invoices"

# MySQL
MYSQL_ROOT_PASSWORD=sdpoifj293fj09030rjnsnedfo3498t
MYSQL_USER=ninja
MYSQL_PASSWORD=sf9823fsjlfkjwioefjlskfh0923jfklsdjf
MYSQL_DATABASE=ninjadplancer

# GoCardless/Nordigen API key for banking integration
NORDIGEN_SECRET_ID=
NORDIGEN_SECRET_KEY=

IS_DOCKER=true
SCOUT_DRIVER=null
#SNAPPDF_CHROMIUM_PATH=/usr/bin/google-chrome-stable

 

 

 

2. 

1.5.14 Invoice Ninja

02. Payment Integrations (Mollie)

After you have successfully installed Invoice Ninja and created an account it is time to connect a payment integration with it. You don't need to have a payment integration to use Invoice Ninja, however if you went through the hassle of installing it you better do it ;) Yes the integrations are not fully open source but it's the best we have so far

1. Go to Settings --> Payment Settings --> Add Payment Gateway

2. Then choose a payment gateway that suits your needs. This could be for instance Stripe for USA and Mollie for European customers. I am from the Netherlands where most people use iDeal/Wero for their payments. Hence, I went with Mollie for my personal invoice ninja instance for my hospitality business. Hence, the following is a Mollie integration but it would work the same for any other payment gateway.

3. In the case of Mollie create an account and make sure you provide them with a business website or a proper description of your business. This is necessary for them to accept iDeal payments for instance.

4. If the iDeal payments are not accepted automatically after one day then I would recommend to contact support. This is what I did and they accepted it straight away.

5. If that is all set you need to obtain an API key and a Profile ID. 

6. Inside of Mollie click on "Developers" that sits in the left bottom corner of your dashboard.

7. Then choose API Access Tokens -> + Create new Token

8. 

1.5.14 Invoice Ninja

03. Localization

This is a short page, but it is of importance! If you operate in country X you would like that the emails and client portals your clients interact with is in language X. This is where localization comes in. 

  1. Go to Settings -> Localization
  2. Then select the right language, timezone and date format for the country you are operating in. 

1.5.15 Frigate NVR

1.5.15 Frigate NVR

01. Frigate Installation

Configuration File
mqtt:
  enabled: true
  host: 10.131.2.131
  user: peaske  
  password: 7&QpP1qBKikkers49$91

semantic_search:
  enabled: true
  reindex: false

record:
  enabled: true
  continuous:
    days: 14

snapshots:
  enabled: true
  retain:
    default: 14
  required_zones: [cam_1_person, cam_2_person, cam_3_person, cam_4_person, 
        cam_5_person, cam_6_person, cam_7_person, cam_8_person, cam_9_person]
  # Optional: Camera override for retention settings (default: global values)

objects:
  track:
    - person

cameras:     # No cameras defined, UI wizard should be used
  cam_1:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.101:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.101:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    objects:
      track:
        - person
        - car
    detect:
      enabled: true
      width: 1280
      height: 720
      fps: 5
    review:
      alerts:
        labels:
          - person
          - car
        required_zones:
          - cam_1_person
    #      - cam_1_car
      detections:
        labels:
          - person
        required_zones:
          - cam_1_person
    #      - cam_1_car
    live:
      streams:
        Stream 1: cam_1_1
        Stream 2: cam_1_2
    notifications:
      enabled: true
    zones:
      cam_1_person:
        coordinates: 0.679,0.187,0.407,0.989,0.668,0.997,0.72,0.593,0.737,0.195
        inertia: 3
        loitering_time: 0
        objects: person
      #cam_1_car:
      #  coordinates: 0.539,0.127,0.285,0.374,0.073,0.772,0.186,1,0.651,0.149
      #  loitering_time: 0
      #  inertia: 3
      #  objects: car
  cam_2:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.102:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.102:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      enabled: true
      width: 1280
      height: 720
      fps: 5
    review:
      alerts:
        labels:
          - person
        required_zones:
          - cam_2_person
      detections:
        labels:
          - person
        required_zones:
          - cam_2_person
    live:
      streams:
        Stream 1: cam_2_1
        Stream 2: cam_2_2
    notifications:
      enabled: true
    zones:
      cam_2_person:
        coordinates: 0.483,0.265,0.35,0.998,0.754,0.992,0.582,0.009
        loitering_time: 0
        objects: person
    objects:
      filters:
        person:
          mask: 0.004,0.011,0.003,0.991,0.335,0.992,0.478,0.241,0.57,0.008
  cam_3:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.103:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.103:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      enabled: true
      width: 1280
      height: 720
      fps: 5
    review:
      alerts:
        labels:
          - person
        required_zones:
          - cam_3_person
      detections:
        labels:
          - person
        required_zones:
          - cam_3_person
    live:
      streams:
        Stream 1: cam_3_1
        Stream 2: cam_3_2
    zones:
      cam_3_person:
        coordinates: 0.497,0.106,0.59,0.991,0.462,0.992,0.45,0.13
        loitering_time: 0
        objects: person
    objects:
      filters:
        person:
          mask: 
            0.506,0.113,0.601,0.982,0.995,0.989,0.992,0.018,0.545,0,0.61,0.087
  cam_4:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.104:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.104:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      enabled: true
      width: 1280
      height: 720
      fps: 5
    review:
      alerts:
        labels:
          - person
        required_zones:
          - cam_4_person
      detections:
        labels:
          - person
        required_zones:
          - cam_4_person
    live:
      streams:
        Stream 1: cam_4_1
        Stream 2: cam_4_2
    notifications:
      enabled: true
    zones:
      cam_4_person:
        coordinates: 0.593,0.465,0.712,0.994,0.366,0.997,0.374,0.466
        loitering_time: 0
        objects: person
    objects:
      mask: 
        0.311,0.005,0.359,0.431,0.664,0.437,0.817,0.995,0.995,0.998,0.994,0.009
  cam_5:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.105:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.105:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      enabled: true
      width: 1280
      height: 720
      fps: 5
    review:
      alerts:
        labels:
          - person
        required_zones:
          - cam_5_person
      detections:
        labels:
          - person
        required_zones:
          - cam_5_person
    live:
      streams:
        Stream 1: cam_5_1
        Stream 2: cam_5_2
    notifications:
      enabled: true
    zones:
      cam_5_person:
        coordinates: 0.534,0.116,0.517,0.991,0.332,0.998,0.491,0.113
        loitering_time: 0
        objects: person
    objects:
      mask: 0.004,0.005,0.006,0.997,0.315,0.994,0.491,0.023
  cam_6:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.106:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.106:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      enabled: true
      width: 1280
      height: 720
      fps: 5
    review:
      alerts:
        labels:
          - person
        required_zones:
          - cam_6_person
      detections:
        labels:
          - person
        required_zones:
          - cam_6_person
    live:
      streams:
        Stream 1: cam_6_1
        Stream 2: cam_6_2
    notifications:
      enabled: true
    zones:
      cam_6_person:
        coordinates: 0.225,0.198,0.279,0.593,0.346,0.998,0.489,1,0.253,0.166
        loitering_time: 0
        objects: person
    objects:
      mask:
        - 0.267,0.008,0.264,0.157,0.505,0.997,0.99,0.986,0.991,0.011
        - 0.003,0.008,0.007,0.994,0.31,0.991,0.194,0.017
  cam_7:
    enabled: true
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam_7_1
          input_args: preset-rtsp-restream
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.107:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      enabled: true
      width: 1280
      height: 720
      fps: 5
    review:
      alerts:
        labels:
          - person
        required_zones:
          - cam_7_person
      detections:
        labels:
          - person
        required_zones:
          - cam_7_person
    live:
      streams:
        Stream 1: cam_7_1
        Stream 2: cam_7_2
    notifications:
      enabled: true
    zones:
      cam_7_person:
        coordinates: 0.519,0.071,0.603,0.991,0.806,0.991,0.595,0.027
        loitering_time: 0
        objects: person
    objects:
      mask:
        - 0.005,0.015,0.009,0.986,0.436,0.991,0.457,0.015
        - 0.614,0.014,0.835,0.992,0.996,0.988,0.997,0.009
  cam_8:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.108:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.108:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      enabled: true
      width: 1280
      height: 720
      fps: 5
    review:
      alerts:
        labels:
          - person
        required_zones:
          - cam_8_person
      detections:
        labels:
          - person
        required_zones:
          - cam_8_person
    live:
      streams:
        Stream 1: cam_8_1
        Stream 2: cam_8_2
    notifications:
      enabled: true
    zones:
      cam_8_person:
        coordinates: 
          0.287,0.585,0.092,0.753,0.126,0.887,0.19,1,0.356,0.931,0.477,0.694
        loitering_time: 0
        objects: person
  cam_9:
    enabled: true
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam_9_1
          input_args: preset-rtsp-restream
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.109:554/cam/realmonitor?channel=1&subtype=1
          roles:
            - detect
    detect:
      enabled: true
      width: 1280
      height: 720
      fps: 5
    review:
      alerts:
        labels:
          - person
        required_zones:
          - cam_9_person
      detections:
        labels:
          - person
        required_zones:
          - cam_9_person
    live:
      streams:
        Stream 1: cam_9_1
        Stream 2: cam_9_2
    notifications:
      enabled: true
    zones:
      cam_9_person:
        coordinates: 0.362,0.184,0.703,0.52,0.554,0.971,0.365,0.973,0.128,0.588
        loitering_time: 0
        objects: person
version: 0.17-0
go2rtc:
  streams:
    cam_1_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.101:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_1_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.101:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_2_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.102:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_2_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.102:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_3_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.103:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_3_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.103:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_4_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.104:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_4_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.104:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_5_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.105:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_5_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.105:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_6_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.106:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_6_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.106:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_7_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.107:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_7_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.107:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_8_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.108:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_8_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.108:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_9_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.109:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_9_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.109:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif

notifications:
  enabled: true
  email: dp@p2pnode.org

 

 

 

 

 

Snapshot

Alerts

Detections

Recording

objects:
  track:
    #- person
    #- car
  filters:
    person:
      min_score: .60
      threshold: .65
    car:
      min_score: .60
      threshold: .65


Compose file
services:
  frigate:
    mem_limit: 5g
    container_name: frigate
    privileged: true # this may not be necessary for all setups
    restart: unless-stopped
    stop_grace_period: 30s # allow enough time to shut down the various services
    image: ghcr.io/blakeblackshear/frigate:stable
    shm_size: "1000mb" # update for your cameras based on calculation above
    devices:
   #   - /dev/bus/usb:/dev/bus/usb # Passes the USB Coral, needs to be modified for other versions
   #   - /dev/apex_0:/dev/apex_0 # Passes a PCIe Coral, follow driver instructions here https://coral.ai/docs/m2/get-started/#2a-on-linux
   #   - /dev/video11:/dev/video11 # For Raspberry Pi 4B
   #   - /dev/dri/card0:/dev/dri/card0 # For intel hwaccel, needs to be updated for your hardware
      - /dev/dri/renderD128:/dev/dri/renderD128 # For intel hwaccel, needs to be updated for your hardware   
    volumes:
      - ./etc/localtime:/etc/localtime:ro
      - ./config:/config
      - ./media:/media/frigate
      - type: tmpfs # Optional: 1GB of memory, reduces SSD/SD Card wear
        target: /tmp/cache
        tmpfs:
          size: 1000000000
    ports:
      - 8971:8971
      - 8080:8080
      - 5000:5000 # Internal unauthenticated access. Expose carefully.
      - 8554:8554 # RTSP feeds
      - 8555:8555/tcp # WebRTC over tcp
      - 8555:8555/udp # WebRTC over udp
    environment:
      FRIGATE_RTSP_PASSWORD: "Stel137&QpP1qB"
  mqtt:
    mem_limit: 1g
    image: eclipse-mosquitto:latest
    container_name: mqtt
    restart: unless-stopped
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log
    ports:
      - 1883:1883

 

# With 1280x720 detect resolution

mqtt:
  host: mqtt # The hostname of mqtt in the docker compose file
  topic_prefix: frigate
  enabled: true

# _______________________________________Detection______________________________________

#ffmpeg: #If you use an AMD CPU for detection then leave this whole section uncommented
#  hwaccel_args: preset-vaapi
#  output_args: 
#    record: preset-record-generic-audi-aac

#detectors:
#  ov_0:
#    type: openvino
#    device: CPU
#  ov_1:
#    type: openvino
#    device: CPU
#  ov_2:
#    type: openvino
#    device: CPU


#logger:
#  default: info
#  logs:
#    frigate.video: debug  # Enable FFmpeg debug logs
#ffmpeg:
#  input_args:
#    - hwaccel
#    - vaapi        
#    - rtsp_transport
#    - tcp
#
# ----------------------------------------Timezone ------------------------------------
# This makes sure that Frigate always uses the local time and does not switch to another 
# Timezone when it is viewed remotely.

ui:
  timezone: Europe/Berlin

# ----------------------------------------Tracked Objects ------------------------------------

objects:
  track:
    - person
    - car
  filters:
    person:
      min_score: .60
      threshold: .65
    car:
      min_score: .60
      threshold: .65

detect:
  enabled: true
go2rtc:
  streams:
    cam1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.101/cam/realmonitor?channel=1&subtype=0
    cam1_sub:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.101/cam/realmonitor?channel=1&subtype=1
    cam2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.102/cam/realmonitor?channel=1&subtype=0
    cam2_sub:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.102/cam/realmonitor?channel=1&subtype=1
    cam3:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.103/cam/realmonitor?channel=1&subtype=0
    cam3_sub:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.103/cam/realmonitor?channel=1&subtype=1
    cam4:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.104/cam/realmonitor?channel=1&subtype=0
    cam4_sub:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.104/cam/realmonitor?channel=1&subtype=1
    cam5:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.105/cam/realmonitor?channel=1&subtype=0
    cam5_sub:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.105/cam/realmonitor?channel=1&subtype=1
    cam6:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.106/cam/realmonitor?channel=1&subtype=0
    cam6_sub:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.106/cam/realmonitor?channel=1&subtype=1
    cam7:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.107/cam/realmonitor?channel=1&subtype=0
    cam7_sub:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.107/cam/realmonitor?channel=1&subtype=1
    cam8:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.108/cam/realmonitor?channel=1&subtype=0
    cam8_sub:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.108/cam/realmonitor?channel=1&subtype=1
    cam9:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.109/cam/realmonitor?channel=1&subtype=0
    cam9_sub:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.109/cam/realmonitor?channel=1&subtype=1


cameras:
# ----------------------------------------CAM 1 ------------------------------------
  cam1:
    review:
      alerts:
        required_zones: cam1_parking_lot
      detections:
        required_zones: cam1_parking_lot
    lpr:
      enabled: false
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam1_sub
          input_args: preset-rtsp-restream
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cam1
          input_args: preset-rtsp-restream
          roles:
            - record
      output_args:
        record: preset-record-generic-audio-aac
    detect:
      width: 1280
      height: 720
      fps: 5
    objects:
      track:
        - person
        - car
      filters:
        person:
          mask: 0,1,0.117,1,0.236,0.86,0.649,0.13,0.719,0,0.625,0,0,0
    snapshots:
      required_zones:
        - cam1_parking_lot
      enabled: true
      timestamp: true
      bounding_box: true
      crop: true
      height: 500
      retain:
        default: 14
    record:
      enabled: true
      alerts:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      detections:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      continuous:
        retain:
          days: 14
      motion:
        days: 0
    notifications:
      enabled: true

    zones:
      cam1_parking_lot:
        coordinates: 
          0.235,0.881,0.129,1,0.487,1,0.678,0.997,0.741,0.47,0.747,0.207,0.752,0,0.733,0,0.715,0.026,0.495,0.423,0.485,0.437
        inertia: 3
        loitering_time: 0
    motion:
      mask: 
        0.742,0,0.745,0.204,0.741,0.46,0.718,0.719,0.678,0.998,0.996,0.998,0.994,0.003

# ----------------------------------------CAM 2 ------------------------------------

  cam2:
    review:
      alerts:
        required_zones: cam2_person
      detections:
        required_zones: cam2_person
    lpr:
      enabled: false
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam2_sub
          input_args: preset-rtsp-restream
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cam2
          input_args: preset-rtsp-restream
          roles:
            - record
      output_args:
        record: preset-record-generic-audio-aac
    detect:
      width: 1280
      height: 720
      fps: 5
    objects:
      track:
        - person
      filters:
        person:
          min_area: 2000
          max_area: 90000
      mask: 0,0,0,1,0.373,1,0.56,0
    snapshots:
      required_zones:
        - cam2_person
      enabled: true
      timestamp: true
      bounding_box: true
      crop: true
      height: 500
      retain:
        default: 14
    record:
      enabled: true
      alerts:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      detections:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      continuous:
        days: 14
      motion:
        days: 0
    notifications:
      enabled: true

    zones:
      cam2_person:
        coordinates: 0.808,1,0.374,1,0.56,0,0.58,0
        inertia: 3
        loitering_time: 0
        objects: person
    motion:
      mask: 0.584,0,0.812,0.998,0.995,0.995,1,0

# ----------------------------------------CAM 3 ------------------------------------

  cam3:
    review:
      alerts:
        required_zones: cam3_person
      detections:
        required_zones: cam3_person
    lpr:
      enabled: false
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam3_sub
          input_args: preset-rtsp-restream
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cam3
          input_args: preset-rtsp-restream
          roles:
            - record
      output_args:
        record: preset-record-generic-audio-aac
    detect:
      width: 1280
      height: 720
      fps: 5
    objects:
      track:
        - person
      mask: 0.539,0.003,0.732,1,0.996,0.995,0.996,0.008
    snapshots:
      required_zones:
        - cam3_person
      enabled: true
      timestamp: true
      bounding_box: true
      crop: true
      height: 500
      retain:
        default: 14
    record:
      enabled: true
      alerts:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      detections:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      continuous:
        days: 14
      motion:
        days: 0
    notifications:
      enabled: true

    zones:
      cam3_person:
        coordinates: 0.442,0,0.243,0.992,0.723,1,0.535,0.003
        inertia: 3
        loitering_time: 0
        objects: person
    motion:
      mask:
        - 0.627,0.032,0.631,0.079,0.971,0.077,0.972,0.034
        - 0.002,0.003,0.007,0.994,0.238,0.99,0.44,0.003

# ----------------------------------------CAM 4 ------------------------------------

  cam4:
    review:
      alerts:
        required_zones: cam4_person
      detections:
        required_zones: cam4_person
    lpr:
      enabled: false
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam4_sub
          input_args: preset-rtsp-restream
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cam4
          input_args: preset-rtsp-restream
          roles:
            - record
      output_args:
        record: preset-record-generic-audio-aac
    detect:
      width: 1280
      height: 720
      fps: 5
    objects:
      track:
        - person
      mask:
        - 0.67,1,0.996,0.992,0.997,0.006,0.302,0.001,0.366,0.538,0.52,0.496,0.575,0.468
        - 0.003,0.002,0,0.978,0.039,0.537,0.295,0.014
    snapshots:
      required_zones:
        - cam4_person
      enabled: true
      timestamp: true
      bounding_box: true
      crop: true
      height: 500
      retain:
        default: 14
    record:
      enabled: true
      alerts:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      detections:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      continuous:
        days: 14
      motion:
        days: 0
    notifications:
      enabled: true

    zones:
      cam4_person:
        coordinates: 
          0.045,0.541,0,0.998,0.664,1,0.572,0.493,0.501,0.513,0.364,0.545,0.299,0.016
        inertia: 3
        loitering_time: 0
        objects: person
    motion: {}

# ----------------------------------------CAM 5 ------------------------------------

  cam5:
    review:
      alerts:
        required_zones: cam5_person
      detections:
        required_zones: cam5_person
    lpr:
      enabled: false
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam5_sub
          input_args: preset-rtsp-restream
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cam5
          input_args: preset-rtsp-restream
          roles:
            - record
      output_args:
        record: preset-record-generic-audio-aac
    detect:
      width: 1280
      height: 720
      fps: 5
    objects:
      track:
        - person
      mask: 0,0,0,1,0.179,1,0.511,0
    snapshots:
      required_zones:
        - cam5_person
      enabled: true
      timestamp: true
      bounding_box: true
      crop: true
      height: 500
      retain:
        default: 14
    record:
      enabled: true
      alerts:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      detections:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      continuous:
        days: 14
      motion:
        days: 0
    notifications:
      enabled: true

    zones:
      cam5_person:
        coordinates: 
          0.521,0,0.456,0.185,0.383,0.42,0.308,0.639,0.193,1,0.811,0.997,0.568,0.005
        inertia: 3
        loitering_time: 0
        objects: person
    motion:
      mask: 0.566,0,0.816,0.998,0.999,0.995,0.998,0.005

# ----------------------------------------CAM 6 ------------------------------------

  cam6:
    review:
      alerts:
        required_zones: cam6_person
      detections:
        required_zones: cam6_person
    lpr:
      enabled: false
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam6_sub
          input_args: preset-rtsp-restream
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cam6
          input_args: preset-rtsp-restream
          roles:
            - record
      output_args:
        record: preset-record-generic-audio-aac
    detect:
      width: 1280
      height: 720
      fps: 5
    objects:
      track:
        - person
      filters:
        person:
          min_area: 3000
          max_area: 90000
      mask: 
        1,1,1,0,0.216,0,0.235,0.231,0.271,0.22,0.298,0.21,0.312,0.249,0.554,1
    snapshots:
      required_zones:
        - cam6_person
      enabled: true
      timestamp: true
      bounding_box: true
      crop: true
      height: 500
      retain:
        default: 14
    record:
      enabled: true
      alerts:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      detections:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      continuous:
        days: 14
      motion:
        days: 0
    notifications:
      enabled: true

    zones:
      cam6_person:
        coordinates: 
          0.215,0.016,0.131,0.333,0.075,1,0.543,0.988,0.296,0.222,0.232,0.245
        inertia: 3
        loitering_time: 0
        objects: person
    motion:
      mask: 0.002,0.003,0.005,0.997,0.069,0.997,0.128,0.321,0.212,0.002

# ----------------------------------------CAM 7 ------------------------------------

  cam7:
    review:
      alerts:
        required_zones: cam7_person
      detections:
        required_zones: cam7_person
    lpr:
      enabled: false
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam7_sub
          input_args: preset-rtsp-restream
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cam7
          input_args: preset-rtsp-restream
          roles:
            - record
      output_args:
        record: preset-record-generic-audio-aac
    detect:
      width: 1280
      height: 720
      fps: 5
    objects:
      track:
        - person
      mask: 0.616,0.003,0.756,0.445,0.866,0.99,0.994,0.997,0.996,0.006
    snapshots:
      required_zones:
        - cam7_person
      enabled: true
      timestamp: true
      bounding_box: true
      crop: true
      height: 500
      retain:
        default: 14
    record:
      enabled: true
      alerts:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      detections:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      continuous:
        days: 14
      motion:
        days: 0
    notifications:
      enabled: true

    zones:
      cam7_person:
        coordinates: 
          0.404,0,0.308,1,0.861,0.994,0.815,0.742,0.739,0.408,0.611,0.002
        inertia: 3
        loitering_time: 0
        objects: person
    motion:
      mask:
        - 0.001,0.003,0.004,0.992,0.303,0.997,0.398,0.005
        - 0.629,0.03,0.633,0.074,0.97,0.079,0.971,0.032

# ----------------------------------------CAM 8 ------------------------------------

  cam8:
    review:
      alerts:
        required_zones: cam8_person
      detections:
        required_zones: cam8_person
    lpr:
      enabled: false
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam8_sub
          input_args: preset-rtsp-restream
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cam8
          input_args: preset-rtsp-restream
          roles:
            - record
      output_args:
        record: preset-record-generic-audio-aac
    detect:
      width: 1280
      height: 720
      fps: 5
    objects:
      track:
        - person
      filters:
        person:
          min_area: 5000
          max_area: 90000
      mask: 
        0.279,0.01,0.342,0.604,0.67,0.801,0.634,0.944,0.62,0.997,0.996,0.995,0.995,0.003
    snapshots:
      required_zones:
        - cam8_person
      enabled: true
      timestamp: true
      bounding_box: true
      crop: true
      height: 500
      retain:
        default: 14
    record:
      enabled: true
      alerts:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      detections:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      continuous:
        days: 14
      motion:
        days: 0
    notifications:
      enabled: true

    zones:
      cam8_person:
        coordinates: 0.042,0.612,0.174,0.99,0.62,0.997,0.671,0.798,0.155,0.499
        inertia: 3
        loitering_time: 0
        objects: person
    motion:
      mask: 0.629,0.027,0.634,0.08,0.972,0.08,0.972,0.03

# ----------------------------------------CAM 9 ------------------------------------

  cam9:
    lpr:
      enabled: false
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam9_sub
          input_args: preset-rtsp-restream
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cam9
          input_args: preset-rtsp-restream
          roles:
            - record
      output_args:
        record: preset-record-generic-audio-aac
    detect:
      width: 1280
      height: 720
      fps: 5
    objects:
      track:
        - person
      filters:
        person:
          min_area: 5000
          max_area: 90000
    snapshots:
      required_zones:
        - cam9_person
      enabled: true
      timestamp: true
      bounding_box: true
      crop: true
      height: 500
      retain:
        default: 14
    record:
      enabled: true
      alerts:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      detections:
        retain:
          days: 14
        pre_capture: 15
        post_capture: 15
      continuous:
        days: 14
      motion:
        days: 0
    notifications:
      enabled: true

    motion:
      mask: 0.627,0.032,0.628,0.079,0.971,0.082,0.974,0.032
    zones:
      cam9_person:
        coordinates: 
          0.123,0.587,0.374,1,0.562,1,0.824,0.018,0.488,0.01,0.324,0.253
        loitering_time: 0
        objects: person
version: 0.17-0


semantic_search:
  enabled: true
  model_size: small
face_recognition:
  enabled: false
  model_size: small
lpr:
  enabled: false
classification:
  bird:
    enabled: false
notifications:
  enabled: true
  email: dp@p2pnode.org

 

New Config
mqtt:
  enabled: true

semantic_search:
  enabled: true
  reindex: false

objects:
  track:
    - person
    - car
  filters:
    person:
      min_score: .60
      threshold: .65
    car:
      min_score: .60
      threshold: .65



cameras:     # No cameras defined, UI wizard should be used
  cam_1:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.101:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.101:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      width: 1280
      height: 720
    record:
      enabled: true
      continuous:
        days: 14
    live:
      streams:
        Stream 1: cam_1_1
        Stream 2: cam_1_2
    notifications:
      enabled: true
    zones:
      cam_1_person:
        coordinates: 
          0.596,0.09,0.523,0.089,0,0.549,0,1,0.111,1,0.607,0.985,0.652,0.097
        inertia: 3
        loitering_time: 0
        objects: person
  cam_2:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.102:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.102:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      width: 1280
      height: 720
    record:
      enabled: true
      continuous:
        days: 14
    live:
      streams:
        Stream 1: cam_2_1
        Stream 2: cam_2_2
    notifications:
      enabled: true
    zones:
      cam_2_person:
        coordinates: 0.483,0.265,0.35,0.998,0.754,0.992,0.582,0.009
        loitering_time: 0
        objects: person
  cam_3:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.103:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.103:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      width: 1280
      height: 720
    live:
      streams:
        Stream 1: cam_3_1
        Stream 2: cam_3_2
    zones:
      cam_3_person:
        coordinates: 0.497,0.106,0.59,0.991,0.462,0.992,0.45,0.13
        loitering_time: 0
        objects: person
  cam_4:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.104:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.104:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      width: 1280
      height: 720
    record:
      enabled: true
      continuous:
        days: 14
    live:
      streams:
        Stream 1: cam_4_1
        Stream 2: cam_4_2
    notifications:
      enabled: true
    zones:
      cam_4_person:
        coordinates: 0.593,0.465,0.712,0.994,0.366,0.997,0.374,0.466
        loitering_time: 0
        objects: person
  cam_5:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.105:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.105:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      width: 1280
      height: 720
    record:
      enabled: true
      continuous:
        days: 14
    live:
      streams:
        Stream 1: cam_5_1
        Stream 2: cam_5_2
    notifications:
      enabled: true
    zones:
      cam_5_person:
        coordinates: 0.534,0.116,0.517,0.991,0.332,0.998,0.491,0.113
        loitering_time: 0
        objects: person
  cam_6:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.106:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.106:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      width: 1280
      height: 720
    record:
      enabled: true
      continuous:
        days: 14
    live:
      streams:
        Stream 1: cam_6_1
        Stream 2: cam_6_2
    notifications:
      enabled: true
    zones:
      cam_6_person:
        coordinates: 0.225,0.198,0.279,0.593,0.346,0.998,0.489,1,0.253,0.166
        loitering_time: 0
        objects: person
  cam_7:
    enabled: true
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam_7_1
          input_args: preset-rtsp-restream
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.107:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      width: 1280
      height: 720
    record:
      enabled: true
      continuous:
        days: 14
    live:
      streams:
        Stream 1: cam_7_1
        Stream 2: cam_7_2
    notifications:
      enabled: true
    zones:
      cam_7_person:
        coordinates: 0.519,0.071,0.603,0.991,0.806,0.991,0.595,0.027
        loitering_time: 0
        objects: person
  cam_8:
    enabled: true
    ffmpeg:
      inputs:
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.108:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.108:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
          roles:
            - detect
    detect:
      width: 1280
      height: 720
    record:
      enabled: true
      continuous:
        days: 14
    live:
      streams:
        Stream 1: cam_8_1
        Stream 2: cam_8_2
    notifications:
      enabled: true
    zones:
      cam_8_person:
        coordinates: 
          0.287,0.585,0.092,0.753,0.126,0.887,0.19,1,0.356,0.931,0.477,0.694
        loitering_time: 0
        objects: person
  cam_9:
    enabled: true
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cam_9_1
          input_args: preset-rtsp-restream
          roles:
            - record
            - audio
        - path: 
            rtsp://admin:Stel13DeternetArea1()@10.131.2.109:554/cam/realmonitor?channel=1&subtype=1
          roles:
            - detect
    detect:
      width: 1280
      height: 720
    record:
      enabled: true
      continuous:
        days: 14
    live:
      streams:
        Stream 1: cam_9_1
        Stream 2: cam_9_2
    notifications:
      enabled: true
    zones:
      cam_9_person:
        coordinates: 0.362,0.184,0.703,0.52,0.554,0.971,0.365,0.973,0.128,0.588
        loitering_time: 0
        objects: person
version: 0.17-0
go2rtc:
  streams:
    cam_1_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.101:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_1_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.101:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_2_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.102:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_2_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.102:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_3_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.103:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_3_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.103:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_4_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.104:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_4_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.104:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_5_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.105:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_5_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.105:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_6_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.106:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_6_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.106:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_7_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.107:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_7_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.107:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_8_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.108:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_8_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.108:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
    cam_9_1:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.109:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
    cam_9_2:
      - rtsp://admin:Stel13DeternetArea1()@10.131.2.109:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
notifications:
  enabled: true
  email: dp@p2pnode.org

 

1.5.15 Frigate NVR

02. Configure Cameras

Camera Network

The first thing to be made sure of is to create a dedicated network for your camera system. This can either be dedicated or VLAN based. In this way you can separate Camera traffic from other traffic and if your camera netwok gets breached than all other services will not be affected. Depending on your network setup you can either define this VLAN directly on an L3 swich or if you use the router on a stick model on your router. Hence, make sure all cameras are on the same CAM network.

Initial Camera Setup

Now, by default your cameras are not yet living on the camera network VLAN that we just created. Now, for each brand and camera it is different but it basically boils down to this.

  1. Make sure you have a laptop with an ethernet port
  2. Look up on the internet how you can reset your particular camera to its default settings.
  3. This will most likely also tell you what the default IP address is that particular camera e.g Amcrest cameras live on 192.168.1.108.
  4. If you know that then you also know on which network it lives.
  5. Next, we need to set our laptop to the same default network as the camera.
  6. Then we can simply access the camera by typing in the address of the camera in our favourite webbrowser.
  7. Then again depending on your camera, go to the networking page to give your camera a static IP address on the Camera Network.
  8. Note! Once you change the ip address of the camera then you can only keep accessing it if you bring your laptop on that network as well.
  9. After you have done that you can stick the IP camera in an untagged CAM Network port on your PoE switch.
  10. If you can then connect to it with your laptop by connected it as well to the same switch then you know it works!
  11. Repeat this process until you brought all the cameras on the camera network.

Streams

Next up are the camera stream possibilities of your camera. Most cameras come at least with two camera streams. These streams are basically recording the same thing but on different resolution scales. For instance, for your recordings you would love to have the highest resolution possible, howerver for your object and sound detection you don't need that. This can greatly reduce the stress on your resources and hence provide a much smoother experience, especially on systems that are not so well resourced.

Other that we also need to know or set the resolution of all of the cameras. The reason being is that these resolutions should be the same as in our Frigate configuration file later on. It is all up to you and the resources but some common resolutions would be

  1. 640x480 (VGA) for substream (The higher the sream the more stress will be put on your resources)
  2. Highest resolution for main stream (e.g 1920x1080 or 2960x1668)

Make sure you write these numbers down and have set them all the same for all your cameras.

NTP Time Server

An NTP server (Network Time Protocol server) is a server that provides accurate time information to clients over the Internet, allowing them to synchronize their clocks to Coordinated Universal Time (UTC) with high precision. NTP servers can maintain time accuracy within a few milliseconds over local networks and tens of milliseconds over the public Internet. 

Now why would you want this ? Well you want that each camera in your system displays the same time i.e that all clocks are synchronized. They even synchronize with summer and winter times ;) This is a must have instead of cameras showing different times. 

Now, which NTP servers to use then ? Well I would recommend to use the NTP servers from the NTP Pool Project. The pool.ntp.org project is a big virtual cluster of timeservers providing reliable, easy to use NTP service for millions of clients. You could click on your continent region and then use any of the time servers they provide. For instance clicking on Europe you get the following addresses

server 0.europe.pool.ntp.org
server 1.europe.pool.ntp.org
server 2.europe.pool.ntp.org
server 3.europe.pool.ntp.org

You can use any of these 4 and implement them as the NTP server for each camera. Make sure to use the same s.t they all use the same time servers and are synchronized. 

Viewing Preferences

Now, depnding on the camera you have obtained they all have different possibilities. It is now then time to go to the cameras and tweak it to your liking. For instance, if you prefer night vision with colour on your camera's then you can set that setting for your camera. I would highly recommend to do this ;)

 

1.5.15 Frigate NVR

01. Hardware Pass Through

To make frigate operate smoothly and effectively a GPU or XXX should be used for hardware acceleration. Otherwise all the image recognition and playback needs to be handled by the CPU which is really heavy. If you don't do this and you have a bunch of cameras then it can easily consume 8 cpu cores if not more. Hence, get a GPU or Google XXX device.

  1. Inside of the BIOS of your system go to

Tutorial on Hardware Acceleration for Frigate: https://www.youtube.com/watch?v=SeHgsIwyOuY

 

1. Then Resizable BAR needs to be turned on in the BIOS of your system if it's available.

2. Then go int 

1.5.15 Frigate NVR

3. Mount NFS Share

04. Mount NFS Share to Linux VM

  1. Create dataset
  2. Data space management -> set it to x TiB
  3. Shares -> Unix -> Add

!!!! Mount should happen at root !!!!!

Now, that we have created an NFS share it is time to share it with another machine virtual or bare metal. In this case we will install inside a linux VM

To test if a TrueNas NFS share can be mounted use the following command:

sudo mount -t nfs 10.131.1.10:/mnt/alexandria/frigate /home/dpasnfrigate/media
sudo mount -t nfs 10.131.1.10:/mnt/alexandria/frigate /home/dpasnfrigate/media

Keep NFS share Mounted on Reboots

Generally, you will want to mount the remote NFS directory automatically when the system boots.

The /etc/fstab file contains a list of entries that define where how and what filesystem will be mounted on system startup.

To automatically mount an NFS share when your Linux system starts up add a line to the /etc/fstab file. The line must include the hostname or the IP address of the NFS server, the exported directory, and the mount point on the local machine.

Use the following procedure to automatically mount an NFS share on Linux systems:

  1. Set up a mount point for the remote NFS share:
sudo mkdir /frigate/media
sudo mkdir /frigate/media
  1. Open the /etc/fstab file with your text editor :
sudo nano /etc/fstab
sudo nano /etc/fstab
  1. Add the following line
10.131.2.10:/mnt/alexandria/frigate frigate/media  nfs defaults 0 0
10.131.2.10:/mnt/alexandria/frigate frigate/media  nfs defaults 0 0
  1. it
  2. Finally you need to update the new file with
systemctl daemon-reload
systemctl daemon-reload
  1. Run the mount command in one of the following forms to mount the NFS share:
mount frigate/media
mount frigate/media
  1. The mount command, will read the content of the /etc/fstab and mount the share.
  2. Next time you reboot the system the NFS share will be mounted automatically.

admin
60ab1406fba90830c0126bbdcae06743d54a4f62d76f1e75

sudo apt install gnome-software-plugin-flatpak

1.5.16 OpenLift

1.5.17 Arr Stack

In this chapter we follow roughly the guidelines from Techhut Instruction Video and the Trash Guides guide. However, there are other methods as well of course.

1.5.17 Arr Stack

01. Architectural Overview

Screenshot From 2026-06-15 17-51-08.png

Movie/Show Request
  1. A user makes a request via Seerr, which is basically a catalogue of all the existing movies and tv shows and upcoming ones. This is where the end user will interact with for requests. Everything else from 2-4 is not publicly accessible. Only 1 and 5 are in the whole process.
  2. Then the request will end up in Radarr for movies and Sonarr for TV shows.
  3. Radarr and Sonarr are open-source applications designed for automating the downloading of movies and TV shows, respectively. They help users track their favorite media, automatically search for new episodes or films, and manage downloads through a user-friendly web interface.
Overview of the Request Process

Radarr/Sonarr  and Prowlarr work in tandem to automate the downloading of movies. Here’s how the process unfolds:

  1. Search Request: Radarr/Sonarr initiates a search request for a specific movie/show.
  2. Querying Indexers: Prowlarr receives this request and queries all its configured indexers.
  3. Returning Results: The results from the indexers are sent back to Prowlarr.
  4. Final Decision: Prowlarr then forwards these results back to Radarr/Sonarr, which makes the final decision on which movie/show/episode to download.
Download clients
  1. After the correct movie/show has been confirmed depending on if its a usenet or torrent file is then send to one of the download clients they are connected to.
  2. Examples of these download clients are nzbget for usenet and qbittorrent for torrents. 
  3. They go out and download the movies/shows to your hard drives
Front end Jellyfin Clients

List of all Jellyfin clients

Then finally you have multiple front ends that users can interact with to actually watch their movies and shows. Basically Jellyfin sits in the backend and any application that can speak to the Jellyfin API can represent this media how they seem fit. Famous ones I like are

  1. Fladder for Android Mobile  and Laptops (It allows Seerr to be integrated into one application)
  2. Jellyfin for TV
  3. Kodi with a sweet theme for the TV
  4. For iOS devices I would recommend 

1.5.17 Arr Stack

03. Mount NFS Share inside Arr VM

Now, that we have created an NFS share we should mount it inside the VM. But before we can mount it we need to install the NFS tools such that it can talk the NFS langauge. This is fairly straightforward with the following commands

1. Within the VM install cifs-utils

sudo apt install cifs-utils

2. Now, edit the fstab file and add the following lines editing them to match your information:

sudo nano /etc/fstab
3. Then in here you will specify your NFS share. It is fairly simple 
 
IP Address arr VM:/mnt/pool/dataset_name /mnt/data  nfs defaults 0 0

4. Once that is saved we restart the daemon and mount the file share

sudo systemctl daemon-reload
sudo mount -a

5. 

1.5.17 Arr Stack

02. Creating a NFS Share

Since this stack will save lots of big files i.e movies and tv shows we will create an NFS share for this on our TrueNas VM. You don't need to use TrueNas obviously to create an NFS share but this guide uses TrueNas and hence we do that ;)

1. First we will create a new dataset in TrueNas

2. Go to Datasets -> Click on Add Dataset

3. Give it a name you like

4. Leave the Dataset Preset to Generi

5. Create the dataset ;)

6. Once that is done we click on the dataset and we need to change its permissions

7. Edit the permissions and make sure that the User 1000 has the read write and Execute permissions on both the User and Group section. Leave all the other boxes for Other unchecked. 

8. Sweet! Then finally we will make an NFS share that our Arr Stack will utilize to download and store content on.

9. Click on Shares -> NFS -> Add

10. Then give it a name and choose the dataset for your Arr Stack.

11. For now you can leave the networks and hosts as it is. We can later restrict it by only allowing the Arr stack access to it but that's for later.

12. Cheers!! you made the Arr Stack NFS share !

Awesome, Let's now look at the folder structure ;) 

1.5.17 Arr Stack

04. Folder Structure

It's good practice to give all containers the same access to the same root directory or share. This is why all containers in the compose file have the bind volume mount /data:/data. It makes everything easier, plus passing in two volumes such as the commonly suggested /tv, /movies, and /downloads makes them look like two different file systems, even if they are a single file system outside the container. See my current setup below.

data
├── books
├── downloads
│   ├── qbittorrent
│   │   ├── completed
│   │   ├── incomplete
│   │   └── torrents
│   └── nzbget
│       ├── completed
│       ├── intermediate
│       ├── nzb
│       ├── queue
│       └── tmp
├── movies
├── music
├── shows
└── youtube

1. Now, to implement this we will move into our NFS share inside our VM and create the above folder structure with the command

mkdir -p downloads/qbittorrent/{completed,incomplete,torrents} && mkdir -p downloads/nzbget/{completed,intermediate,nzb,queue,tmp}

2. Perfect! You can check with "ls" if the folder structure was actually created correctly.

3. Now, to get the permissions right we need to move back into TrueNas to have the ownership of the folders recursively owned by user 1000 and group 1000. 

4. Go to TrueNas -> Datasets -> DATASET_NAME -> Click on Permissions

5. In here make sure that the 6 top blocks are all set and that you check the boxes

6. Then accept the Warning and save the permission. Sweet! now you are ready to start setting up the docker compose file and can be sure that all the containers have the proper permissions to all the folders. 

 

1.5.17 Arr Stack

05. Creating the Docker Compose file

Now, that the NFS share and folder structure  is created and added to the Virtual machine with /etc/fstab we can start creating the docker compose file and its environment ".env" file. It must be said that this docker compose file is basically a copy paste from the compose file created by home lab teacher Techhut. You can find his compose file here. In this docker page I will first explain all the different components and what they do. However, you can also directly copy and paste docker compose and environment file if you want of course. 

Docker Compose File
networks:
  servarrnetwork:
    name: servarrnetwork
    ipam:
      config:
        - subnet: 172.39.0.0/24
services:
  gluetun:
    image: qmcgaw/gluetun
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun # If running on an LXC see readme for more info.
    networks:
      servarrnetwork:
        ipv4_address: ${SET_IP_GLUETUN}
    ports:
      - 8080:8080 # qbittorrent web interface
      - 6881:6881 # qbittorrent torrent port
      - 6789:6789 # nzbget
      - 9696:9696 # prowlarr
      - 8191:8191 # flaresolverr
    volumes:
      - ./gluetun:/gluetun
    # Make a '.env' file in the same directory.
    env_file:
      - .env
    healthcheck:
      test: ping -c 1 www.google.com || exit 1
      interval: 20s
      timeout: 10s
      retries: 5
    restart: unless-stopped
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    restart: unless-stopped
    labels:
      - deunhealth.restart.on.unhealthy=true
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - WEBUI_PORT=8080 # must match "qbittorrent web interface" port number in gluetun's service above
      - TORRENTING_PORT=${FIREWALL_VPN_INPUT_PORTS} # airvpn forwarded port, pulled from .env
    volumes:
      - ./qbittorrent:/config
      - ${ROOT_DIR}:/data
    depends_on:
      gluetun:
        condition: service_healthy
        restart: true
    network_mode: service:gluetun
    healthcheck:
      test: ping -c 1 www.google.com || exit 1
      interval: 60s
      retries: 3
      start_period: 20s
      timeout: 10s
  # See the 'qBittorrent Stalls with VPN Timeout' section for more information.
  deunhealth:
    image: qmcgaw/deunhealth
    container_name: deunhealth
    network_mode: none
    environment:
      - LOG_LEVEL=info
      - HEALTH_SERVER_ADDRESS=127.0.0.1:9999
      - TZ=${TZ}
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
  nzbget:
    image: lscr.io/linuxserver/nzbget:latest
    container_name: nzbget
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./nzbget:/config
      - ${ROOT_DIR}:/data
    depends_on:
      gluetun:
        condition: service_healthy
        restart: true
    restart: unless-stopped
    network_mode: service:gluetun
  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./prowlarr:/config
    restart: unless-stopped
    depends_on:
      gluetun:
        condition: service_healthy
        restart: true
    network_mode: service:gluetun
  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    environment:
      - LOG_LEVEL=${LOG_LEVEL:-info}
      - LOG_HTML=${LOG_HTML:-false}
      - CAPTCHA_SOLVER=${CAPTCHA_SOLVER:-none}
      - TZ=${TZ}
    depends_on:
      gluetun:
        condition: service_healthy
        restart: true
    network_mode: service:gluetun
    restart: unless-stopped
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    restart: unless-stopped
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./sonarr:/config
      - ${ROOT_DIR}:/data
    ports:
      - 8989:8989
    networks:
      servarrnetwork:
        ipv4_address: ${SET_IP_SONARR}
  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    restart: unless-stopped
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./radarr:/config
      - ${ROOT_DIR}:/data
    ports:
      - 7878:7878
    networks:
      servarrnetwork:
        ipv4_address: ${SET_IP_RADARR}
  lidarr:
    container_name: lidarr
    image: lscr.io/linuxserver/lidarr:latest
    restart: unless-stopped
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./lidarr:/config
      - ${ROOT_DIR}:/data
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    ports:
      - 8686:8686
    networks:
      servarrnetwork:
        ipv4_address: ${SET_IP_LIDARR}
  bazarr:
    image: lscr.io/linuxserver/bazarr:latest
    container_name: bazarr
    restart: unless-stopped
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./bazarr:/config
      - ${ROOT_DIR}:/data
    ports:
      - 6767:6767
    networks:
      servarrnetwork:
        ipv4_address: ${SET_IP_BAZARR}
  seerr:
    container_name: seerr
    image: ghcr.io/seerr-team/seerr:latest
    environment:
      - LOG_LEVEL=debug
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    healthcheck:
      test: wget --no-verbose --tries=1 --spider http://localhost:5055/api/v1/status
        || exit 1
      start_period: 20s
      timeout: 3s
      interval: 15s
      retries: 3
    restart: unless-stopped
    volumes:
      - ./seerr:/app/config
    ports:
      - 5055:5055
    networks:
      servarrnetwork:
        ipv4_address: ${SET_IP_SEERR}
  # jellystat-db:
  #   image: postgres:18.1
  #   container_name: jellystat-db
  #   restart: unless-stopped
  #   shm_size: '1gb'
  #   environment:
  #     - POSTGRES_USER=${JELLYSTAT_POSTGRES_USER}
  #     - POSTGRES_PASSWORD=${JELLYSTAT_POSTGRES_PASSWORD}
  #   volumes:
  #     - ./jellystat/postgres-data:/var/lib/postgresql/data
  #   healthcheck:
  #     test: ["CMD-SHELL", "pg_isready --dbname=postgres --username=${JELLYSTAT_POSTGRES_USER}"]
  #     interval: 10s
  #     timeout: 5s
  #     retries: 5
  #   networks:
  #     servarrnetwork:
  #       ipv4_address: ${SET_IP_JELLYSTAT_DB}
  #
  # jellystat:
  #   image: cyfershepard/jellystat:latest
  #   container_name: jellystat
  #   restart: unless-stopped
  #   environment:
  #     - POSTGRES_USER=${JELLYSTAT_POSTGRES_USER}
  #     - POSTGRES_PASSWORD=${JELLYSTAT_POSTGRES_PASSWORD}
  #     - POSTGRES_IP=jellystat-db
  #     - POSTGRES_PORT=5432
  #     - JWT_SECRET=${JELLYSTAT_JWT_SECRET}
  #     - TZ=${TZ}
  #   volumes:
  #     - ./jellystat/backup-data:/app/backend/backup-data
  #   ports:
  #     - 3000:3000
  #   depends_on:
  #     jellystat-db:
  #       condition: service_healthy
  #   healthcheck:
  #     test: wget --no-verbose --tries=1 --spider http://localhost:3000/auth/isConfigured || exit 1
  #     interval: 60s
  #     timeout: 30s
  #     retries: 5
  #     start_period: 30s
  #   networks:
  #     servarrnetwork:
  #       ipv4_address: ${SET_IP_JELLYSTAT}

 

Environment Variables
# General UID/GID and Timezone
TZ=America/Los_Angeles
PUID=1000
PGID=1000

# Input your VPN provider and type here
VPN_SERVICE_PROVIDER=airvpn
VPN_TYPE=wireguard

# Mandatory, airvpn forwarded port
FIREWALL_VPN_INPUT_PORTS=port

# Copy all these variables from your generated configuration file
WIREGUARD_PUBLIC_KEY=key
WIREGUARD_PRIVATE_KEY=key
WIREGUARD_PRESHARED_KEY=key
WIREGUARD_ADDRESSES=ip

# Optional location variables, comma separated list, no spaces after commas, make sure it matches the config you created
# NOTE: These can cause connection failures with some providers. Remove or comment out if Gluetun won't connect.
#SERVER_COUNTRIES=country
#SERVER_CITIES=city

# Health check duration
HEALTH_VPN_DURATION_INITIAL=120s

# Static IPs for services on servarrnetwork
SET_IP_GLUETUN=172.39.0.2
SET_IP_SONARR=172.39.0.3
SET_IP_RADARR=172.39.0.4
SET_IP_LIDARR=172.39.0.5
SET_IP_BAZARR=172.39.0.6
SET_IP_SEERR=172.39.0.7
SET_IP_JELLYSTAT_DB=172.39.0.8
SET_IP_JELLYSTAT=172.39.0.9

# Jellystat (uncomment service block in compose.yaml to enable)
JELLYSTAT_POSTGRES_USER=postgres
JELLYSTAT_POSTGRES_PASSWORD=changeme
JELLYSTAT_JWT_SECRET=changeme

 

Network
  1. First of all an internal docker network is created called "servarrnetwork"
  2. Then we give this network as well a subnet in a /24. This can be any private subnet you please since it is only internal

Gluetun

So what does this docker compose file represent ? What is the workflow here ?

1.5.17 Arr Stack

06. qBittorent

In this section we will setup our qBittorrent downloading client.

1.  First get your temporary password that is provided to login which you can find in the logs. Use the command

sudo docker logs qbittorrent
Example output

[migrations] started
[migrations] no migrations found
───────────────────────────────────────

      ██╗     ███████╗██╗ ██████╗
      ██║     ██╔════╝██║██╔═══██╗
      ██║     ███████╗██║██║   ██║
      ██║     ╚════██║██║██║   ██║
      ███████╗███████║██║╚██████╔╝
      ╚══════╝╚══════╝╚═╝ ╚═════╝

   Brought to you by linuxserver.io
───────────────────────────────────────

To support LSIO projects visit:
https://www.linuxserver.io/donate/

───────────────────────────────────────
GID/UID
───────────────────────────────────────

User UID:    1000
User GID:    1000
───────────────────────────────────────
Linuxserver.io version: 5.2.1_v2.0.13-ls460
Build-date: 2026-06-08T23:02:32+00:00
───────────────────────────────────────
    
[custom-init] No custom files found, skipping...
WebUI will be started shortly after internal preparations. Please wait...

******** Information ********
To control qBittorrent, access the WebUI at: http://localhost:8080
The WebUI administrator username is: admin
The WebUI administrator password was not set. A temporary password is provided for this session: yfUHUIHFX
You should set your own password in program preferences.
Connection to localhost (::1) 8080 port [tcp/http-alt] succeeded!

2.  Then go to IP address:8080 and login with "admin" and your temporary password
3.  Once logged in you can change your password and username by going to Settings > WebUI

4. 

 


5.  What's next is setting up the directory scheme for the torrent files
6.  Go to Settings > Downloads
7.  Then Look for

Default save path: /data/torrents/completed  
Keep incomplete torrents in: /data/torrents/incomplete  
Copy torrent files to: /data/torrents/torrents

8.  Congratz now your qBittorent client is all set

1.5.17 Arr Stack

07. NZBGet


For a Usenet downloading client we choose NZBget. Yes this is not the only one out there but it is leightweight and efficient. If your container is up and running you can find your NZB instance at

IP address VM:6789

  1. Now, the first time that you will login you will need the standard login credentials which are

Default credentials:

Username: nzbget
Password: tegbzn6789

  1. As with any platform we first change the user credentials. Go to Settings > Security > Change username and Password

Download Folders

  1. Next we will setup up the download folders nzbget will use for its downloads. We already configured them in our folder structure so that will be a piece of cake
  2. For this go to Settings > Paths

In here we will specify

Main Dir: /data/usenet/
DestDir: /data/usenet/completed
InterDir: /data/usenet/incompleted

  1. This is again according to the Trash Guide folder structure we created in the beginning. If you then hit Save and Apply it should give you no errors. If nzbget cannot reach the folders for writing permissions or they don't exist then it will tell you with a red banner.

News-Server

  1. Now, we will setup our new server. It is up to you which news server you choose however regardless you will need 3 items. Your username, password and the host URL. If you have obtained these credentials by login in to you news reader then you can go to Settings > News-Server
  1. By hitting save and apply everything should be good if you did not receive any errors.
  2. Aweseom then your downloading clients are set and we can move on

Categories

Now, we have created the folders movies, tv, books and music inside the completed usenet folder. We now need to tell nzbget about these locations through the means of categories.

  1. Go to Settings > Categories
  2. Add the Categories movies, tv, books and music. Make sure it is all in lower case ;)
NZBGET Health

Health
A decreasing health for a movie in NZBGet typically indicates that the files are becoming less available or are missing parts, making it difficult to download them successfully. This can happen due to content takedowns or the age of the files, leading to failed downloads

Extraction
Files need to be extracted from .rar archives in NZBGet because they are compressed to save space and facilitate faster downloads. Extracting them allows you to access the original files contained within the archive

1.5.17 Arr Stack

08. Radarr


1. Create Account

Now, that we have setup our Download clients i.e qBittorent and NZBGet we will configure Radarr, Sonarr, Lidarr, Bazaarr. However, we will only demonstrate one of the 4 since the configuration is exactly the same

  1. Go to your Radarr's IP address :7878
  2. Once, there you will start making an account. For this you can again use Bitwarden to create random login credentials for your instance

2. Media Management

  1. in this step we will assign the folders in which the radarr files will reside
  2. Go to Settings > Media Management > Add root folder
  3. Here you will choose the movie folder you have created in your docker compose file. If you follow this guide this would be at /data/media/Movies
  4. Congratulations, now you have told Radarr where to store all the movies.

3. Downloading Clients

Now we will connect the downloading clients qBittorent and nzbget to Radarr for it to use.

  1. Go to Settings > Downloading Clients > + add
  2. Select qBittorent and fill in the credentials of your qBittorent downloading client
  3. Once done, add another client but this time choose for nzbget.
  4. Also fill in here the credentials of your nzbget client with the category movies
  5. Awesome you are all set !

4. Indexers

The final thing we need to setup for our Radarr instance are indexers. However, we will use Prowlarr for this as a centralised indexer for all ARR apps. The only thing we need for Prowlarr to
connect to Radarr is it's API key.

  1. Go to Settings > General
  2. Copy paste the API key
1.5.17 Arr Stack

09. Sonarr


1. Create Account

For Sonarr it will be quite similar as for Radarr

  1. Go to your Radarr's IP address:8989
  2. Once, there you will start making an account. For this you can again use Bitwarden to create random login credentials for your instance

2. Media Management

  1. in this step we will assign the folders in which the radarr files will reside
  2. Go to Settings > Media Management > Add root folder
  3. Here you will choose the movie folder you have created in your docker compose file. If you follow this guide this would be at /data/media/tvshows
  4. Congratulations, now you have told Radarr where to store all the movies.

3. Downloading Clients

Now we will connect the downloading clients qBittorent and nzbget to Radarr for it to use.

  1. Go to Settings > Downloading Clients > + add
  2. Select qBittorent and fill in the credentials of your qBittorent downloading client
  3. Once done, add another client but this time choose for nzbget.
  4. Also fill in here the credentials of your nzbget client with the category "tv"
  5. Awesome you are all set !

4. Indexers

The final thing we need to setup for our Radarr instance are indexers. However, we will use Prowlarr for this as a centralised indexer for all ARR apps. The only thing we need for Prowlarr to
connect to Radarr is it's API key.

  1. Go to Settings > General
  2. Copy paste the API key
1.5.17 Arr Stack

10. Prowlarr

So Prowlarr is an indexer manager and proxy that integrates with various PVR (Personal Video Recorder) applications, allowing users to manage both Torrent Trackers and Usenet Indexers seamlessly. It works with applications like Radarr, Sonarr, and Lidarr to automate the process of finding and downloading media content. In other words it basically tells all the other ARR applications about the indexers that are available to use to find media content.

Prowlarr Apps

  1. Go to Radarr and obtain its API key at Settings > General > API key
  2. Then open up Prowlarr at VMIP:9696
  3. Go to Settings > Apps > + Icon
  4. Then make sure you fill in the subnet IP you created for the servarrnetwork. For instance if your servarrnetwork is 172.44.0.1/24 then your subnet IP will be 172.44.0.1

Prowlar Indexers

Setting up the nzbgeek indexer in Prowlarr

1.5.17 Arr Stack

11. Profillar

The next ARR app we will install is profillar. This one, is just really important since it allow you to filter what type and size of content you want all don automatically. If you don't have that much storage space you would not want to download 4K content all the time for instance. On top of that it is hard to see the difference between 4K and 1080p anwyays, even 720p does well on smaller screens. Personally I download everything in 1080p or 720p if I don't care about it to much. The 4K is really not visible on my TV anyways. It gives you just great control over your quality and storage space you have access to.

  1. Go to VMIP:6868
  2. Create an account
  3. The first thing we will do just as with Prowlarr is connect it to Radarr and Sonarr.
  4. Grab your API keys from both apps again
  5. Go to Settings > External Apps > Add Radar and then add Sonarr. The IP address for both Sonarr and Radarr is your VMIP

Quality Profiles

  1. After this we need to add a quality profile repository to our profillar app. There are many different repositories but the one that seems used most often is the following
    https://github.com/Dictionarry-Hub/database
  2. Then go to

Media Management

  1. Go back to Radarr and Sonarr
  2. Go to General > Media Management > Click on Advanced in the left top corner
  3. Then scroll down to Propers and Repacks and select "DO NOT PREFER"
1.5.17 Arr Stack

12. Seerr

1.5.17 Arr Stack

13. Bazaarr

1.5.17 Arr Stack

Jellyfin Clients

🌎 Browser-Based

Name OSS Free Paid Downloads
ampcast ⚒️ 🎵 github
Aperture ⚒️ github
Feishin ⚒️ 🎵 github
Finetic github
Fladder github Google Play flathub
Jelly Music App 🎵 github
Jellyfin Vue 🔹 ⚒️ github Demo
Jellyfin Web 🔹 github
Jellyflix Demo App Store github
Moonfin github github github Google Play App Store
Pelagica github Demo
Preserve ⚒️ 🎵 GitLab Demo
Shadfin ⚒️ github Demo
sonixd ⚒️ 🎵 github

💻 Desktop

Windows

Name OSS Free Paid Downloads
ampcast ⚒️ 🎵 github
Blink ⚒️ github
Bloodin ⚒️
Feishin ⚒️ 🎵 github
Fladder github Google Play flathub
FluentFin ⚒️ github
Foxy 🎵 github
Hills ☑️ Google Play Microsoft Store
iPlay github App Store Amazon Microsoft Store
Jelly Music App 🎵 github
Jellyamp 🎵 github
Jellyfin Media Player 🔹 github flathub
Jellyfin MPV Shim 🔹 github flathub
Jellyflix Demo App Store github
Moonfin github github github Google Play App Store
Playtorrio github
Preserve ⚒️ 🎵 GitLab Demo
Rodel Player Microsoft Store
Sonixd ⚒️ 🎵 github
Supersonic ⚒️ 🎵 github flathub
Switchfin ⚒️ github
Tauon Music Box 🎵 github flathub
tsukimi ⚒️ github

macOS

Name OSS Free Paid Downloads
ampcast ⚒️ 🎵 github
Blink ⚒️ github
Bloodin ⚒️
Coppelia ⚒️ 🎵 github
Dee Jellie 🎵 ☑️ App Store
Discrete 🎵 ☑️ App Store
Feishin ⚒️ 🎵 github
Finer 🎵 App Store
Fladder github Google Play flathub
Foxy 🎵 github
HamHub App Store
Infuse ☑️ App Store
iPlay github App Store Amazon Microsoft Store
Jelly Music App 🎵 github
Jellyfin Media Player 🔹 github flathub
Jellyfin MPV Shim 🔹 github flathub
Jellyflix Demo App Store github
Moonfin github github github Google Play App Store
Phyn ☑️ Google Play App Store
Playtorrio github
SenPlayer ☑️ App Store
Sonixd ⚒️ 🎵 github
Supersonic ⚒️ 🎵 github flathub
Switchfin ⚒️ github
VidHub ☑️ Google Play App Store

Linux

Name OSS Free Paid Downloads
ampcast ⚒️ 🎵 github
Blink ⚒️ github
Bloodin ⚒️
Coppelia ⚒️ 🎵 github
Delfin ⚒️ flathub
Feishin ⚒️ 🎵 github
Fladder github Google Play flathub
Foxy 🎵 github
Gelly ⚒️ 🎵 github flathub
Jelly Music App 🎵 github
Jellyamp 🎵 github
Jellyfin Media Player 🔹 github flathub
Jellyfin MPV Shim 🔹 github flathub
Jellyflix Demo App Store github
Moonfin github github github Google Play App Store
Playtorrio github
Preserve ⚒️ 🎵 GitLab Demo
Sonixd ⚒️ 🎵 github
Supersonic ⚒️ 🎵 github flathub
Switchfin ⚒️ github
Tauon Music Box 🎵 github flathub
tsukimi ⚒️ github

📱 Mobile

iOS

Name OSS Free Paid Downloads
Coppelia ⚒️ 🎵 github
Dee Jellie 🎵 ☑️ App Store
Discrete 🎵 ☑️ App Store
Filebar ☑️ App Store
Finamp 🎵 Google Play App Store
Finer 🎵 App Store
Fintunes 🎵 github F-Droid Google Play App Store
Fladder github Google Play flathub
HamHub App Store
Infuse ☑️ App Store
iPlay github App Store Amazon Microsoft Store
Jellify 🎵 github TestFlight
JellyBook 📖 github Izzysoft TestFlight
Jellyfin Mobile for iOS 🔹 App Store
Jellyflix Demo App Store github
JellyTV ☑️ App Store
Manet 🎵 App Store
Moonfin github github github Google Play App Store
Phyn ☑️ Google Play App Store
Plappa 🎵 App Store
SenPlayer ☑️ App Store
Streamyfin App Store Google Play github
Sunkfin App Store
Swiftfin 🔹 ⚒️ App Store
VidHub ☑️ Google Play App Store
yybx ☑️ App Store

Android

Name OSS Free Paid Downloads
AFinity ⚒️ github
AfuseKt ☑️ github
Coppelia ⚒️ 🎵 github
Finamp 🎵 Google Play App Store
Findroid ⚒️ Izzysoft Google Play F-Droid
Fintunes 🎵 github F-Droid Google Play App Store
Fladder github Google Play flathub
Gelli 🎵 github F-Droid
Ghosten-Player github
Hills ☑️ Google Play Microsoft Store
Jellify 🎵 github TestFlight
JellyBook 📖 github Izzysoft TestFlight
JellyCine github
Jellyfin for Android 🔹 F-Droid Amazon Google Play
Jellyflix Demo App Store github
Moonfin github github github Google Play App Store
Phyn ☑️ Google Play App Store
Playtorrio github
Reefin Google Play github
Streamyfin App Store Google Play github
Symfonium ⚒️ 🎵 ☑️ Google Play
VidHub ☑️ Google Play App Store
Void ⚒️ github github
Yamby ☑️ Google Play

Sailfish OS

Name OSS Free Paid Downloads
Sailfin OpenRepos

HarmonyOS

Name OSS Free Paid Downloads
FinMusic 🎵 AppGallery github
FinVideo github AppGallery

Nintendo Switch

Name OSS Free Paid Downloads
Switchfin ⚒️ github

📺 TV

Apple TV

Name OSS Free Paid Downloads
Dee Jellie 🎵 ☑️ App Store
Filebar ☑️ App Store
HamHub App Store
Infuse ☑️ App Store
JellySee ☑️ App Store
Mediora App Store
Moonfin github github github Google Play App Store
MrMC ☑️ Amazon App Store Google Play
SenPlayer ☑️ App Store
Swiftfin 🔹 ⚒️ App Store
VidHub ☑️ Google Play App Store
yybx ☑️ App Store

Android TV

Name OSS Free Paid Downloads
AfuseKt TV ☑️ github
DUNE ⚒️ github
Elefin github
Fladder github Google Play flathub
Ghosten-Player github
Jellyfin for Android TV 🔹 Amazon Google Play
Moonfin github github github Google Play App Store
Reefin Google Play github
VidHub ☑️ Google Play App Store
Void ⚒️ github github
Wholphin Google Play github
Yamby ☑️ Google Play

Kodi

Name OSS Free Paid Downloads
JellyCon 🔹 github
Jellyfin for Kodi 🔹 github

Roku

Name OSS Free Paid Downloads
Jellyfin for Roku 🔹 Roku
JellyRock Roku github
Moonfin github github github Google Play App Store

Tizen

Name OSS Free Paid Downloads
Jellyfin for Tizen 🔹 Build It Yourself
Litefin github
Moonfin github github github Google Play App Store

webOS

Name OSS Free Paid Downloads
Breezyfin ⚒️ github
Jellyfin for WebOS 🔹 LG
Litefin github
Moonfin github github github Google Play App Store

By Type

🎵 Music Client

Name OSS Free Paid Downloads
ampcast ⚒️ 🎵 github
Coppelia ⚒️ 🎵 github
Dee Jellie 🎵 ☑️ App Store
Discrete 🎵 ☑️ App Store
Feishin ⚒️ 🎵 github
Finamp 🎵 Google Play App Store
Finer 🎵 App Store
FinMusic 🎵 AppGallery github
Fintunes 🎵 github F-Droid Google Play App Store
Foxy 🎵 github
Gelli 🎵 github F-Droid
Gelly ⚒️ 🎵 github flathub
Jellify 🎵 github TestFlight
Jelly Music App 🎵 github
Jellyamp 🎵 github
Manet 🎵 App Store
Plappa 🎵 App Store
Preserve ⚒️ 🎵 GitLab Demo
Sonixd ⚒️ 🎵 github
sonixd ⚒️ 🎵 github
Supersonic ⚒️ 🎵 github flathub
Symfonium ⚒️ 🎵 ☑️ Google Play
Tauon Music Box 🎵 github flathub

📖 Reader

Name OSS Free Paid Downloads
JellyBook 📖 github Izzysoft TestFlight


[!NOTE] Please note that this Markdown file is automatically generated from the clients.yaml file. Contributions should only be made by updating the clients.yaml file. Any updates to the .yaml file will result in this Markdown file being re-generated to reflect those changes.

1.5.17 Arr Stack

14. Jellyfin Themes

List of Jellyfin Themes'

ElegantFin: https://lscambo13.github.io/ElegantFin/

CSS: 

@import url("https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@main/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css");

1.5.17 Arr Stack

15. Jellyfin Plugins

banner.png
A List of Awesome Jellyfin Themes and Plugins

[ PluginsCompanion Apps & ToolsGuidesSnippetsRelatedContribute ]

🖌️ Themes

Browse the collection of community themes

📺 Clients

Browse available clients for all platforms

🧩 Plugins

🎨 UI & Customization

📂 Collections & Playlists

▶️ Playback

🔗 Integration & Sync

🔔 Notifications

🔐 Authentication

📚 Library Management

🏷️ Metadata Providers

👾 Companion Apps & Tools

🖼️ Artwork & Posters

🔍 Media Requests & Discovery

📊 Statistics & Watch History

👥 User Management

📁 Media Organization

💬 Subtitles

🎵 Music

📥 Content Import

🔧 Server Administration

🧰 Miscellaneous

📜 Snippets

📖 Guides

This section includes software, guides and tools that are not specifically designed to work with Jellyfin, but can be useful for tasks related to media management or to enhance Jellyfin's functionality.

Communities

This section contains links to communities which focus on Jellyfin or related topics.

Contribute

Contributions welcome! But please read the contribution guidelines first. You can also create a new issue.

1.5.17 Arr Stack

16. Jellyfin Enhanced

 

 

Reboot: It is important that after installing Jellyfin Enhanced that you reboot the whole Jellyfin stack. Just a simple reboot from the dashboard screen won't work. Hence, go to Dockhand and reboot the whole stack, from the cli or any other docker configuration software that you are using. After the reboot you will notice that at least a random item button will appear in the right top corner

 

1.5.18 Cal.com

1.5.19 Kurier

1.5.20 ArchiveTune Music

Source: https://github.com/ArchiveTuneApp/ArchiveTune

This is not a self hosted app, but it is perfect to replace spotify and itunes with.

1.5.21 OpenCut

Source: https://opencut.app/roadmap

1.5.22 Sure Finance

Sure is an all-in-one personal finance platform. Track, optimize, grow, and manage your money through every stage of life.

1.5.22 Sure Finance

01. What is Sure Finance ?

Track your bank accounts bank logos , check how your stocks stock logos and crypto crypto logos are doing, and monitor your everything else credit card logos in any currency currency logos all in one place.

Alright! Well yeah we still live in a capitalistic world and hence our finances have a big impact on our lives and the time we can spend on open source projects ;) Hence, a great open source project called Sure can help you organizing your financial life. It basically handles all your assets in one central place. That includes values of all your assets, cash and bank accounts. This can be interestingly handy if you want to have one glass that displays everything instead of having multiple apps, accounts and dashboards you need to go through to keep track of all your finances. It can both be used for families and personal accounts but as well for small businesses.


1.5.22 Sure Finance

02. Installation

Sure provides a great installation page on their website as can be found over here. However, I would like to drop the docker compose file straight away. Also note that I like to keep all the volumes inside the same folder instead of all in docker volumes. Hence, this has been changed compared to the original docker compose file. We will deploy this again nicely in Dockhand with compose and .env file that can be found below

compose.yml
x-db-env: &db_env
  POSTGRES_USER: ${POSTGRES_USER:-sure_user}
  POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-sure_password}
  POSTGRES_DB: ${POSTGRES_DB:-sure_production}

x-rails-env: &rails_env
  <<: *db_env
  SECRET_KEY_BASE: ${SECRET_KEY_BASE:-a7523c3d0ae56415046ad8abae168d71074a79534a7062258f8d1d51ac2f76d3c3bc86d86b6b0b307df30d9a6a90a2066a3fa9e67c5e6f374dbd7dd4e0778e13}
  SELF_HOSTED: "true"
  RAILS_FORCE_SSL: "false"
  RAILS_ASSUME_SSL: "false"
  DB_HOST: db
  DB_PORT: 5432
  REDIS_URL: redis://redis:6379/1
  # NOTE: enabling OpenAI will incur costs when you use AI-related features in the app (chat, rules).  Make sure you have set appropriate spend limits on your account before adding this.
#  OPENAI_ACCESS_TOKEN: ${OPENAI_ACCESS_TOKEN}

services:
  web:
    image: ghcr.io/we-promise/sure:stable
    volumes:
      - ./app-storage:/rails/storage
    ports:
      - ${PORT:-3000}:3000
      # To also publish on IPv6 (dual-stack), uncomment the line below AND
      # set BINDING=:: in the environment block. See docs/hosting/docker.md
      # "Binding to IPv6" for details.
      # - "[::]:${PORT:-3000}:3000"
    restart: unless-stopped
    environment:
      <<: *rails_env
      # BINDING: "::"  # Uncomment for IPv6 dual-stack inside the container
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    dns:
      - 8.8.8.8
      - 1.1.1.1
    networks:
      - sure_net

  worker:
    image: ghcr.io/we-promise/sure:stable
    command: bundle exec sidekiq
    volumes:
      - ./app-storage:/rails/storage
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    dns:
      - 8.8.8.8
      - 1.1.1.1
    environment:
      <<: *rails_env
    networks:
      - sure_net

  db:
    image: postgres:16
    restart: unless-stopped
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    environment:
      <<: *db_env
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" ]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - sure_net

  backup:
    profiles:
      - backup
    image: prodrigestivill/postgres-backup-local
    restart: unless-stopped
    volumes:
      - ./backups:/backups # Change this path to your desired backup location on the host machine
    environment:
      - POSTGRES_HOST=db
      - POSTGRES_DB=${POSTGRES_DB:-sure_production}
      - POSTGRES_USER=${POSTGRES_USER:-sure_user}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-sure_password} # pipelock:ignore
      - SCHEDULE=@daily # Runs once a day at midnight
      - BACKUP_KEEP_DAYS=7 # Keeps the last 7 days of backups
      - BACKUP_KEEP_WEEKS=4 # Keeps 4 weekly backups
      - BACKUP_KEEP_MONTHS=6 # Keeps 6 monthly backups
    depends_on:
      - db
    networks:
      - sure_net

  redis:
    image: redis:latest
    restart: unless-stopped
    volumes:
      - ./redis-data:/data
    healthcheck:
      test: [ "CMD", "redis-cli", "ping" ]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - sure_net

networks:
  sure_net:
    driver: bridge

Then finally you can set your environment variables in the .env file, for the postgres and email details. Just make sure you create again a nice secret key with the command

openssl rand -hex 64

and then paste it into the .env file for the SECRET_KEY_BASE

.env
POSTGRES_USER=usernamedifficult
POSTGRES_PASSWORD=0394jgoipjeroigjoisdjgkljyourcode
POSTGRES_DB=sure_production
SECRET_KEY_BASE=a7523c3d0ae56415046ad8abae168d71074a79534a7062258f8d1d51ac2f76d3c3bc86d86b6b0b307df30d9a6a90a2066a3fa9e67c5e6f374dbd7dd4e0778e13
PORT=3000
SMTP_ADDRESS=smtp.example.com
SMTP_PORT=587
SMTP_DOMAIN=example.com
SMTP_USERNAME=your-username
SMTP_PASSWORD=your-password
SMTP_AUTHENTICATION=plain
SMTP_ENABLE_STARTTLS_AUTO=true

Note! Make sure that sure has the right writing permissions of 750 for the "sure" folder you are hosting everything in. Otherwise it cannot create a directory for your account.

Most likely you will get an error for because of sure not being able to write to the sure folder. In that case as noted above go into your folder and change the permissions and restart the sure stack. 

Then you will be able to reach your sure instance at DOCKERVMIP:Port and create a user account. Now, as with any application you should give it a domain name public or not. If you don't know how to do this check the Reverse Proxy section. 

Perfect! Now on the next pages we will discuss some basic settings and things you can do with it. However, just check the documentation for everything of course. 

1.5.23 Open Notebook

Open Notebook is the cognitive partner you always wanted and could never explain why. It combines the power of AI with unwavering privacy controls. It's designed for researchers, students, and professionals who want to enhance their learning and abilities while maintaining complete control over workflows, models, and how their data gets used and exposed.

1.5.23 Open Notebook

1. Installation

We will again use our Dockervm to deploy this application via docker. The installation instructions can be found on their Github page but here is the compose file regardless ;)

compose.yml
services:
  surrealdb:
    image: surrealdb/surrealdb:v2
    # Credentials default to root:root for a zero-config local setup. Before
    # exposing this instance to a network, set SURREAL_USER / SURREAL_PASSWORD
    # in a .env file (see .env.example) — they are applied here and to the
    # open_notebook service below, so the two always stay in sync.
    command: start --log info --user ${SURREAL_USER:-root} --pass ${SURREAL_PASSWORD:-root} rocksdb:/mydata/mydatabase.db
    user: root  # Required for bind mounts on Linux
    ports:
      - "8000:8000"
    volumes:
      - ./surreal_data:/mydata
    environment:
      - SURREAL_EXPERIMENTAL_GRAPHQL=true
    restart: always
    pull_policy: always

  open_notebook:
    image: lfnovo/open_notebook:v1-latest
    ports:
      - "8502:8502"  # Web UI
      - "5055:5055"  # REST API
    environment:
      # REQUIRED: Change this to your own secret string
      # This encrypts your API keys in the database
      - OPEN_NOTEBOOK_ENCRYPTION_KEY=change-me-to-a-secret-string

      # Database connection. SURREAL_USER / SURREAL_PASSWORD default to root:root
      # for local use; override them in a .env file before exposing the instance
      # (the same values configure the surrealdb service above).
      - SURREAL_URL=ws://surrealdb:8000/rpc
      - SURREAL_USER=${SURREAL_USER:-root}
      - SURREAL_PASSWORD=${SURREAL_PASSWORD:-root}
      - SURREAL_NAMESPACE=open_notebook
      - SURREAL_DATABASE=open_notebook
      - API_URL=http://localhost:5055
      - OPEN_NOTEBOOK_PASSWORD=${OPEN_NOTEBOOK_PASSWORD:-secure-webgui-password}
    volumes:
      - ./notebook_data:/app/data
    depends_on:
      - surrealdb
    restart: always
    pull_policy: always

Just copy and paste this in your dockhand instance as a new stack. Just makes sure you change the SURREAL_USER, SURREAL_PASSWORD and the OPEN_NOTEBOOK_PASSWORD to something more complicated than root and root. You can do this in the environment variables section in the Dockhand Stack

Then just deploy the stack and visit it at http://IP-DOCKERVM:8052

Attach Domain Name

Now, if you want to attach a domain name to your open notebook instance then you need to make a tiny addition to your docker compose file environment variables. Namely the API URL needs to match your domain name. If for instance your URL is opennotebook.example.org then the add the following line to the environment variables of the opennotebook container.

      - API_URL=https://opennotebook.example.org

1.5.23 Open Notebook

2. Configuration

1.5.24 Dawarich

Dawarich is a self-hostable web app designed to replace Google Timeline (aka Google Location History). It enables you to:

1.5.24 Dawarich

Owntracks/Traccar clients

https://tracks.rfeyn.org/api/v1/owntracks/points?api_key=18349285cbc1dcb8e68427175509225e3a244c9c6791f7bc8a9affe887f46f5f

Sadly enough they all require google play services to work 

 

1.5.24 Dawarich

1. Installation

This app is again extremely simple to install. 

 

 

 

What is important is that you set the IP of your DockerVM and the domain name you want to use to access the Dawarich instance in the APPLICATION_HOSTS= environment variable. Otherwise you won't be able to connect to it from any other machine then local host ! 

1.5.25 Ente Photos

1.5.25 Ente Photos

1. Ente vs Immich

Ente vs. Immich – a comparison

Both Ente and Immich are tools to manage images and videos, and both come with a self-hosting option. And even though they do the same thing in general, there are some fundamental differences in their implementation.

In this article, I explicitly compare them for self-hosting purposes and thus ignoring the cloud version of Ente explicitly. If you don’t want to self-host, Ente would be your way to go anyways.

Principles

Immich stores the files in is unencrypted form, making them still usable if the software itself breaks. Ente, on the other hand, encrypts all files. While this is generally a good thing, it makes you dependent on their implementation. And if the software breaks, it’s not that easy to get to the files. While in my case I only self-host locally, there’s no special need for an encryption for me.

Aside of that, Immich just stores the files in a directory you want it to, while Ente requires an actual S3 storage, which requires more administrative work.

This is also not a fully review between all functions of both systems. I just write about things I noticed while using them. It’s more an impression on how they behave.

Installation

Installing Immich is pretty straight forward and doesn’t really left any questions open. This is not the case for Ente and you need to understand how the particular parts of the system work together to get the system working. This is especially true for my TrueNAS Community system in this case, since Immich can be installed via a pre-defined app, while Ente needs to be installed manually.

Import

Since I wanted to import all of my photos and videos, both systems had something to do, as there are over 16.000 files to import. Immich tells me when files are excluded, but doesn’t tell me why. I assume it was because of duplicates by importing them via both on iOS and macOS.

None of the apps did it very smoothly, though. While the upload to Ente was way more faster, it also draw so much power and used so much compute resources that the iPhone was nearly unusable during this time. On macOS, it also used around 60 % CPU usage while importing.

Also, the iOS app stopped multiple times and finally crashed, often after 50 – 150 files. But sometimes, it also successfully uploaded more than 1.000 files in one try.

While not requiring that much resources in the Immich app, the upload was way slower. And it too crashed more often than it should.

In the end, in Ente it took me around 4 hours to import all the data, while Immich took me more than 24 hours.

Mobile iOS app

In short: The Ente app is superior most of the time. Syncing files in the background is faster, viewing items stored on my NAS is way faster, especially noticeable in the same Wi-Fi, where Immich sometimes behaves like it had just a slow 3G connection.

One nice thing I noticed in the Ente app (and made me worried at first, because I didn’t expect it) is that it uses the images on the device itself to display, at least if it has no connection to the server. Thus, images in the Photos app are always displayed there, even without internet connection. Immich, on the other hand, does only store thumbnails locally and doesn’t access locally stored images if there is no connection.

I also like the UI more for Ente, but essentially they are similar. Scrolling is more snappy for the Ente app, while especially faster scrolling is stuttering in the Immich app.

However, this all comes with one major drawback: Nearly everything you do in the Ente app requires very much compute resources, which is noticeable especially when viewing videos. My iPhone 13 Pro gets very hot while watching, while there’s no such problem in Immich.

My best guess is that this is to the continuous encryption and decryption operations in Ente, since the server apps always run very cool and without many resources.

Server resources

Which brings us to the server resources. Ente can run even with very small amount of resources. According to the documentation, also some embedded devices are sufficient to run it. On the other hand, Immich requires 4 GiB of RAM (recommended 6 GiB) and 2 CPU cores (4 cores recommended).

In my system, the Ente apps need around 130 – 150 MiB RAM and nearly no CPU usage while viewing a video, the S3 storage 300 – 500 MiB RAM and 1 – 3 % CPU usage. Immich uses more than 900 MiB RAM with 5 – 12 % CPU usage while viewing a video.

Immich on the other hand needs around 950 MiB RAM and also 3 % CPU usage while viewing a video.

However, if you upload files, you can see the difference between on-device handling for Ente, which does produce nearly no notable usage on the server, while Immich ramps up to using around 25 % of my AMD Ryzen 5 PRO 2400GE CPU and peaks at 1,6 GiB RAM usage.

And even though all systems are in the same Wi-Fi, a 4K clip sometimes cannot be viewed without buffering on Immich. It just doesn’t load fast enough (this seems to be improved the last weeks through updates, though). However, the comparison is not really fair, since Ente doesn’t allow you to view the raw footage as originally stored. In my case, the original video was around 145 MiB in size, while the video viewed directly in Ente was only half its size. So, there’s definitely some kind of converting taking place in Ente.

Interestingly, Immich has a file storage usage of 127 GiB, while the S3 storage of Ente consumes only 107 GiB. And in its UI, Immich tells me it stores 236 GiB.

Machine learning

Both projects use machine learning, e.g. for face recognition or searching for natural language and it works fine for both. They also get both a plus for using the correct term machine learning instead of “AI”.

Also, in Ente, machine learning is an on-device process, while Immich does it on server-side. While both should be working fine, in general, I like it more to have it on server-side (since I also own the server) to keep resource usage low on the (often mobile) device.

1.5.26 Immich

1.5.26 Immich

1. Installation

han

You want to use the immich release version s.t it will always update to the newest version and keep immich up to date.

100MB File size Cloudflare Tunnel Limits

Source: Cloudflare Tunnels

If you use cloudflare tunnels for your public websites then it is limited to files of 100MB or less. This is a problem of course since lots of video files are larger than 100 MB. Hence, if you use cloudlfare tunnels the files either need to be uploaded in chuncks smaller than a 100MB or you need to remove the cloudflare tunnel and expose your public IP to the internet. The former is of course the best option which can be solved by using

30 Journiv

30 Journiv

1. Installation

Source: https://www.journiv.com/docs/installation/docker

Then go into your terminal and make sure you provide the following permissions for the journiv data folder. 

mkdir -p data/app data/postgres data/valkey
sudo chown -R 1000:1000 data
sudo chmod -R 755 data

Then fill in the docker compose file

Then copy paste the .env file

Then in the .env file you should create again some secrets with

 


docker compose up -d

Then make sure you have your reverse proxy ready !! Because it will only load for the Domain you have set in the .env file!!

 

 

 

 

 

 

31. Tinycld

31. Tinycld

1. Installtion

First run setup, visit below URL to configure tinycld: │
2026-07-08T00:45:55.850609771Z [tinycld] │ │
2026-07-08T00:45:55.850612081Z [tinycld]http://0.0.0.0:7090/admin?token=f6a1f9128b179c8d4ac119982b156676c58f893934e170c9a2838171a96748e8

31. Journal/Diary Applications

This chapter looks at different open source journal/diary applications. As usual we look both at E2EE and non E2EE versions. These applications are meant to provide you with an oversight of your thoughts, the things you did during the day, how you felt, etcetera. Also there will be apps that are local only i.e just work on your phone for instance or once that work via your server that can be accessed on multiple devices and used by multiple users. In this case it is especially necessary that your data is E2EE, since you don't want someone else reading your most personal notes. However, if you host your own server at home then you can go with some beautiful journal applications that are not E2EE.

At this moment the only E2EE encrypted open source diary apps would be Notesnook and Joplin ;) However, I would say they are more focused on being a note taking app with folders, sub folders for studying and developing. So far the best project in my opinion is Journiv that would be most focused on your personal life/diary format, but unfortunately it is not E2EE at this moment. However, if you are self hosting this obviously does not matter ;)


31. Journal/Diary Applications

1. Memos

The first application that we will discuss is the Memos application

 

31. Journal/Diary Applications

2. Journiv

Community: Discord

At this moment I use Journiv as my main diary application for my personal life. The reason I use this one is the following

  1. It allows me to create multiple Journals for different things. For instance a journal for Sports, Work, Relationships, Health, etcetera.
  2. Second it allows me to create tags. With these tags I can easily navigate how often I did or had something. For instance how often you went to the gym, had an anxiety attack, how often you took vitamine pills etcetera. 
  3. It is a new kid on the block with lots of features planned
  4. It integrates with Immich, but will integrate with many other features according to their roadmap
  5. The only down side to it is that you won't get all the features unless you get a paid subscription. The price is not that high, but it always makes you wonder to what extend the open source app will become a commercial product and just uses open source to sell. However, this could also change over the years of course. At this moment the free version gives you enough capabilities. 
  6. It allows you to add people to your memories as well. Hence, you can also easily click on a person in your diary and see all the notes that included these people.

31. Journal/Diary Applications

3. Daily You

32. Dashboards

This chapter discusses different open source webbrowser dashboards. You could see it as the landing page of your webbrowser from which you move to your apps or the web. These dashboards are more than just a clickable link, since there are also API integrations possible.

32. Dashboards

1. Homarr

5. Dashboards

6. Collaboration

This Chapter discusses some collaboration applications i.e alternatives to slack for instance.

6. Collaboration

1. Mattermost

Source: Mattermost Docker Installation

compose.yml
# https://docs.docker.com/compose/environment-variables/
services:
  postgres:
    image: postgres:${POSTGRES_IMAGE_TAG}
    restart: ${RESTART_POLICY}
    security_opt:
      - no-new-privileges:true
    # See https://docs.mattermost.com/administration-guide/scale/scaling-for-enterprise.html
    # for guidance on memory limits based on your deployment size.
    mem_limit: 16G
    read_only: true
    tmpfs:
      - /tmp
      - /var/run/postgresql
    volumes:
      - ${POSTGRES_DATA_PATH}:/var/lib/postgresql
    environment:
      # timezone inside container
      - TZ

      # necessary Postgres options/variables
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB

  mattermost:
    depends_on:
      - postgres
    image: mattermost/${MATTERMOST_IMAGE}:${MATTERMOST_IMAGE_TAG}
    restart: ${RESTART_POLICY}
    security_opt:
      - no-new-privileges:true
    # See https://docs.mattermost.com/administration-guide/scale/scaling-for-enterprise.html
    # for guidance on memory limits based on your deployment size.
    mem_limit: 4G
    read_only: ${MATTERMOST_CONTAINER_READONLY}
    tmpfs:
      - /tmp
    volumes:
      - ${MATTERMOST_CONFIG_PATH}:/mattermost/config:rw
      - ${MATTERMOST_DATA_PATH}:/mattermost/data:rw
      - ${MATTERMOST_LOGS_PATH}:/mattermost/logs:rw
      - ${MATTERMOST_PLUGINS_PATH}:/mattermost/plugins:rw
      - ${MATTERMOST_CLIENT_PLUGINS_PATH}:/mattermost/client/plugins:rw
      - ${MATTERMOST_BLEVE_INDEXES_PATH}:/mattermost/bleve-indexes:rw
      # When you want to use SSO with GitLab, you have to add the cert pki chain of GitLab inside Alpine
      # to avoid Token request failed: certificate signed by unknown authority 
      # (link: https://github.com/mattermost/mattermost-server/issues/13059 and https://github.com/mattermost/docker/issues/34)
      # - ${GITLAB_PKI_CHAIN_PATH}:/etc/ssl/certs/pki_chain.pem:ro
    environment:
      # timezone inside container
      - TZ

      # necessary Mattermost options/variables (see env.example)
      - MM_SQLSETTINGS_DRIVERNAME
      - MM_SQLSETTINGS_DATASOURCE

      # necessary for bleve
      - MM_BLEVESETTINGS_INDEXDIR

      # additional settings
      - MM_SERVICESETTINGS_SITEURL

Check the permissions of the folders! needs to be 2000:2000

.env
# Domain of service
DOMAIN=mattermost.rfeyn.org

# Container settings
## Timezone inside the containers. The value needs to be in the form 'Europe/Berlin'.
## A list of these tz database names can be looked up at Wikipedia
## https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
TZ=Europe/Amsterdam
RESTART_POLICY=unless-stopped

# Postgres settings
## Documentation for this image and available settings can be found on hub.docker.com
## https://hub.docker.com/_/postgres
## Please keep in mind this will create a superuser and it's recommended to use a less privileged
## user to connect to the database.
## A guide on how to change the database user to a nonsuperuser can be found in docs/creation-of-nonsuperuser.md
POSTGRES_IMAGE_TAG=18-alpine
POSTGRES_DATA_PATH=./volumes/db/var/lib/postgresql/data

POSTGRES_USER=mmuser
POSTGRES_PASSWORD=aosdjfasjdfajsfjlaskdfasdf
POSTGRES_DB=mattermost

# Nginx
## The nginx container will use a configuration found at the NGINX_MATTERMOST_CONFIG. The config aims
## to be secure and uses a catch-all server vhost which will work out-of-the-box. For additional settings
## or changes ones can edit it or provide another config. Important note: inside the container, nginx sources
## every config file inside */etc/nginx/conf.d* ending with a *.conf* file extension.

## Inside the container the uid and gid is 101. The folder owner can be set with
## `sudo chown -R 101:101 ./nginx` if needed.
## Note that this repository requires nginx version 1.25.1 or later
NGINX_IMAGE_TAG=alpine

## The folder containing server blocks and any additional config to nginx.conf
NGINX_CONFIG_PATH=./nginx/conf.d
NGINX_DHPARAMS_FILE=./nginx/dhparams4096.pem

CERT_PATH=./volumes/web/cert/cert.pem
KEY_PATH=./volumes/web/cert/key-no-password.pem
#GITLAB_PKI_CHAIN_PATH=<path_to_your_gitlab_pki>/pki_chain.pem
#CERT_PATH=./certs/etc/letsencrypt/live/${DOMAIN}/fullchain.pem
#KEY_PATH=./certs/etc/letsencrypt/live/${DOMAIN}/privkey.pem

## Exposed ports to the host. Inside the container 80, 443 and 8443 will be used
HTTPS_PORT=443
HTTP_PORT=80
CALLS_PORT=8443

# Mattermost settings
## Inside the container the uid and gid is 2000. The folder owner can be set with
## `sudo chown -R 2000:2000 ./volumes/app/mattermost`.
MATTERMOST_CONFIG_PATH=./volumes/app/mattermost/config
MATTERMOST_DATA_PATH=./volumes/app/mattermost/data
MATTERMOST_LOGS_PATH=./volumes/app/mattermost/logs
MATTERMOST_PLUGINS_PATH=./volumes/app/mattermost/plugins
MATTERMOST_CLIENT_PLUGINS_PATH=./volumes/app/mattermost/client/plugins
MATTERMOST_BLEVE_INDEXES_PATH=./volumes/app/mattermost/bleve-indexes

## Bleve index (inside the container)
MM_BLEVESETTINGS_INDEXDIR=/mattermost/bleve-indexes

## This will be 'mattermost-enterprise-edition' or 'mattermost-team-edition' based on the version of Mattermost you're installing.
MATTERMOST_IMAGE=mattermost-enterprise-edition
## Update the image tag if you want to upgrade your Mattermost version. You may also upgrade to the latest one. The example is based on the latest Mattermost ESR versi>
MATTERMOST_IMAGE_TAG=11.7.0

## Make Mattermost container readonly. This interferes with the regeneration of root.html inside the container. Only use
## it if you know what you're doing.
## See https://github.com/mattermost/docker/issues/18
MATTERMOST_CONTAINER_READONLY=false

## The app port is only relevant for using Mattermost without the nginx container as reverse proxy. This is not meant
## to be used with the internal HTTP server exposed but rather in case one wants to host several services on one host
## or for using it behind another existing reverse proxy.
APP_PORT=1023

## Configuration settings for Mattermost. Documentation on the variables and the settings itself can be found at
## https://docs.mattermost.com/administration/config-settings.html
## Keep in mind that variables set here will take precedence over the same setting in config.json. This includes
## the system console as well and settings set with env variables will be greyed out.

## Below one can find necessary settings to spin up the Mattermost container
MM_SQLSETTINGS_DRIVERNAME=postgres
MM_SQLSETTINGS_DATASOURCE=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable&connect_timeout=10

## Example settings (any additional setting added here also needs to be introduced in the docker-compose.yml)

 

 

Notifications: https://docs.mattermost.com/end-user-guide/preferences/manage-your-mobile-notifications.html

 

6. Collaboration

2. Matrix

4. LLM Dashboards

This chapter contains different open source LLM access dashboards. Hopefully, it will include also open source LLM's whenever they come out. Yes OpenAI is not open source. Up untill then it mainly discusses open source dashboard where you can add your own models and store all the gathered data locally as well.

4. LLM Dashboards

1. OpenWebui

Installation

There are different ways you can install OpenWebUI but as usual I like to install it with docker compose that also includes Ollama as well. You can find the docker compose file over here

compose.yml
services:
  ollama:
    volumes:
      - ./ollama:/root/.ollama
    container_name: ollama
    environment:
      - OLLAMA_NUM_GPU=1     
      - GPU_DEVICE_ORDINAL=0
    devices:    
      - /dev/dri/renderD128:/dev/dri/renderD128
    pull_policy: always
    tty: true
    restart: unless-stopped
    image: ollama/ollama:${OLLAMA_DOCKER_TAG-latest}

  open-webui:
    build:
      context: .
      dockerfile: Dockerfile
    image: ghcr.io/open-webui/open-webui:${WEBUI_DOCKER_TAG-main}
    container_name: open-webui
    volumes:
      - ./open-webui:/app/backend/data
    depends_on:
      - ollama
    ports:
      - ${OPEN_WEBUI_PORT-3000}:8080
    environment:
      - 'OLLAMA_BASE_URL=http://ollama:11434'
      - 'WEBUI_SECRET_KEY='
    extra_hosts:
      - host.docker.internal:host-gateway
    restart: unless-stopped

and the environment variables fil

.env
# Ollama URL for the backend to connect
# The path '/ollama' will be redirected to the specified backend URL
OLLAMA_BASE_URL='http://localhost:11434'

OPENAI_API_BASE_URL=''
OPENAI_API_KEY=''

# AUTOMATIC1111_BASE_URL="http://localhost:7860"

# For production, you should only need one host as
# fastapi serves the svelte-kit built frontend and backend from the same host and port.
# To test # Ollama URL for the backend to connect
# The path '/ollama' will be redirected to the specified backend URL
OLLAMA_BASE_URL='http://localhost:11434'

OPENAI_API_BASE_URL=''
OPENAI_API_KEY=''
OPEN_WEBUI_PORT=1024

# AUTOMATIC1111_BASE_URL="http://localhost:7860"

# For production, you should only need one host as
# fastapi serves the svelte-kit built frontend and backend from the same host and port.
# To test with CORS locally, you can set something like
# CORS_ALLOW_ORIGIN='http://localhost:5173;http://localhost:8080'
CORS_ALLOW_ORIGIN='*'

# Set to false to keep memory tools enabled without adding memory context to the system context.
ENABLE_MEMORY_SYSTEM_CONTEXT=true

# For production you should set this to match the proxy configuration (127.0.0.1)
FORWARDED_ALLOW_IPS='*'

# DO NOT TRACK
SCARF_NO_ANALYTICS=true
DO_NOT_TRACK=true
ANONYMIZED_TELEMETRY=false

# Valkey Vector Store (requires VECTOR_DB=valkey)
# VALKEY_URL='valkey://localhost:6379'
# VALKEY_COLLECTION_PREFIX='open_webui'
# VALKEY_INDEX_TYPE='HNSW'
# VALKEY_DISTANCE_METRIC='COSINE'
# VALKEY_HNSW_M='16'
# VALKEY_HNSW_EF_CONSTRUCTION='200'
# VALKEY_HNSW_EF_RUNTIME='10'with CORS locally, you can set something like
# CORS_ALLOW_ORIGIN='http://localhost:5173;http://localhost:8080'
CORS_ALLOW_ORIGIN='*'

# Set to false to keep memory tools enabled without adding memory context to the system context.
ENABLE_MEMORY_SYSTEM_CONTEXT=true

 

Then simply deploy the stack with docker compose up -d or with Dockhand by simply clicking on Deploy stack ;) !! Then if it is all green lights you will be able to access it either on the URL you sepcified in the .env file or on http://DOCKERVM-IP:PORT. You will then be presented with this interesting screen ;) 

Screenshot From 2026-07-16 16-02-49.png

Then simply create your admin account and save it in your Vaultwarden account. You will then be brought to your OpenWebUI dashboard. Note! There is no model configured yet, so that's what we will do in the next step. 

Screenshot From 2026-07-16 16-16-48.png

1. Cloud Storage

This book describes different cloud storage experience

20. Documentation

20. Documentation

1. HedgeDocs

Source: https://docs.hedgedoc.dev/how-to/reverse-proxy/

 

8. ToDo Software

This chapter discusses different ToDo list open source software. This can either be integrated into another bigger application or completely stand alone.

8. ToDo Software

1. Vikunja

Source: Vikunja

8. ToDo Software

10. Nextcloud ToDo

Nextcloud ToDo comes integrated with Nextcloud as an application. 

Messenger Applications

Messenger Applications

1. Session Messenger

Source: Session Messenger FAQ

Source: Arguments against Session Messenger

Loki: Is basiscally Tor for rich people

 

Session Messenger would not go down if seed nodes were destroyed, though it would create temporary disruptions for new users trying to connect.

Here's how it would work:

Initial impact on new users: Seed nodes are bootstrap points that help new users find and connect to the decentralized network. If all seed nodes were destroyed, new users would have difficulty joining the network initially since they wouldn't have a starting point to discover peers.

Existing users unaffected: Users already connected to the network would continue to function normally. Their clients maintain direct peer-to-peer connections with other users and nodes, so they wouldn't depend on seed nodes to keep messaging.

Recovery and resilience: The decentralized nature of Session Messenger means that:

  • New seed nodes could be deployed relatively quickly by the community
  • Users could potentially connect manually by knowing the address of at least one existing peer on the network
  • The network's distributed architecture is specifically designed to be resistant to this kind of centralized attack

The key distinction: Session's design acknowledges that some bootstrap infrastructure is necessary, but the actual message routing and peer-to-peer communication happens across the distributed network. Destroying seed nodes would be more of an inconvenience than a fatal blow—it would slow adoption and create friction for newcomers, but wouldn't collapse the network for active participants.

This is actually one of the arguments proponents make for decentralized messenger systems: they're theoretically more resilient to government or corporate infrastructure takedowns compared to services relying on centralized data centers.