refactor(cutover): retire monolith — runner + apply script post-split

Application stacks now live in stacks/<domain>/ and are deployed by
Portainer's git poll. The runner workflow no longer pushes or applies the
monolithic root docker-compose.yml — it just pushes the Caddyfile and reloads
Caddy (now via docker exec, since `dc exec` no longer makes sense). apply-
compose.ps1 drops its -Compose and -DevStack flags for the same reason.

Local docker-compose.yml and dev-compose.yml are deleted; their content has
been split into stacks/* and is already running in production. The server
copies at /valhalla-lab/docker-compose.yml and /valhalla-lab/dev-compose.yml
are now orphan files (no workflow or script writes to them) and will be
removed after Caddy is cut over to its own proxy Portainer stack.

No live container is touched by this commit. The runner's next invocation
runs the new strict-subset workflow (Caddyfile push + reload only).
This commit is contained in:
ginnoir
2026-06-04 17:59:23 -05:00
parent b7f51524d9
commit cee43aa733
2 changed files with 0 additions and 1178 deletions
-379
View File
@@ -1,379 +0,0 @@
# dev-compose.yml
# Full developer tooling stack: Git hosting, browser IDE, container registry,
# database UI, secret management, documentation wiki, and project management.
#
# This file creates the `devstack` network. The main docker-compose.yml
# references it as external so Caddy can reach these services.
#
# Fresh-install order:
# docker compose -f portainer-compose.yml up -d # portainer_proxy network
# docker compose -f dev-compose.yml up -d # devstack network + dev tools
# dc up -d # application stack
#
# Apply changes:
# powershell -File apply-compose.ps1 -DevStack
services:
# ============================================================
# GITEA (self-hosted Git)
# ============================================================
gitea:
container_name: gitea
image: gitea/gitea:latest
restart: unless-stopped
networks:
- devstack
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=postgres_gitea:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=${GITEA_DB_PASSWORD}
- GITEA__server__DOMAIN=gitea.ginnoir.com
- GITEA__server__ROOT_URL=https://gitea.ginnoir.com
- GITEA__server__SSH_DOMAIN=gitea.ginnoir.com
- GITEA__server__SSH_PORT=2222
volumes:
- /config/gitea:/data
- /etc/localtime:/etc/localtime:ro
ports:
- "3030:3000"
- "2222:22"
depends_on:
postgres_gitea:
condition: service_healthy
postgres_gitea:
container_name: postgres_gitea
image: postgres:16-alpine
restart: unless-stopped
networks:
- devstack
environment:
POSTGRES_USER: gitea
POSTGRES_PASSWORD: ${GITEA_DB_PASSWORD}
POSTGRES_DB: gitea
volumes:
- gitea_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitea -d gitea"]
interval: 10s
timeout: 5s
retries: 5
# ============================================================
# CODE-SERVER (VS Code in browser)
# ============================================================
code-server:
container_name: code_server
image: lscr.io/linuxserver/code-server:latest
restart: unless-stopped
networks:
- devstack
environment:
- PUID=1000
- PGID=1000
- TZ=${TZ}
- PASSWORD=${CODE_SERVER_PASSWORD}
- SUDO_PASSWORD=${CODE_SERVER_PASSWORD}
- DEFAULT_WORKSPACE=/workspace
volumes:
- /config/code-server:/config
- /home/ginnoir:/workspace
ports:
- "8443:8443"
# ============================================================
# PRIVATE CONTAINER REGISTRY
# ============================================================
registry:
container_name: registry
image: registry:2
restart: unless-stopped
networks:
- devstack
volumes:
- /config/registry:/var/lib/registry
ports:
- "5000:5000"
# ============================================================
# DBX (Multi-database web UI)
# https://github.com/t8y2/dbx
# ============================================================
dbx:
container_name: dbx
image: t8y2/dbx:latest
restart: unless-stopped
networks:
- devstack
volumes:
- dbx_data:/app/data
ports:
- "4224:4224"
# ============================================================
# VAULT (Secret management)
# One-time setup after first start:
# ssh ginnoir@valhalla
# docker exec -it vault vault operator init
# vault operator unseal (three times with unseal keys)
# ============================================================
vault:
container_name: vault
image: hashicorp/vault:latest
restart: unless-stopped
networks:
- devstack
cap_add:
- IPC_LOCK
environment:
- VAULT_ADDR=http://0.0.0.0:8200
volumes:
- /config/vault/config:/vault/config:ro
- /config/vault/data:/vault/data
- /config/vault/logs:/vault/logs
command: vault server -config=/vault/config/vault.hcl
ports:
- "8200:8200"
# ============================================================
# BOOKSTACK (Documentation / Wiki)
# ============================================================
bookstack:
container_name: bookstack
image: lscr.io/linuxserver/bookstack:latest
restart: unless-stopped
networks:
- devstack
environment:
- PUID=1000
- PGID=1000
- TZ=${TZ}
- APP_URL=https://docs.ginnoir.com
- DB_HOST=mariadb_bookstack
- DB_PORT=3306
- DB_USER=bookstack
- DB_PASS=${BOOKSTACK_DB_PASSWORD}
- DB_DATABASE=bookstack
volumes:
- /config/bookstack:/config
ports:
- "6875:80"
depends_on:
mariadb_bookstack:
condition: service_healthy
mariadb_bookstack:
container_name: mariadb_bookstack
image: lscr.io/linuxserver/mariadb:latest
restart: unless-stopped
networks:
- devstack
environment:
- PUID=1000
- PGID=1000
- TZ=${TZ}
- MYSQL_ROOT_PASSWORD=${BOOKSTACK_DB_ROOT_PASSWORD}
- MYSQL_DATABASE=bookstack
- MYSQL_USER=bookstack
- MYSQL_PASSWORD=${BOOKSTACK_DB_PASSWORD}
volumes:
- /config/bookstack-db:/config
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
# ============================================================
# PLANE (Project management)
# First start: log into https://plane.ginnoir.com and create a workspace.
# Caddy routes /api/ and /auth/ to plane-api, everything else to plane-web.
# ============================================================
plane-web:
container_name: plane_web
image: makeplane/plane-frontend:latest
restart: unless-stopped
networks:
- devstack
command: node web/server.js
environment:
- NEXT_PUBLIC_API_BASE_URL=https://plane.ginnoir.com
plane-api:
container_name: plane_api
image: makeplane/plane-backend:latest
restart: unless-stopped
networks:
- devstack
command: ./bin/docker-entrypoint-api.sh
environment:
- SECRET_KEY=${PLANE_SECRET_KEY}
- DEBUG=0
- DATABASE_URL=postgresql://plane:${PLANE_DB_PASSWORD}@postgres_plane:5432/plane
- REDIS_URL=redis://redis_plane:6379/
- USE_MINIO=1
- AWS_REGION=us-east-1
- AWS_ACCESS_KEY_ID=${PLANE_MINIO_USER}
- AWS_SECRET_ACCESS_KEY=${PLANE_MINIO_PASSWORD}
- AWS_S3_ENDPOINT_URL=http://plane_minio:9000
- AWS_S3_BUCKET_NAME=uploads
- WEB_URL=https://plane.ginnoir.com
- CORS_ALLOWED_ORIGINS=https://plane.ginnoir.com
- GUNICORN_WORKERS=2
depends_on:
postgres_plane:
condition: service_healthy
redis_plane:
condition: service_healthy
plane-migrator:
condition: service_completed_successfully
plane-worker:
container_name: plane_worker
image: makeplane/plane-backend:latest
restart: unless-stopped
networks:
- devstack
command: ./bin/docker-entrypoint-worker.sh
environment:
- SECRET_KEY=${PLANE_SECRET_KEY}
- DEBUG=0
- DATABASE_URL=postgresql://plane:${PLANE_DB_PASSWORD}@postgres_plane:5432/plane
- REDIS_URL=redis://redis_plane:6379/
- USE_MINIO=1
- AWS_REGION=us-east-1
- AWS_ACCESS_KEY_ID=${PLANE_MINIO_USER}
- AWS_SECRET_ACCESS_KEY=${PLANE_MINIO_PASSWORD}
- AWS_S3_ENDPOINT_URL=http://plane_minio:9000
- AWS_S3_BUCKET_NAME=uploads
depends_on:
postgres_plane:
condition: service_healthy
redis_plane:
condition: service_healthy
plane-migrator:
condition: service_completed_successfully
plane-beat:
container_name: plane_beat
image: makeplane/plane-backend:latest
restart: unless-stopped
networks:
- devstack
command: ./bin/docker-entrypoint-beat.sh
environment:
- SECRET_KEY=${PLANE_SECRET_KEY}
- DEBUG=0
- DATABASE_URL=postgresql://plane:${PLANE_DB_PASSWORD}@postgres_plane:5432/plane
- REDIS_URL=redis://redis_plane:6379/
depends_on:
postgres_plane:
condition: service_healthy
redis_plane:
condition: service_healthy
plane-migrator:
condition: service_completed_successfully
plane-migrator:
container_name: plane_migrator
image: makeplane/plane-backend:latest
restart: "no"
networks:
- devstack
command: ./bin/docker-entrypoint-migrator.sh
environment:
- SECRET_KEY=${PLANE_SECRET_KEY}
- DATABASE_URL=postgresql://plane:${PLANE_DB_PASSWORD}@postgres_plane:5432/plane
- REDIS_URL=redis://redis_plane:6379/
depends_on:
postgres_plane:
condition: service_healthy
redis_plane:
condition: service_healthy
plane-create-bucket:
container_name: plane_create_bucket
image: minio/mc:latest
restart: "no"
networks:
- devstack
depends_on:
plane-minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
until /usr/bin/mc alias set plane_minio http://plane_minio:9000 $$MINIO_USER $$MINIO_PASSWORD; do sleep 1; done;
/usr/bin/mc mb plane_minio/uploads --ignore-existing;
exit 0;
"
environment:
- MINIO_USER=${PLANE_MINIO_USER}
- MINIO_PASSWORD=${PLANE_MINIO_PASSWORD}
postgres_plane:
container_name: postgres_plane
image: postgres:16-alpine
restart: unless-stopped
networks:
- devstack
environment:
POSTGRES_USER: plane
POSTGRES_PASSWORD: ${PLANE_DB_PASSWORD}
POSTGRES_DB: plane
volumes:
- plane_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U plane -d plane"]
interval: 10s
timeout: 5s
retries: 5
redis_plane:
container_name: redis_plane
image: redis:7-alpine
restart: unless-stopped
networks:
- devstack
command: --save 60 1 --loglevel warning
volumes:
- plane_redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
plane-minio:
container_name: plane_minio
image: minio/minio:latest
restart: unless-stopped
networks:
- devstack
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${PLANE_MINIO_USER}
MINIO_ROOT_PASSWORD: ${PLANE_MINIO_PASSWORD}
volumes:
- plane_minio_data:/data
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:9000/minio/health/live || exit 1"]
interval: 30s
timeout: 20s
retries: 3
start_period: 30s
networks:
devstack:
name: devstack
volumes:
gitea_db_data:
dbx_data:
plane_db_data:
plane_redis_data:
plane_minio_data:
-799
View File
@@ -1,799 +0,0 @@
services:
# ============================================================
# FOUNDRY VTT
# ============================================================
foundry:
container_name: foundry
image: felddy/foundryvtt:latest
hostname: valhalla-primary
init: true
restart: unless-stopped
networks:
- foundry
volumes:
- /storage1/foundry:/data
- /storage1/foundry/updatemain.bash:/home/foundry/updatemain.bash
- /storage1/Books/Tabletop:/data/Data/storage
environment:
- FOUNDRY_USERNAME=${FOUNDRY_USERNAME}
- FOUNDRY_PASSWORD=${FOUNDRY_PASSWORD}
- FOUNDRY_ADMIN_KEY=${FOUNDRY_ADMIN_KEY}
- CONTAINER_VERBOSE=true
- FOUNDRY_HOSTNAME=foundry.ginnoir.com
- FOUNDRY_PROXY_PORT=443
- FOUNDRY_PROXY_SSL=true
- CONTAINER_PRESERVE_OWNER=/data/Data/storage
- FOUNDRY_UID=1000
- FOUNDRY_GID=1000
- CONTAINER_PATCH_URLS=${FOUNDRY_PATCH_URLS}
ports:
- "30000:30000"
foundry2:
container_name: foundry2
image: felddy/foundryvtt:latest
hostname: valhalla-secondary
init: true
restart: unless-stopped
networks:
- foundry
volumes:
- /storage1/foundry2:/data
- /storage1/foundry/updatemain.bash:/home/foundry/updatemain.bash
- /storage1/Books/Tabletop:/data/Data/storage
environment:
- FOUNDRY_USERNAME=${FOUNDRY_USERNAME}
- FOUNDRY_PASSWORD=${FOUNDRY_PASSWORD}
- FOUNDRY_ADMIN_KEY=${FOUNDRY_ADMIN_KEY}
- CONTAINER_VERBOSE=true
- FOUNDRY_HOSTNAME=foundry2.ginnoir.com
- FOUNDRY_PROXY_PORT=443
- FOUNDRY_PROXY_SSL=true
- CONTAINER_PRESERVE_OWNER=/data/Data/storage
- FOUNDRY_UID=1000
- FOUNDRY_GID=1000
- CONTAINER_PATCH_URLS=${FOUNDRY_PATCH_URLS}
ports:
- "30001:30000"
# ============================================================
# CADDY (reverse proxy)
# serfriz/caddy-cloudflare-ddns bundles:
# - caddy-dns/cloudflare (DNS-01 ACME challenge)
# - caddy-cloudflare-ip (trust Cloudflare proxy IPs for real client IPs)
# - caddy-dynamicdns (keeps DNS records current if WAN IP changes)
# ============================================================
caddy:
container_name: caddy
image: serfriz/caddy-cloudflare-ddns:latest
restart: unless-stopped
networks:
- foundry
- owncloud
- resume
- media_external
- media
- famapp
- authentik
- management
- portainer_proxy
- devstack
- edge
ports:
- "80:80"
- "443:443"
environment:
- CF_API_TOKEN=${CF_API_TOKEN}
volumes:
- /config/caddy/Caddyfile:/etc/caddy/Caddyfile
- /config/caddy/site:/srv
- /config/caddy/data:/data
- /config/caddy/config:/config
- /storage1/Books:/srv/Books
# ============================================================
# MEDIA STACK
# ============================================================
deluge:
container_name: deluge
image: lscr.io/linuxserver/deluge:latest
restart: unless-stopped
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ${ROOT}/storage1/torrents:/downloads
- ${ROOT}/config/deluge:/config
ports:
- "8112:8112"
nzbget:
container_name: nzbget
image: lscr.io/linuxserver/nzbget:latest
restart: unless-stopped
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ${ROOT}/storage1/complete:/downloads
- ${ROOT}/config/nzbget:/config
ports:
- "6789:6789"
sonarr:
container_name: sonarr
image: lscr.io/linuxserver/sonarr:latest
restart: unless-stopped
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ROOT}/config/sonarr:/config
- ${ROOT}/storage1/Media/TV:/tv
- ${ROOT}/storage1/Media/Anime:/anime
- ${ROOT}/storage1/complete:/downloads
ports:
- "8989:8989"
radarr:
container_name: radarr
image: lscr.io/linuxserver/radarr:latest
restart: unless-stopped
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ROOT}/config/radarr:/config
- ${ROOT}/storage1/Media/Movies:/movies
- ${ROOT}/storage1/complete:/downloads
ports:
- "7878:7878"
bazarr:
container_name: bazarr
image: lscr.io/linuxserver/bazarr:latest
restart: unless-stopped
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
- UMASK_SET=022
volumes:
- ${ROOT}/config/bazarr:/config
- ${ROOT}/storage1/Media/Movies:/movies
- ${ROOT}/storage1/Media/TV:/tv
- ${ROOT}/storage1/Media/Anime:/anime
ports:
- "6767:6767"
prowlarr:
container_name: prowlarr
image: ghcr.io/hotio/prowlarr:latest
restart: unless-stopped
networks:
- media
environment:
- PUID=1000
- PGID=1000
- UMASK=002
- TZ=${TZ}
volumes:
- /config/prowlarr:/config
ports:
- "9696:9696"
overseerr:
container_name: overseerr
image: sctx/overseerr:latest
networks:
- media_external
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- UMASK=002
- TZ=${TZ}
volumes:
- /config/overseerr:/app/config
ports:
- "5055:5055"
# ============================================================
# MEDIA UTILITIES
# ============================================================
tautulli:
image: ghcr.io/linuxserver/tautulli:latest
container_name: tautulli
restart: unless-stopped
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ${ROOT}/config/tautulli:/config
- ${ROOT}/storage1/PMS:/logs
ports:
- "8181:8181"
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
restart: unless-stopped
networks:
- media
environment:
- PUID=1000
- PGID=1000
- TZ=${TZ}
- WEBUI_PORT=3232
- TORRENTING_PORT=6881
volumes:
- /config/qbittorrent:/config
- /storage1/complete:/downloads
ports:
- "3232:3232"
- "6881:6881"
- "6881:6881/udp"
whisparr:
container_name: whisparr
image: ghcr.io/hotio/whisparr:latest
restart: unless-stopped
networks:
- media
environment:
- PUID=1000
- PGID=1000
- UMASK=002
- TZ=${TZ}
- WEBUI_PORTS=6969/tcp
volumes:
- /config/whisparr:/config
- /storage1:/data
ports:
- "6969:6969"
stash:
image: stashapp/stash:latest
container_name: stash
restart: unless-stopped
networks:
- media
logging:
driver: json-file
options:
max-file: "10"
max-size: "2m"
environment:
- PUID=1000
- PGID=1000
- STASH_STASH=/data/
- STASH_GENERATED=/generated/
- STASH_METADATA=/metadata/
- STASH_CACHE=/cache/
- STASH_PORT=6970
volumes:
- /etc/localtime:/etc/localtime:ro
- /config/stash:/root/.stash
- /storage1/LinuxISOs:/data
- /storage1/stash/metadata:/metadata
- /storage1/stash/cache:/cache
- /storage1/stash/blobs:/blobs
- /storage1/stash/generated:/generated
ports:
- "6971:6970"
# ============================================================
# OWNCLOUD STACK
# ============================================================
owncloud:
image: owncloud/server:latest
container_name: owncloud_server
restart: unless-stopped
networks:
- owncloud
ports:
- "8080:8080"
depends_on:
mariadb:
condition: service_healthy
redis:
condition: service_healthy
environment:
- OWNCLOUD_DOMAIN=${OWNCLOUD_DOMAIN}
- OWNCLOUD_DB_TYPE=mysql
- OWNCLOUD_DB_NAME=owncloud
- OWNCLOUD_DB_USERNAME=${OWNCLOUD_DB_USER}
- OWNCLOUD_DB_PASSWORD=${OWNCLOUD_DB_PASSWORD}
- OWNCLOUD_DB_HOST=mariadb
- OWNCLOUD_ADMIN_USERNAME=${OWNCLOUD_ADMIN_USER}
- OWNCLOUD_ADMIN_PASSWORD=${OWNCLOUD_ADMIN_PASSWORD}
- OWNCLOUD_MYSQL_UTF8MB4=true
- OWNCLOUD_REDIS_ENABLED=true
- OWNCLOUD_REDIS_HOST=redis
healthcheck:
test: ["CMD", "/usr/bin/healthcheck"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- /config/owncloud:/mnt/data
- /storage1/Books:/mnt/books
- /mnt/dropbox:/mnt/data/files/dropbox
- /storage1/ROMs:/mnt/roms
mariadb:
image: mariadb:latest
container_name: owncloud_mariadb
restart: unless-stopped
networks:
- owncloud
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_USER=${OWNCLOUD_DB_USER}
- MYSQL_PASSWORD=${OWNCLOUD_DB_PASSWORD}
- MYSQL_DATABASE=owncloud
command: ["--max-allowed-packet=128M", "--innodb-log-file-size=64M"]
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-u", "root", "--password=${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- /config/mysql:/var/lib/mysql
redis:
image: redis:latest
container_name: owncloud_redis
restart: unless-stopped
networks:
- owncloud
command: ["--databases", "1"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- /config/redis:/data
# ============================================================
# REACTIVE RESUME STACK
# ============================================================
postgres:
container_name: postgres_resume
image: postgres:16-alpine
restart: unless-stopped
networks:
- resume
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 10s
timeout: 5s
retries: 5
resume-minio:
container_name: minio_resume
image: minio/minio:latest
restart: unless-stopped
networks:
- resume
command: server /data --console-address :9001
ports:
- "9000:9000"
- "9004:9001"
volumes:
- minio_data:/data
environment:
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:9000/minio/health/live || exit 1"]
interval: 30s
timeout: 20s
retries: 3
start_period: 30s
chrome:
container_name: chrome
image: ghcr.io/browserless/chromium:latest
restart: unless-stopped
networks:
- resume
environment:
- TIMEOUT=10000
- CONCURRENT=10
- TOKEN=${CHROME_TOKEN}
- EXIT_ON_HEALTH_FAILURE=true
- PRE_REQUEST_HEALTH_CHECK=true
app:
container_name: resume
image: amruthpillai/reactive-resume:latest
restart: unless-stopped
networks:
- resume
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
resume-minio:
condition: service_healthy
chrome:
condition: service_started
environment:
- PORT=3000
- NODE_ENV=production
- APP_URL=https://resume.ginnoir.com
- STORAGE_URL=https://storage.j-costa.com/default
- PRINTER_ENDPOINT=http://chrome:3000
- PRINTER_TOKEN=${CHROME_TOKEN}
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres
- AUTH_SECRET=${RESUME_AUTH_SECRET}
- MAIL_FROM=noreply@localhost
- STORAGE_ENDPOINT=resume-minio
- STORAGE_PORT=9000
- STORAGE_REGION=us-east-1
- STORAGE_BUCKET=default
- STORAGE_ACCESS_KEY=${MINIO_ROOT_USER}
- STORAGE_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- STORAGE_USE_SSL=false
- STORAGE_SKIP_BUCKET_CHECK=false
# ============================================================
# NOTIFICATIONS & RSS
# ============================================================
ntfy:
image: binwiederhier/ntfy:latest
container_name: ntfy
command:
- serve
restart: unless-stopped
environment:
- TZ=${TZ}
- NTFY_DEFAULT_HOST=https://ntfy.ginnoir.com
- NTFY_BASE_URL=https://ntfy.ginnoir.com
- NTFY_CACHE_FILE=/var/lib/ntfy/cache.db
- NTFY_AUTH_FILE=/var/lib/ntfy/auth.db
- NTFY_AUTH_DEFAULT_ACCESS=read-write
- NTFY_BEHIND_PROXY=true
- NTFY_ATTACHMENT_CACHE_DIR=/var/lib/ntfy/attachments
- NTFY_ENABLE_LOGIN=true
user: 1000:1000
networks:
- famapp
volumes:
- /config/ntfy/var/cache/ntfy:/var/cache/ntfy
- /config/ntfy/etc/ntfy:/etc/ntfy
- /config/ntfy/var/lib/ntfy:/var/lib/ntfy
ports:
- "8000:80"
healthcheck:
test:
[
"CMD-SHELL",
"wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1",
]
interval: 60s
timeout: 10s
retries: 3
start_period: 40s
freshrss:
image: freshrss/freshrss:latest
container_name: freshrss
hostname: freshrss
restart: unless-stopped
networks:
- famapp
logging:
options:
max-size: 10m
volumes:
- /config/freshrss/data:/var/www/FreshRSS/data
- /config/freshrss/extensions:/var/www/FreshRSS/extensions
ports:
- "9999:80"
environment:
- CRON_MIN=3,33
- TRUSTED_PROXY=172.16.0.1/12 192.168.0.1/16
- FRESHRSS_INSTALL=--default-user ginnoir --api-enabled --base-url https://freshrss.ginnoir.com
- FRESHRSS_USER=--api-password ${FRESHRSS_API_PASSWORD} --email ${FRESHRSS_EMAIL} --password ${FRESHRSS_PASSWORD} --user ginnoir
vigilant:
container_name: vigilant
image: ghcr.io/verifiedjoseph/vigilant:latest
restart: unless-stopped
environment:
- VIGILANT_NOTIFICATION_SERVICE=ntfy
- VIGILANT_NOTIFICATION_NTFY_URL=https://ntfy.ginnoir.com/
- VIGILANT_NOTIFICATION_NTFY_TOPIC=RSS
volumes:
- /config/vigilant/feeds.yaml:/app/feeds.yaml
- cache:/app/cache
security_opt:
- no-new-privileges:true
# ============================================================
# REMOTE ACCESS
# ============================================================
hbbr:
container_name: hbbr
image: rustdesk/rustdesk-server:latest
command: hbbr
restart: unless-stopped
ports:
- "21117:21117"
volumes:
- /config/rustdesk/hbbr/data:/root
hbbs:
container_name: hbbs
image: rustdesk/rustdesk-server:latest
command: hbbs
restart: unless-stopped
ports:
- "21115:21115"
- "21116:21116"
- "21116:21116/udp"
volumes:
- /config/rustdesk/hbbs/data:/root
depends_on:
- hbbr
# ============================================================
# MANAGEMENT
# ============================================================
uptime-kuma:
container_name: uptime_kuma
image: louislam/uptime-kuma:2
restart: unless-stopped
networks:
- management
ports:
- "3001:3001"
volumes:
- /config/uptime-kuma:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro
homarr:
container_name: homarr
image: ghcr.io/homarr-labs/homarr:latest
restart: unless-stopped
environment:
- SECRET_ENCRYPTION_KEY=${HOMARR_SECRET_ENCRYPTION_KEY}
networks:
- management
ports:
- "7575:7575"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /config/homarr/configs:/app/data/configs
- /config/homarr/icons:/app/public/icons
- /config/homarr/data:/data
# ============================================================
# UTILITIES
# ============================================================
5etools:
container_name: 5etools
image: ghcr.io/5etools-mirror-3/5etools-src:latest
restart: unless-stopped
networks:
- foundry
ports:
- "9009:80"
# ============================================================
# FAMAPP
# ============================================================
famapp:
container_name: famapp
image: ${FAMAPP_IMAGE:-ghcr.io/ginnoir/famapp:latest}
pull_policy: ${FAMAPP_PULL_POLICY:-always}
restart: unless-stopped
networks:
- famapp
environment:
NODE_ENV: production
AUTH_URL: ${AUTH_URL}
DATABASE_URL: postgres://${FAMAPP_DB_USER}:${FAMAPP_DB_PASSWORD}@famapp-db:5432/${FAMAPP_DB_NAME}
AUTH_SECRET: ${FAMAPP_AUTH_SECRET}
AUTH_OIDC_ISSUER: ${AUTH_OIDC_ISSUER}
AUTH_OIDC_CLIENT_ID: ${AUTH_OIDC_CLIENT_ID}
AUTH_OIDC_CLIENT_SECRET: ${AUTH_OIDC_CLIENT_SECRET}
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY}
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY}
VAPID_SUBJECT: ${VAPID_SUBJECT}
NTFY_URL: ${NTFY_URL:-}
NTFY_TOPIC: ${NTFY_TOPIC:-}
LOG_LEVEL: ${LOG_LEVEL:-info}
RUN_MIGRATIONS: ${RUN_MIGRATIONS:-true}
MINIO_ENDPOINT: http://famapp-minio:9000
MINIO_ROOT_USER: ${famapp_MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${famapp_MINIO_ROOT_PASSWORD}
MINIO_BUCKET: ${famapp_MINIO_BUCKET}
OPENPLANTBOOK_CLIENT_ID: ${famapp_OPENPLANTBOOK_CLIENT_ID}
OPENPLANTBOOK_CLIENT_SECRET: ${famapp_OPENPLANTBOOK_CLIENT_SECRET}
ports:
- "3010:3000"
depends_on:
famapp-db:
condition: service_healthy
famapp-minio:
condition: service_healthy
famapp-db:
container_name: postgres_famapp
image: postgres:16-alpine
restart: unless-stopped
networks:
- famapp
environment:
POSTGRES_USER: ${FAMAPP_DB_USER}
POSTGRES_PASSWORD: ${FAMAPP_DB_PASSWORD}
POSTGRES_DB: ${FAMAPP_DB_NAME}
volumes:
- famapp_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${FAMAPP_DB_USER} -d ${FAMAPP_DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
famapp-minio:
container_name: minio_famapp
image: minio/minio:latest
command: server /data --console-address ":9003"
restart: unless-stopped
environment:
MINIO_ROOT_USER: ${famapp_MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${famapp_MINIO_ROOT_PASSWORD}
volumes:
- garden_uploads:/data
ports:
- "9002:9000"
- "9003:9003"
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:9000/minio/health/live || exit 1"]
interval: 30s
timeout: 20s
retries: 3
start_period: 30s
networks:
- famapp
# ============================================================
# AUTHENTIK
# ============================================================
authentik-server:
container_name: authentik_server
image: ghcr.io/goauthentik/server:${AUTHENTIK_IMAGE_TAG:-2024.12.3}
restart: unless-stopped
command: server
networks:
- authentik
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
ports:
- "9200:9000"
depends_on:
authentik-db:
condition: service_healthy
authentik-redis:
condition: service_healthy
authentik-worker:
container_name: authentik_worker
image: ghcr.io/goauthentik/server:${AUTHENTIK_IMAGE_TAG:-2024.12.3}
restart: unless-stopped
command: worker
networks:
- authentik
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
depends_on:
authentik-db:
condition: service_healthy
authentik-redis:
condition: service_healthy
authentik-db:
container_name: postgres_authentik
image: postgres:16-alpine
restart: unless-stopped
networks:
- authentik
environment:
POSTGRES_USER: ${AUTHENTIK_DB_USER:-authentik}
POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASSWORD}
POSTGRES_DB: ${AUTHENTIK_DB_NAME:-authentik}
volumes:
- authentik_db_data:/var/lib/postgresql/data
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${AUTHENTIK_DB_USER:-authentik} -d ${AUTHENTIK_DB_NAME:-authentik}",
]
interval: 10s
timeout: 5s
retries: 5
authentik-redis:
container_name: redis_authentik
image: redis:7-alpine
restart: unless-stopped
networks:
- authentik
command: --save 60 1 --loglevel warning
volumes:
- authentik_redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
foundry:
owncloud:
resume:
media_external:
media:
famapp:
authentik:
management:
portainer_proxy:
external: true
devstack:
external: true
edge:
name: edge
external: true
volumes:
minio_data:
postgres_data:
cache:
famapp_db_data:
authentik_db_data:
authentik_redis_data:
garden_uploads: