Initial commit: homelab configuration and documentation

This commit is contained in:
2025-11-29 19:03:14 +00:00
commit 0769ca6888
72 changed files with 7806 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
# Full corrected Immich/Media stack (Traefik-ready)
# Requires pre-existing external overlay: traefik-public
version: '3.9'
networks:
traefik-public:
external: true
media-backend:
driver: overlay
volumes:
plex_config:
jellyfin_config:
immich_upload:
immich_model_cache:
immich_db:
immich_redis:
homarr_config:
services:
homarr:
image: ghcr.io/ajnart/homarr:latest
networks:
- traefik-public
- media-backend
volumes:
- homarr_config:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- TZ=America/Chicago
deploy:
placement:
constraints:
- node.labels.leader == true
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.homarr-router.rule=Host(`homarr.sj98.duckdns.org`)"
- "traefik.http.routers.homarr-router.entrypoints=websecure"
- "traefik.http.routers.homarr-router.tls.certresolver=leresolver"
- "traefik.http.services.homarr.loadbalancer.server.port=7575"
- "traefik.docker.network=traefik-public"
resources:
limits:
memory: 512M
reservations:
memory: 128M
restart_policy:
condition: on-failure
max_attempts: 3
plex:
image: plexinc/pms-docker:latest
hostname: plex
networks:
- traefik-public
- media-backend
volumes:
- plex_config:/config
- /mnt/media:/media:ro
environment:
- TZ=America/Chicago
- PLEX_CLAIM=claim-xxxxxxxxxxxx
- ADVERTISE_IP=http://192.168.1.196:32400/
deploy:
placement:
constraints:
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.plex-router.rule=Host(`plex.sj98.duckdns.org`)"
- "traefik.http.routers.plex-router.entrypoints=websecure"
- "traefik.http.routers.plex-router.tls.certresolver=leresolver"
- "traefik.http.services.plex.loadbalancer.server.port=32400"
- "traefik.docker.network=traefik-public"
restart_policy:
condition: on-failure
max_attempts: 3
jellyfin:
image: jellyfin/jellyfin:latest
networks:
- traefik-public
- media-backend
volumes:
- jellyfin_config:/config
- /mnt/media:/media:ro
environment:
- TZ=America/Chicago
deploy:
placement:
constraints:
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.jellyfin-router.rule=Host(`jellyfin.sj98.duckdns.org`)"
- "traefik.http.routers.jellyfin-router.entrypoints=websecure"
- "traefik.http.routers.jellyfin-router.tls.certresolver=leresolver"
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
- "traefik.docker.network=traefik-public"
restart_policy:
condition: on-failure
max_attempts: 3
immich-server:
image: ghcr.io/immich-app/immich-server:release
networks:
- traefik-public
- media-backend
volumes:
- /mnt/media/immich:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
environment:
- DB_HOSTNAME=immich-db
- DB_USERNAME=immich
- DB_PASSWORD=immich
- DB_DATABASE_NAME=immich
- REDIS_HOSTNAME=immich-redis
- TZ=America/Chicago
depends_on:
- immich-redis
- immich-db
deploy:
placement:
constraints:
- node.labels.leader == true
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.immich-server-router.rule=Host(`immich.sj98.duckdns.org`)"
- "traefik.http.routers.immich-server-router.entrypoints=websecure"
- "traefik.http.routers.immich-server-router.tls.certresolver=leresolver"
- "traefik.http.services.immich-server.loadbalancer.server.port=2283"
- "traefik.docker.network=traefik-public"
# Immich-specific headers and settings
- "traefik.http.routers.immich-server-router.middlewares=immich-headers"
- "traefik.http.middlewares.immich-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.services.immich-server.loadbalancer.passhostheader=true"
resources:
limits:
memory: 2G
restart_policy:
condition: on-failure
max_attempts: 3
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:release
networks:
- media-backend
volumes:
- immich_model_cache:/cache
environment:
- TZ=America/Chicago
depends_on:
- immich-server
deploy:
placement:
constraints:
- node.labels.heavy == true
- node.labels.ai == true
restart_policy:
condition: on-failure
max_attempts: 3
immich-redis:
image: redis:7-alpine
networks:
- media-backend
volumes:
- immich_redis:/data
deploy:
placement:
constraints:
- node.labels.leader == true
- node.role == manager
restart_policy:
condition: on-failure
max_attempts: 3
immich-db:
image: tensorchord/pgvecto-rs:pg14-v0.2.0
networks:
- media-backend
volumes:
- /mnt/database/immich:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=immich
- POSTGRES_USER=immich
- POSTGRES_DB=immich
deploy:
placement:
constraints:
- node.labels.leader == true
- node.role == manager
restart_policy:
condition: on-failure
max_attempts: 3

View File

@@ -0,0 +1,54 @@
version: '3.9'
networks:
traefik-public:
external: true
configs:
traefik_yml:
external: true
name: traefik.yml
services:
traefik:
image: traefik:v3.6.1
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /mnt/traefik/letsencrypt:/letsencrypt
networks:
- traefik-public
environment:
- DUCKDNS_TOKEN=14880437-fcee-4206-800a-af057cdfffe2
configs:
- source: traefik_yml
target: /etc/traefik/traefik.yml
deploy:
placement:
constraints:
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`traefik.sj98.duckdns.org`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=leresolver"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.services.traefik.loadbalancer.server.port=8080"
whoami:
image: traefik/whoami
networks:
- traefik-public
deploy:
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.sj98.duckdns.org`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=leresolver"
- "traefik.http.services.whoami.loadbalancer.server.port=80"

View File

@@ -0,0 +1,100 @@
version: '3.9'
networks:
traefik-public:
external: true
productivity-backend:
driver: overlay
volumes:
nextcloud_data:
nextcloud_db:
nextcloud_redis:
services:
nextcloud-db:
image: postgres:15-alpine
volumes:
- /mnt/database/nextcloud:/var/lib/postgresql/data
environment:
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=nextcloud # Replace with a secure password in production
networks:
- productivity-backend
deploy:
placement:
constraints:
- node.labels.leader == true
restart_policy:
condition: on-failure
nextcloud-redis:
image: redis:7-alpine
volumes:
- nextcloud_redis:/data
networks:
- productivity-backend
deploy:
placement:
constraints:
- node.labels.leader == true
restart_policy:
condition: on-failure
nextcloud:
image: nextcloud:latest
volumes:
- /mnt/nextcloud_apps:/var/www/html/custom_apps
- /mnt/nextcloud_config:/var/www/html/config
- /mnt/nextcloud_data:/var/www/html/data
environment:
- POSTGRES_HOST=nextcloud-db
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=nextcloud # Replace with a secure password in production
- REDIS_HOST=nextcloud-redis
- NEXTCLOUD_ADMIN_USER=admin # Replace with your desired admin username
- NEXTCLOUD_ADMIN_PASSWORD=password # Replace with a secure password
- NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.sj98.duckdns.org
- OVERWRITEPROTOCOL=https
- OVERWRITEHOST=nextcloud.sj98.duckdns.org
- TRUSTED_PROXIES=172.16.0.0/12
depends_on:
- nextcloud-db
- nextcloud-redis
networks:
- traefik-public
- productivity-backend
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 2G
reservations:
memory: 512M
restart_policy:
condition: on-failure
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.rule=Host(`nextcloud.sj98.duckdns.org`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=leresolver"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
- "traefik.docker.network=traefik-public"
# Nextcloud-specific middlewares
- "traefik.http.routers.nextcloud.middlewares=nextcloud-chain"
- "traefik.http.middlewares.nextcloud-chain.chain.middlewares=nextcloud-caldav,nextcloud-headers"
# CalDAV/CardDAV redirect
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.regex=^https://(.*)/.well-known/(card|cal)dav"
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.replacement=https://$$1/remote.php/dav/"
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.permanent=true"
# Security headers
- "traefik.http.middlewares.nextcloud-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.nextcloud-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.nextcloud-headers.headers.stsPreload=true"
- "traefik.http.middlewares.nextcloud-headers.headers.forceSTSHeader=true"
- "traefik.http.middlewares.nextcloud-headers.headers.customFrameOptionsValue=SAMEORIGIN"
- "traefik.http.middlewares.nextcloud-headers.headers.customResponseHeaders.X-Robots-Tag=noindex,nofollow"

View File

@@ -0,0 +1,55 @@
version: '3.8'
networks:
traefik-public:
external: true
volumes:
openwebui_data:
services:
openwebui:
image: ghcr.io/open-webui/open-webui:0.3.32
volumes:
- openwebui_data:/app/backend/data
networks:
- traefik-public
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
placement:
constraints:
- node.labels.heavy == true
resources:
limits:
memory: 4G
cpus: '4.0'
reservations:
memory: 2G
cpus: '1.0'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.openwebui.rule=Host(`ai.sj98.duckdns.org`)"
- "traefik.http.routers.openwebui.entrypoints=websecure"
- "traefik.http.routers.openwebui.tls.certresolver=leresolver"
- "traefik.http.services.openwebui.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=openwebui"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

View File

@@ -0,0 +1,409 @@
version: '3.8'
networks:
traefik-public:
external: true
homelab-backend:
driver: overlay
volumes:
paperless_data:
paperless_media:
paperless_db:
paperless_redis:
openwebui_data:
stirling_pdf_data:
searxng_data:
n8n_data:
secrets:
paperless_db_password:
external: true
paperless_secret_key:
external: true
services:
n8n:
image: n8nio/n8n:latest
volumes:
- n8n_data:/home/node/.n8n
- /var/run/docker.sock:/var/run/docker.sock
networks:
- traefik-public
environment:
- N8N_HOST=n8n.sj98.duckdns.org
- N8N_PROTOCOL=https
- NODE_ENV=production
- WEBHOOK_URL=https://n8n.sj98.duckdns.org/
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:5678/healthz || exit 1"]
interval: 30s
timeout: 10s
retries: 3
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 1G
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`n8n.sj98.duckdns.org`)"
- "traefik.http.routers.n8n.entrypoints=websecure"
- "traefik.http.routers.n8n.tls.certresolver=leresolver"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
- "traefik.docker.network=traefik-public"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
openwebui:
image: ghcr.io/open-webui/open-webui:0.3.32
volumes:
- openwebui_data:/app/backend/data
networks:
- traefik-public
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
placement:
constraints:
- node.labels.heavy == true
resources:
limits:
memory: 4G
cpus: '4.0'
reservations:
memory: 2G
cpus: '1.0'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.openwebui.rule=Host(`ai.sj98.duckdns.org`)"
- "traefik.http.routers.openwebui.entrypoints=websecure"
- "traefik.http.routers.openwebui.tls.certresolver=leresolver"
- "traefik.http.services.openwebui.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=openwebui"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
paperless-redis:
image: redis:7-alpine
volumes:
- paperless_redis:/data
networks:
- homelab-backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 3s
retries: 3
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 256M
cpus: '0.5'
reservations:
memory: 64M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
paperless-db:
image: postgres:15-alpine
volumes:
- paperless_db:/var/lib/postgresql/data
networks:
- homelab-backend
environment:
- POSTGRES_DB=paperless
- POSTGRES_USER=paperless
- POSTGRES_PASSWORD_FILE=/run/secrets/paperless_db_password
secrets:
- paperless_db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paperless"]
interval: 30s
timeout: 5s
retries: 3
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.25'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
paperless:
image: ghcr.io/paperless-ngx/paperless-ngx:2.19.3
volumes:
- paperless_data:/usr/src/paperless/data
- paperless_media:/usr/src/paperless/media
environment:
- PAPERLESS_REDIS=redis://paperless-redis:6379
- PAPERLESS_DBHOST=paperless-db
- PAPERLESS_DBNAME=paperless
- PAPERLESS_DBUSER=paperless
- PAPERLESS_DBPASS_FILE=/run/secrets/paperless_db_password
- PAPERLESS_URL=https://paperless.sj98.duckdns.org
- PAPERLESS_SECRET_KEY_FILE=/run/secrets/paperless_secret_key
- TZ=America/Chicago
secrets:
- paperless_db_password
- paperless_secret_key
depends_on:
- paperless-redis
- paperless-db
networks:
- traefik-public
- homelab-backend
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 90s
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 1536M
cpus: '2.0'
reservations:
memory: 768M
cpus: '0.5'
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.paperless.rule=Host(`paperless.sj98.duckdns.org`)"
- "traefik.http.routers.paperless.entrypoints=websecure"
- "traefik.http.routers.paperless.tls.certresolver=leresolver"
- "traefik.http.services.paperless.loadbalancer.server.port=8000"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=paperless"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
stirling-pdf:
image: frooodle/s-pdf:0.18.1
volumes:
- stirling_pdf_data:/configs
environment:
- DOCKER_ENABLE_SECURITY=false
- INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
- LANGS=en_US
networks:
- traefik-public
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 1536M
cpus: '2.0'
reservations:
memory: 768M
cpus: '0.5'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.pdf.rule=Host(`pdf.sj98.duckdns.org`)"
- "traefik.http.routers.pdf.entrypoints=websecure"
- "traefik.http.routers.pdf.tls.certresolver=leresolver"
- "traefik.http.services.pdf.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=pdf"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
searxng:
image: searxng/searxng:2024.11.20-e9f6095cc
volumes:
- searxng_data:/etc/searxng
environment:
- SEARXNG_BASE_URL=https://search.sj98.duckdns.org/
networks:
- traefik-public
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 1536M
cpus: '2.0'
reservations:
memory: 512M
cpus: '0.5'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.searxng.rule=Host(`search.sj98.duckdns.org`)"
- "traefik.http.routers.searxng.entrypoints=websecure"
- "traefik.http.routers.searxng.tls.certresolver=leresolver"
- "traefik.http.services.searxng.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=search"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
watchtower:
image: containrrr/watchtower:1.7.1
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DOCKER_API_VERSION=1.44
command: --cleanup --interval 86400
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 256M
cpus: '0.25'
reservations:
memory: 64M
cpus: '0.05'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
tsdproxy:
image: almeidapaulopt/tsdproxy:v0.5.1
networks:
- traefik-public
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /srv/tsdproxy/config/tsdproxy.yaml:/config/tsdproxy.yaml:ro
- /srv/tsdproxy/data:/data
deploy:
resources:
limits:
memory: 256M
cpus: '0.25'
reservations:
memory: 64M
cpus: '0.05'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.tsdproxy.rule=Host(`tsdproxy.sj98.duckdns.org`)"
- "traefik.http.routers.tsdproxy.entrypoints=websecure"
- "traefik.http.routers.tsdproxy.tls.certresolver=leresolver"
- "traefik.http.services.tsdproxy.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=tsdproxy"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

View File

@@ -0,0 +1,104 @@
version: '3.8'
networks:
traefik-public:
external: true
gitea-internal:
driver: overlay
attachable: true
volumes:
gitea_data:
gitea_db_data:
secrets:
gitea_db_password:
external: true
services:
gitea:
image: gitea/gitea:latest
volumes:
- gitea_data:/data
networks:
- traefik-public
- gitea-internal
ports:
- "2222:22"
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=gitea-db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD_FILE=/run/secrets/gitea_db_password
- GITEA__server__DOMAIN=git.sj98.duckdns.org
- GITEA__server__ROOT_URL=https://git.sj98.duckdns.org
- GITEA__server__SSH_DOMAIN=git.sj98.duckdns.org
- GITEA__server__SSH_PORT=2222
- GITEA__service__DISABLE_REGISTRATION=false
secrets:
- gitea_db_password
depends_on:
- gitea-db
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:3000 || exit 1"]
interval: 30s
timeout: 10s
retries: 3
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 1G
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.2'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.gitea.rule=Host(`git.sj98.duckdns.org`)"
- "traefik.http.routers.gitea.entrypoints=websecure"
- "traefik.http.routers.gitea.tls.certresolver=leresolver"
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
- "traefik.docker.network=traefik-public"
gitea-db:
image: postgres:15-alpine
volumes:
- gitea_db_data:/var/lib/postgresql/data
networks:
- gitea-internal
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD_FILE=/run/secrets/gitea_db_password
- POSTGRES_DB=gitea
secrets:
- gitea_db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitea"]
interval: 30s
timeout: 5s
retries: 3
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 128M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3

View File

@@ -0,0 +1,170 @@
version: '3.8'
networks:
traefik-public:
external: true
homelab-backend:
driver: overlay
volumes:
tsdproxy_config:
tsdproxy_data:
komodo_data:
komodo_mongo_data:
services:
komodo-mongo:
image: mongo:7
volumes:
- komodo_mongo_data:/data/db
networks:
- homelab-backend
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 128M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
komodo-core:
image: ghcr.io/moghtech/komodo:latest
depends_on:
- komodo-mongo
environment:
- KOMODO_DATABASE_ADDRESS=komodo-mongo:27017
volumes:
- komodo_data:/config
networks:
- traefik-public
- homelab-backend
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 128M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.komodo.rule=Host(`komodo.sj98.duckdns.org`)"
- "traefik.http.routers.komodo.entrypoints=websecure"
- "traefik.http.routers.komodo.tls.certresolver=leresolver"
- "traefik.http.services.komodo.loadbalancer.server.port=9120"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=komodo"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
komodo-periphery:
image: ghcr.io/moghtech/komodo-periphery:latest
environment:
- PERIPHERY_Id=periphery-{{.Node.Hostname}}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
deploy:
mode: global
resources:
limits:
memory: 128M
cpus: '0.5'
reservations:
memory: 32M
cpus: '0.05'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
watchtower:
image: containrrr/watchtower:1.7.1
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DOCKER_API_VERSION=1.44
command: --cleanup --interval 86400
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 256M
cpus: '0.25'
reservations:
memory: 64M
cpus: '0.05'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
tsdproxy:
image: almeidapaulopt/tsdproxy:v0.5.1
networks:
- traefik-public
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- tsdproxy_config:/config
- tsdproxy_data:/data
deploy:
resources:
limits:
memory: 256M
cpus: '0.25'
reservations:
memory: 64M
cpus: '0.05'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.tsdproxy.rule=Host(`tsdproxy.sj98.duckdns.org`)"
- "traefik.http.routers.tsdproxy.entrypoints=websecure"
- "traefik.http.routers.tsdproxy.tls.certresolver=leresolver"
- "traefik.http.services.tsdproxy.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=tsdproxy"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

View File

@@ -0,0 +1,5 @@
# Please replace claim-xxxxxxxxxxxx with your actual Plex claim token.
PLEX_CLAIM=claim-xxxxxxxxxxxx
# The ADVERTISE_IP is currently hardcoded in the docker-compose file.
# You may want to review it and change it to your actual IP address.

View File

@@ -0,0 +1,235 @@
version: '3.9'
networks:
traefik-public:
external: true
media-backend:
driver: overlay
volumes:
plex_config:
jellyfin_config:
immich_upload:
immich_model_cache:
immich_db:
immich_redis:
homarr_config:
services:
homarr:
image: ghcr.io/homarr-labs/homarr:1.43.0
networks:
- traefik-public
- media-backend
volumes:
- homarr_config:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- TZ=America/Chicago
deploy:
placement:
constraints:
- node.labels.leader == true
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.homarr-router.rule=Host(`homarr.sj98.duckdns.org`)"
- "traefik.http.routers.homarr-router.entrypoints=websecure"
- "traefik.http.routers.homarr-router.tls.certresolver=leresolver"
- "traefik.http.services.homarr.loadbalancer.server.port=7575"
- "traefik.docker.network=traefik-public"
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 128M
cpus: '0.2'
restart_policy:
condition: on-failure
max_attempts: 3
plex:
image: plexinc/pms-docker:latest
hostname: plex
networks:
- traefik-public
- media-backend
volumes:
- plex_config:/config
- /mnt/media:/media:ro
environment:
- TZ=America/Chicago
- PLEX_CLAIM=${PLEX_CLAIM}
- ADVERTISE_IP=http://192.168.1.196:32400/
deploy:
placement:
constraints:
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.plex-router.rule=Host(`plex.sj98.duckdns.org`)"
- "traefik.http.routers.plex-router.entrypoints=websecure"
- "traefik.http.routers.plex-router.tls.certresolver=leresolver"
- "traefik.http.services.plex.loadbalancer.server.port=32400"
- "traefik.docker.network=traefik-public"
resources:
limits:
memory: 1G
cpus: '2.0'
reservations:
memory: 512M
cpus: '0.5'
restart_policy:
condition: on-failure
max_attempts: 3
jellyfin:
image: jellyfin/jellyfin:latest
networks:
- traefik-public
- media-backend
volumes:
- jellyfin_config:/config
- /mnt/media:/media:ro
environment:
- TZ=America/Chicago
deploy:
placement:
constraints:
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.jellyfin-router.rule=Host(`jellyfin.sj98.duckdns.org`)"
- "traefik.http.routers.jellyfin-router.entrypoints=websecure"
- "traefik.http.routers.jellyfin-router.tls.certresolver=leresolver"
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
- "traefik.docker.network=traefik-public"
resources:
limits:
memory: 1G
cpus: '2.0'
reservations:
memory: 512M
cpus: '0.5'
restart_policy:
condition: on-failure
max_attempts: 3
immich-server:
image: ghcr.io/immich-app/immich-server:release
networks:
- traefik-public
- media-backend
volumes:
- immich_upload:/usr/src/app/upload
- /mnt/media/Photos:/usr/src/app/upload/library:rw
- /etc/localtime:/etc/localtime:ro
environment:
- DB_HOSTNAME=immich-db
- DB_USERNAME=immich
- DB_PASSWORD=immich
- DB_DATABASE_NAME=immich
- REDIS_HOSTNAME=immich-redis
- TZ=America/Chicago
- IMMICH_MEDIA_LOCATION=/usr/src/app/upload/library
depends_on:
- immich-redis
- immich-db
deploy:
placement:
constraints:
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.immich-server-router.rule=Host(`immich.sj98.duckdns.org`)"
- "traefik.http.routers.immich-server-router.entrypoints=websecure"
- "traefik.http.routers.immich-server-router.tls.certresolver=leresolver"
- "traefik.http.services.immich-server.loadbalancer.server.port=2283"
- "traefik.docker.network=traefik-public"
# Immich-specific headers and settings
- "traefik.http.routers.immich-server-router.middlewares=immich-headers"
- "traefik.http.middlewares.immich-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.services.immich-server.loadbalancer.passhostheader=true"
resources:
limits:
memory: 2G
cpus: '2.0'
reservations:
memory: 1G
cpus: '0.5'
restart_policy:
condition: on-failure
max_attempts: 3
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:release
networks:
- media-backend
volumes:
- immich_model_cache:/cache
environment:
- TZ=America/Chicago
depends_on:
- immich-server
deploy:
placement:
constraints:
- node.labels.heavy == true
- node.labels.ai == true
resources:
limits:
memory: 4G
cpus: '4.0'
reservations:
memory: 2G
cpus: '2.0'
restart_policy:
condition: on-failure
max_attempts: 3
immich-redis:
image: redis:7-alpine
networks:
- media-backend
volumes:
- immich_redis:/data
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 256M
cpus: '0.5'
reservations:
memory: 64M
cpus: '0.1'
restart_policy:
condition: on-failure
max_attempts: 3
immich-db:
image: tensorchord/pgvecto-rs:pg14-v0.2.0
networks:
- media-backend
volumes:
- immich_db:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=immich
- POSTGRES_USER=immich
- POSTGRES_DB=immich
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.25'
restart_policy:
condition: on-failure
max_attempts: 3

View File

@@ -0,0 +1,233 @@
version: '3.8'
networks:
traefik-public:
external: true
monitoring:
driver: overlay
volumes:
prometheus_data:
grafana_data:
alertmanager_data:
secrets:
grafana_admin_password:
external: true
configs:
prometheus_config:
external: true
name: prometheus.yml
services:
prometheus:
image: prom/prometheus:v3.0.1
volumes:
- prometheus_data:/prometheus
configs:
- source: prometheus_config
target: /etc/prometheus/prometheus.yml
networks:
- monitoring
- traefik-public
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9090/-/healthy"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 2G
cpus: '1.0'
reservations:
memory: 512M
cpus: '0.25'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.prometheus.rule=Host(`prometheus.sj98.duckdns.org`)"
- "traefik.http.routers.prometheus.entrypoints=websecure"
- "traefik.http.routers.prometheus.tls.certresolver=leresolver"
- "traefik.http.services.prometheus.loadbalancer.server.port=9090"
- "traefik.docker.network=traefik-public"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
grafana:
image: grafana/grafana:11.3.1
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SERVER_ROOT_URL=https://grafana.sj98.duckdns.org
- GF_SECURITY_ADMIN_PASSWORD__FILE=/run/secrets/grafana_admin_password
secrets:
- grafana_admin_password
networks:
- monitoring
- traefik-public
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 1G
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.25'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana.rule=Host(`grafana.sj98.duckdns.org`)"
- "traefik.http.routers.grafana.entrypoints=websecure"
- "traefik.http.routers.grafana.tls.certresolver=leresolver"
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
- "traefik.docker.network=traefik-public"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
alertmanager:
image: prom/alertmanager:v0.27.0
volumes:
- alertmanager_data:/alertmanager
command:
- '--config.file=/etc/alertmanager/config.yml'
- '--storage.path=/alertmanager'
networks:
- monitoring
- traefik-public
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9093/-/healthy"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 256M
cpus: '0.25'
reservations:
memory: 64M
cpus: '0.05'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.alertmanager.rule=Host(`alertmanager.sj98.duckdns.org`)"
- "traefik.http.routers.alertmanager.entrypoints=websecure"
- "traefik.http.routers.alertmanager.tls.certresolver=leresolver"
- "traefik.http.services.alertmanager.loadbalancer.server.port=9093"
- "traefik.docker.network=traefik-public"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
node-exporter:
image: prom/node-exporter:v1.8.2
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
networks:
- monitoring
deploy:
mode: global
resources:
limits:
memory: 128M
cpus: '0.2'
reservations:
memory: 32M
cpus: '0.05'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "2"
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.50.0
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
command:
- '--docker_only=true'
- '--housekeeping_interval=30s'
networks:
- monitoring
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
deploy:
mode: global
resources:
limits:
memory: 256M
cpus: '0.3'
reservations:
memory: 64M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "2"

View File

@@ -0,0 +1,54 @@
version: '3.8'
networks:
traefik-public:
external: true
volumes:
n8n_data:
services:
n8n:
image: n8nio/n8n:latest
volumes:
- n8n_data:/home/node/.n8n
- /var/run/docker.sock:/var/run/docker.sock
networks:
- traefik-public
environment:
- N8N_HOST=n8n.sj98.duckdns.org
- N8N_PROTOCOL=https
- NODE_ENV=production
- WEBHOOK_URL=https://n8n.sj98.duckdns.org/
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:5678/healthz || exit 1"]
interval: 30s
timeout: 10s
retries: 3
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 1G
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`n8n.sj98.duckdns.org`)"
- "traefik.http.routers.n8n.entrypoints=websecure"
- "traefik.http.routers.n8n.tls.certresolver=leresolver"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
- "traefik.docker.network=traefik-public"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

View File

@@ -0,0 +1,110 @@
version: '3.8'
networks:
traefik-public:
external: true
secrets:
duckdns_token:
external: true
volumes:
traefik_letsencrypt:
external: true
configs:
traefik_yml:
external: true
name: traefik.yml
services:
traefik:
image: traefik:v3.2.3
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_letsencrypt:/letsencrypt
networks:
- traefik-public
secrets:
- duckdns_token
configs:
- source: traefik_yml
target: /etc/traefik/traefik.yml
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
deploy:
mode: replicated
replicas: 2
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 128M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
order: start-first
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`traefik.sj98.duckdns.org`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=leresolver"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.services.traefik.loadbalancer.server.port=8080"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
whoami:
image: traefik/whoami:v1.10
networks:
- traefik-public
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/health"]
interval: 30s
timeout: 5s
retries: 3
deploy:
resources:
limits:
memory: 64M
cpus: '0.1'
reservations:
memory: 16M
cpus: '0.01'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.sj98.duckdns.org`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=leresolver"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "2"

View File

@@ -0,0 +1,38 @@
version: '3.8'
networks:
monitoring:
external: true
services:
node-exporter:
image: prom/node-exporter:v1.8.2
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
volumes:
- '/proc:/host/proc:ro'
- '/sys:/host/sys:ro'
- '/:/rootfs:ro,rslave'
networks:
- monitoring
deploy:
mode: global
resources:
limits:
memory: 128M
cpus: '0.2'
reservations:
memory: 32M
cpus: '0.05'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "2"

View File

@@ -0,0 +1,133 @@
version: '3.8'
networks:
traefik-public:
external: true
portainer-agent:
driver: overlay
attachable: true
volumes:
portainer_data:
services:
portainer:
image: portainer/portainer-ce:2.21.4
command: -H tcp://tasks.agent:9001 --tlsskipverify
ports:
- "9000:9000"
- "9443:9443"
volumes:
- portainer_data:/data
networks:
- traefik-public
- portainer-agent
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9000/api/status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
deploy:
mode: replicated
replicas: 1
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.portainer.rule=Host(`portainer.sj98.duckdns.org`)"
- "traefik.http.routers.portainer.entrypoints=websecure"
- "traefik.http.routers.portainer.tls.certresolver=leresolver"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
- "traefik.docker.network=traefik-public"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Linux agent
agent:
image: portainer/agent:2.21.4
environment:
AGENT_CLUSTER_ADDR: tasks.agent
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
networks:
- portainer-agent
deploy:
mode: global
placement:
constraints:
- node.platform.os == linux
resources:
limits:
memory: 128M
cpus: '0.25'
reservations:
memory: 64M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "2"
# Windows agent (optional - only deploys if Windows node exists)
agent-windows:
image: portainer/agent:2.21.4
environment:
AGENT_CLUSTER_ADDR: tasks.agent
volumes:
- type: npipe
source: \\\\.\\pipe\\docker_engine
target: \\\\.\\pipe\\docker_engine
- type: bind
source: C:\\ProgramData\\docker\\volumes
target: C:\\ProgramData\\docker\\volumes
networks:
portainer-agent:
aliases:
- agent
deploy:
mode: global
placement:
constraints:
- node.platform.os == windows
resources:
limits:
memory: 128M
cpus: '0.25'
reservations:
memory: 64M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "2"

View File

@@ -0,0 +1,4 @@
# Please replace these with your actual credentials
POSTGRES_PASSWORD=nextcloud
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=password

View File

@@ -0,0 +1,112 @@
version: '3.9'
networks:
traefik-public:
external: true
productivity-backend:
driver: overlay
volumes:
nextcloud_data:
nextcloud_db:
nextcloud_redis:
services:
nextcloud-db:
image: postgres:15-alpine
volumes:
- nextcloud_db:/var/lib/postgresql/data
environment:
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD} # Replace with a secure password in production
networks:
- productivity-backend
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 1G
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.25'
restart_policy:
condition: on-failure
nextcloud-redis:
image: redis:7-alpine
volumes:
- nextcloud_redis:/data
networks:
- productivity-backend
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 256M
cpus: '0.5'
reservations:
memory: 64M
cpus: '0.1'
restart_policy:
condition: on-failure
nextcloud:
image: nextcloud:30.0.8
volumes:
- nextcloud_data:/var/www/html
environment:
- POSTGRES_HOST=nextcloud-db
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD} # Replace with a secure password in production
- REDIS_HOST=nextcloud-redis
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER} # Replace with your desired admin username
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD} # Replace with a secure password
- NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.sj98.duckdns.org
- OVERWRITEPROTOCOL=https
- OVERWRITEHOST=nextcloud.sj98.duckdns.org
- TRUSTED_PROXIES=172.16.0.0/12
depends_on:
- nextcloud-db
- nextcloud-redis
networks:
- traefik-public
- productivity-backend
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 2G
reservations:
memory: 512M
restart_policy:
condition: on-failure
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.rule=Host(`nextcloud.sj98.duckdns.org`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=leresolver"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
- "traefik.docker.network=traefik-public"
# Nextcloud-specific middlewares
- "traefik.http.routers.nextcloud.middlewares=nextcloud-chain"
- "traefik.http.middlewares.nextcloud-chain.chain.middlewares=nextcloud-caldav,nextcloud-headers"
# CalDAV/CardDAV redirect
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.regex=^https://(.*)/.well-known/(card|cal)dav"
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.replacement=https://$$1/remote.php/dav/"
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.permanent=true"
# Security headers
- "traefik.http.middlewares.nextcloud-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.nextcloud-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.nextcloud-headers.headers.stsPreload=true"
- "traefik.http.middlewares.nextcloud-headers.headers.forceSTSHeader=true"
- "traefik.http.middlewares.nextcloud-headers.headers.customFrameOptionsValue=SAMEORIGIN"
- "traefik.http.middlewares.nextcloud-headers.headers.customResponseHeaders.X-Robots-Tag=noindex,nofollow"

View File

@@ -0,0 +1,253 @@
version: '3.8'
networks:
traefik-public:
external: true
homelab-backend:
driver: overlay
volumes:
paperless_data:
paperless_media:
paperless_db:
paperless_redis:
stirling_pdf_data:
searxng_data:
secrets:
paperless_db_password:
external: true
paperless_secret_key:
external: true
services:
paperless-redis:
image: redis:7-alpine
volumes:
- paperless_redis:/data
networks:
- homelab-backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 3s
retries: 3
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 256M
cpus: '0.5'
reservations:
memory: 64M
cpus: '0.1'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
paperless-db:
image: postgres:15-alpine
volumes:
- paperless_db:/var/lib/postgresql/data
networks:
- homelab-backend
environment:
- POSTGRES_DB=paperless
- POSTGRES_USER=paperless
- POSTGRES_PASSWORD_FILE=/run/secrets/paperless_db_password
secrets:
- paperless_db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paperless"]
interval: 30s
timeout: 5s
retries: 3
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.25'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
paperless:
image: ghcr.io/paperless-ngx/paperless-ngx:2.19.3
volumes:
- paperless_data:/usr/src/paperless/data
- paperless_media:/usr/src/paperless/media
environment:
- PAPERLESS_REDIS=redis://paperless-redis:6379
- PAPERLESS_DBHOST=paperless-db
- PAPERLESS_DBNAME=paperless
- PAPERLESS_DBUSER=paperless
- PAPERLESS_DBPASS_FILE=/run/secrets/paperless_db_password
- PAPERLESS_URL=https://paperless.sj98.duckdns.org
- PAPERLESS_SECRET_KEY_FILE=/run/secrets/paperless_secret_key
- TZ=America/Chicago
secrets:
- paperless_db_password
- paperless_secret_key
depends_on:
- paperless-redis
- paperless-db
networks:
- traefik-public
- homelab-backend
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 90s
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 1536M
cpus: '2.0'
reservations:
memory: 768M
cpus: '0.5'
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.paperless.rule=Host(`paperless.sj98.duckdns.org`)"
- "traefik.http.routers.paperless.entrypoints=websecure"
- "traefik.http.routers.paperless.tls.certresolver=leresolver"
- "traefik.http.services.paperless.loadbalancer.server.port=8000"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=paperless"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
stirling-pdf:
image: frooodle/s-pdf:0.18.1
volumes:
- stirling_pdf_data:/configs
environment:
- DOCKER_ENABLE_SECURITY=false
- INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
- LANGS=en_US
networks:
- traefik-public
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 1536M
cpus: '2.0'
reservations:
memory: 768M
cpus: '0.5'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.pdf.rule=Host(`pdf.sj98.duckdns.org`)"
- "traefik.http.routers.pdf.entrypoints=websecure"
- "traefik.http.routers.pdf.tls.certresolver=leresolver"
- "traefik.http.services.pdf.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=pdf"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
searxng:
image: searxng/searxng:2024.11.20-e9f6095cc
volumes:
- searxng_data:/etc/searxng
environment:
- SEARXNG_BASE_URL=https://search.sj98.duckdns.org/
networks:
- traefik-public
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
placement:
constraints:
- node.labels.leader == true
resources:
limits:
memory: 1536M
cpus: '2.0'
reservations:
memory: 512M
cpus: '0.5'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.searxng.rule=Host(`search.sj98.duckdns.org`)"
- "traefik.http.routers.searxng.entrypoints=websecure"
- "traefik.http.routers.searxng.tls.certresolver=leresolver"
- "traefik.http.services.searxng.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik-public"
- "tsdproxy.enable=true"
- "tsdproxy.name=search"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

View File

@@ -0,0 +1,45 @@
version: '3.8'
networks:
traefik-public:
external: true
services:
dozzle:
image: amir20/dozzle:v8.14.6
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- traefik-public
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/healthcheck"]
interval: 30s
timeout: 5s
retries: 3
deploy:
placement:
constraints:
- node.role == manager
resources:
limits:
memory: 256M
cpus: '0.25'
reservations:
memory: 64M
cpus: '0.05'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.dozzle.rule=Host(`dozzle.sj98.duckdns.org`)"
- "traefik.http.routers.dozzle.entrypoints=websecure"
- "traefik.http.routers.dozzle.tls.certresolver=leresolver"
- "traefik.http.services.dozzle.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik-public"
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "2"

View File

@@ -0,0 +1,2 @@
# Please replace with your actual TSDPROXY_AUTHKEY
TSDPROXY_AUTHKEY=tskey-auth-kUFWCyDau321CNTRL-Vdt9PFUDUqAb7iQYLvCjqAkhcnq3aTTtg

View File

@@ -0,0 +1,32 @@
version: '3.9'
networks:
traefik-public:
external: true
volumes:
tsdproxydata:
services:
tsdproxy:
image: almeidapaulopt/tsdproxy:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- tsdproxydata:/data
environment:
- TSDPROXY_AUTHKEY=${TSDPROXY_AUTHKEY}
- DOCKER_HOST=unix:///var/run/docker.sock
networks:
- traefik-public
deploy:
restart_policy:
condition: on-failure
placement:
constraints:
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.tsdproxy.rule=Host(`proxy.sj98.duckdns.org`)"
- "traefik.http.routers.tsdproxy.entrypoints=websecure"
- "traefik.http.routers.tsdproxy.tls.certresolver=leresolver"
- "traefik.http.services.tsdproxy.loadbalancer.server.port=8080"

View File

@@ -0,0 +1,29 @@
version: '3.8'
services:
traefik:
image: traefik:v2.10
command:
- --api.insecure=false
- --providers.docker=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --certificatesresolvers.leresolver.acme.email=sterlenjohnson6@gmail.com
- --certificatesresolvers.leresolver.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.leresolver.acme.dnschallenge=true
- --certificatesresolvers.leresolver.acme.dnschallenge.provider=duckdns
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /letsencrypt:/letsencrypt
deploy:
mode: replicated
replicas: 2
placement:
constraints: [node.role == manager]
networks:
- webnet
networks:
webnet:
driver: overlay

View File

@@ -0,0 +1,54 @@
# traefik.yml - static configuration (file provider)
checkNewVersion: true
sendAnonymousUsage: false
log:
level: INFO
api:
dashboard: true
insecure: false # set to true only for quick local testing (not recommended for public)
# single entryPoints section (merged)
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
# optional timeouts can live under transport as well (kept only on websecure below)
websecure:
address: ":443"
http:
tls:
certResolver: leresolver
transport:
respondingTimeouts:
# keep these large if you expect long uploads/downloads or long-lived requests
readTimeout: 600s
writeTimeout: 600s
idleTimeout: 600s
providers:
swarm:
endpoint: "unix:///var/run/docker.sock"
certificatesResolvers:
leresolver:
acme:
email: "sterlenjohnson6@gmail.com"
storage: "/letsencrypt/acme.json"
# DNS-01, using DuckDNS provider
dnsChallenge:
provider: duckdns
delayBeforeCheck: 60s
# Usually unnecessary to specify "resolvers" unless you have special internal resolvers.
# If you DO need Traefik to use specific DNS servers for the challenge, make sure
# the container has network access to them and that they will answer public DNS queries.
resolvers:
- "192.168.1.196:53"
- "192.168.1.245:53"
- "192.168.1.62:53"