Initial commit: homelab configuration and documentation
This commit is contained in:
36
services/standalone/Caddy/Caddyfile
Normal file
36
services/standalone/Caddy/Caddyfile
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
# Global options
|
||||
admin off
|
||||
}
|
||||
|
||||
# Main fallback server
|
||||
:80 {
|
||||
root * /srv/maintenance
|
||||
file_server
|
||||
|
||||
# Serve maintenance page for all requests
|
||||
handle {
|
||||
rewrite * /maintenance.html
|
||||
file_server
|
||||
}
|
||||
|
||||
# Log all requests
|
||||
log {
|
||||
output file /var/log/caddy/access.log
|
||||
}
|
||||
}
|
||||
|
||||
# Optional: HTTPS fallback (if you have certificates)
|
||||
:443 {
|
||||
root * /srv/maintenance
|
||||
file_server
|
||||
|
||||
handle {
|
||||
rewrite * /maintenance.html
|
||||
file_server
|
||||
}
|
||||
|
||||
log {
|
||||
output file /var/log/caddy/access.log
|
||||
}
|
||||
}
|
||||
27
services/standalone/Caddy/docker-compose.yml
Normal file
27
services/standalone/Caddy/docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
caddy:
|
||||
image: caddy:latest
|
||||
container_name: caddy_fallback
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:80"
|
||||
- "8443:443"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||
- ./maintenance.html:/srv/maintenance/maintenance.html
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
- caddy_logs:/var/log/caddy
|
||||
networks:
|
||||
- caddy_net
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
caddy_logs:
|
||||
|
||||
networks:
|
||||
caddy_net:
|
||||
driver: bridge
|
||||
68
services/standalone/Caddy/maintenance.html
Normal file
68
services/standalone/Caddy/maintenance.html
Normal file
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Service Maintenance</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 2rem;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔧 Maintenance Mode</h1>
|
||||
<p>Our services are temporarily unavailable due to maintenance or system updates.</p>
|
||||
<p>We'll be back online shortly. Thank you for your patience.</p>
|
||||
<div class="status">⏳ Please check back soon</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
34
services/standalone/MacOS/docker-compose.yaml
Normal file
34
services/standalone/MacOS/docker-compose.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
# https://github.com/dockur/macos
|
||||
services:
|
||||
macos:
|
||||
image: dockurr/macos
|
||||
container_name: macos
|
||||
environment:
|
||||
VERSION: "15"
|
||||
DISK_SIZE: "50G"
|
||||
RAM_SIZE: "6G"
|
||||
CPU_CORES: "4"
|
||||
# DHCP: "Y" # if enabled you must create a macvlan
|
||||
devices:
|
||||
- /dev/kvm
|
||||
- /dev/net/tun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
- 8006:8006
|
||||
- 5900:5900/tcp
|
||||
- 5900:5900/udp
|
||||
volumes:
|
||||
- ./macos:/storage
|
||||
restart: always
|
||||
stop_grace_period: 2m
|
||||
networks:
|
||||
macos:
|
||||
ipv4_address: 172.70.20.3
|
||||
|
||||
networks:
|
||||
macos:
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.70.20.0/29
|
||||
name: macos
|
||||
107
services/standalone/Nextcloud/docker-compose.yml
Normal file
107
services/standalone/Nextcloud/docker-compose.yml
Normal file
@@ -0,0 +1,107 @@
|
||||
# Place this at ~/docker/docker-compose.yml (overwrite existing if ready)
|
||||
# NOTE: the top-level "version" key is optional in modern Compose v2/v3 usage.
|
||||
services:
|
||||
tsdproxy:
|
||||
image: almeidapaulopt/tsdproxy:1
|
||||
container_name: tsdproxy
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- tsd_data:/data
|
||||
- ./tsdproxy/config:/config
|
||||
ports:
|
||||
- "8080:8080"
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
environment:
|
||||
# You may optionally set an auth key here, or add it to /config/tsdproxy.yaml later
|
||||
TAILSCALE_AUTHKEY: "tskey-auth-kUFWCyDau321CNTRL-Vdt9PFUDUqAb7iQYLvCjqAkhcnq3aTTtg" # (optional — recommended to use config file)
|
||||
TS_EXTRA_ARGS: "--accept-routes"
|
||||
|
||||
db:
|
||||
image: mariadb:11
|
||||
container_name: nextcloud-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecurepassword
|
||||
MYSQL_DATABASE: nextcloud
|
||||
MYSQL_USER: nextcloud
|
||||
MYSQL_PASSWORD: nextcloudpassword
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
|
||||
nextcloud:
|
||||
image: nextcloud:29
|
||||
container_name: nextcloud-app
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- db
|
||||
environment:
|
||||
MYSQL_HOST: db
|
||||
MYSQL_DATABASE: nextcloud
|
||||
MYSQL_USER: nextcloud
|
||||
MYSQL_PASSWORD: nextcloudpassword
|
||||
volumes:
|
||||
- /mnt/nextcloud-data:/var/www/html/data
|
||||
- /mnt/nextcloud-config:/var/www/html/config
|
||||
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=letsencrypt"
|
||||
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
|
||||
- "tsdproxy.enable=true"
|
||||
- "tsdproxy.name=nextcloud"
|
||||
|
||||
plex:
|
||||
image: lscr.io/linuxserver/plex:latest
|
||||
container_name: plex
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
environment:
|
||||
PLEX_CLAIM: claim-your-plex-claim
|
||||
PUID: 1000
|
||||
PGID: 1000
|
||||
TZ: America/Chicago
|
||||
volumes:
|
||||
- /mnt/media:/media
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.tcp.routers.plex.rule=HostSNI(`plex.sj98.duckdns.org`)"
|
||||
- "traefik.tcp.routers.plex.entrypoints=websecure"
|
||||
- "traefik.tcp.services.plex.loadbalancer.server.port=32400"
|
||||
- "tsdproxy.enable=true"
|
||||
- "tsdproxy.name=plex"
|
||||
|
||||
jellyfin:
|
||||
image: jellyfin/jellyfin:latest
|
||||
container_name: jellyfin
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
environment:
|
||||
PUID: 1000
|
||||
PGID: 1000
|
||||
TZ: America/Chicago
|
||||
volumes:
|
||||
- /mnt/media:/media
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.tcp.routers.jellyfin.rule=HostSNI(`jellyfin.sj98.duckdns.org`)"
|
||||
- "traefik.tcp.routers.jellyfin.entrypoints=websecure"
|
||||
- "traefik.tcp.services.jellyfin.loadbalancer.server.port=8096"
|
||||
- "tsdproxy.enable=true"
|
||||
- "tsdproxy.name=jellyfin"
|
||||
|
||||
watchtower:
|
||||
image: containrrr/watchtower
|
||||
container_name: watchtower
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
command: --interval 3600
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
tsd_data:
|
||||
87
services/standalone/Paperless/docker-compose.yaml
Normal file
87
services/standalone/Paperless/docker-compose.yaml
Normal file
@@ -0,0 +1,87 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
networks:
|
||||
- web
|
||||
|
||||
db:
|
||||
image: docker.io/library/postgres:15
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_DB: paperless
|
||||
POSTGRES_USER: paperless
|
||||
POSTGRES_PASSWORD: paperless
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB} || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- web
|
||||
|
||||
webserver:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- db
|
||||
- broker
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
- ./export:/usr/src/paperless/export
|
||||
- ./consume:/usr/src/paperless/consume
|
||||
environment:
|
||||
PAPERLESS_DBHOST: db
|
||||
PAPERLESS_DBNAME: paperless
|
||||
PAPERLESS_DBUSER: paperless
|
||||
PAPERLESS_DBPASS: paperless
|
||||
PAPERLESS_REDIS: redis://broker:6379/0
|
||||
PAPERLESS_TIME_ZONE: "America/Chicago"
|
||||
PAPERLESS_SECRET_KEY: "replace-with-a-64-char-random-string"
|
||||
PAPERLESS_ADMIN_USER: admin@example.local
|
||||
PAPERLESS_ADMIN_PASSWORD: changeme
|
||||
PAPERLESS_ALLOWED_HOSTS: '["paperless.sj98.duckdns.org"]'
|
||||
PAPERLESS_CSRF_TRUSTED_ORIGINS: '["https://paperless.sj98.duckdns.org"]'
|
||||
|
||||
# Add / adjust these for running behind Traefik:
|
||||
PAPERLESS_URL: "https://paperless.sj98.duckdns.org" # required/preferred
|
||||
PAPERLESS_PROXY_SSL_HEADER: '["HTTP_X_FORWARDED_PROTO","https"]' # tells Django to treat X-Forwarded-Proto=https as TLS
|
||||
PAPERLESS_USE_X_FORWARD_HOST: "true" # optional, can help URL generation
|
||||
PAPERLESS_USE_X_FORWARD_PORT: "true" # optional
|
||||
# Optional: restrict trusted proxies to your docker network or Traefik IP
|
||||
# PAPERLESS_TRUSTED_PROXIES: "172.18.0.0/16" # <-- replace with your web network subnet or Traefik IP if you want to lock down
|
||||
networks:
|
||||
- web
|
||||
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=true"
|
||||
- "traefik.http.routers.paperless.tls.certresolver=duckdns"
|
||||
- "traefik.http.services.paperless.loadbalancer.server.port=8000"
|
||||
- "tsdproxy.enable=true"
|
||||
- "tsdproxy.name=paperless"
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
pgdata:
|
||||
redisdata:
|
||||
|
||||
networks:
|
||||
web:
|
||||
external: true
|
||||
14
services/standalone/Portainer Agent/docker-compose.yml
Normal file
14
services/standalone/Portainer Agent/docker-compose.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
portainer-agent:
|
||||
image: portainer/agent:latest
|
||||
container_name: portainer-agent
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /var/lib/docker/volumes:/var/lib/docker/volumes
|
||||
environment:
|
||||
AGENT_CLUSTER_ADDR: 192.168.1.81 # Replace with the actual IP address
|
||||
AGENT_PORT: 9001
|
||||
ports:
|
||||
- "9001:9001" # Port for agent communication
|
||||
restart: always
|
||||
39
services/standalone/RustDesk/docker-compose.yml
Normal file
39
services/standalone/RustDesk/docker-compose.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
rustdesk-hbbs:
|
||||
image: rustdesk/rustdesk-server:latest
|
||||
container_name: rustdesk-hbbs
|
||||
restart: unless-stopped
|
||||
platform: linux/arm64
|
||||
command: ["hbbs", "--relay-servers", "192.168.1.245:21117"]
|
||||
volumes:
|
||||
- rustdesk_data:/root
|
||||
ports:
|
||||
- "21115:21115/tcp"
|
||||
- "21115:21115/udp"
|
||||
- "21116:21116/tcp"
|
||||
- "21116:21116/udp"
|
||||
|
||||
rustdesk-hbbr:
|
||||
image: rustdesk/rustdesk-server:latest
|
||||
container_name: rustdesk-hbbr
|
||||
restart: unless-stopped
|
||||
platform: linux/arm64
|
||||
command: ["hbbr"]
|
||||
volumes:
|
||||
- rustdesk_data:/root
|
||||
ports:
|
||||
- "21117:21117/tcp"
|
||||
- "21118:21118/udp"
|
||||
- "21119:21119/tcp"
|
||||
- "21119:21119/udp"
|
||||
environment:
|
||||
- TOTAL_BANDWIDTH=20480
|
||||
- SINGLE_BANDWIDTH=128
|
||||
- LIMIT_SPEED=100Mb/s
|
||||
- DOWNGRADE_START_CHECK=600
|
||||
- DOWNGRADE_THRESHOLD=0.9
|
||||
|
||||
volumes:
|
||||
rustdesk_data:
|
||||
53
services/standalone/Traefik/docker-compose.yml
Normal file
53
services/standalone/Traefik/docker-compose.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:latest
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# Replace this placeholder with your DuckDNS token
|
||||
- DUCKDNS_TOKEN=03a4d8f7-695a-4f51-b66c-cc2fac555fc1
|
||||
networks:
|
||||
- web
|
||||
ports:
|
||||
- "80:80" # http
|
||||
- "443:443" # https
|
||||
- "8089:8089" # traefik dashboard (secure it if exposed)
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./letsencrypt:/letsencrypt # <-- keep this directory inside WSL filesystem
|
||||
- ./traefik_dynamic.yml:/etc/traefik/traefik_dynamic.yml:ro
|
||||
command:
|
||||
|
||||
- --api.insecure=false
|
||||
- --api.dashboard=true
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.dashboard.address=:8089
|
||||
- --providers.docker=true
|
||||
- --providers.docker.endpoint=unix:///var/run/docker.sock
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.file.filename=/etc/traefik/traefik_dynamic.yml
|
||||
- --providers.file.watch=true
|
||||
- --certificatesresolvers.duckdns.acme.email=sterlenjohnson6@gmail.com
|
||||
- --certificatesresolvers.duckdns.acme.storage=/letsencrypt/acme.json
|
||||
- --certificatesresolvers.duckdns.acme.dnschallenge.provider=duckdns
|
||||
- --certificatesresolvers.duckdns.acme.dnschallenge.disablepropagationcheck=true
|
||||
|
||||
whoami:
|
||||
image: containous/whoami:latest
|
||||
container_name: whoami
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
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=true"
|
||||
- "traefik.http.routers.whoami.tls.certresolver=duckdns"
|
||||
|
||||
networks:
|
||||
web:
|
||||
external: true
|
||||
41
services/standalone/Traefik/letsencrypt/acme.json
Normal file
41
services/standalone/Traefik/letsencrypt/acme.json
Normal file
File diff suppressed because one or more lines are too long
18
services/standalone/Traefik/traefik_dynamic.yml
Normal file
18
services/standalone/Traefik/traefik_dynamic.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
# traefik_dynamic.yml
|
||||
http:
|
||||
routers:
|
||||
traefik-dashboard:
|
||||
entryPoints:
|
||||
- dashboard
|
||||
rule: "Host(`localhost`) && (PathPrefix(`/dashboard`) || PathPrefix(`/`))"
|
||||
service: "api@internal"
|
||||
middlewares:
|
||||
- dashboard-auth
|
||||
|
||||
middlewares:
|
||||
dashboard-auth:
|
||||
basicAuth:
|
||||
# replace the example hash below with a hash you generate (see step 3)
|
||||
users:
|
||||
- "admin:$2y$05$8CZrANjYoKRm5VG6QO8kseVpumnDXnLDU2vREgfMm9F/JdsTpq.iy"
|
||||
- "Sterl:$2y$05$t8LnSDA190LOs2Wpmbt/p.7dFHzZKDT4BMLjSjqsxg0i6re5I9wlm"
|
||||
198
services/swarm/omv_volume_stacks/docker-swarm-media-stack.yml
Normal file
198
services/swarm/omv_volume_stacks/docker-swarm-media-stack.yml
Normal 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
|
||||
54
services/swarm/omv_volume_stacks/networking-stack.yml
Normal file
54
services/swarm/omv_volume_stacks/networking-stack.yml
Normal 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"
|
||||
100
services/swarm/omv_volume_stacks/productivity-stack.yml
Normal file
100
services/swarm/omv_volume_stacks/productivity-stack.yml
Normal 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"
|
||||
55
services/swarm/stacks/ai.yml
Normal file
55
services/swarm/stacks/ai.yml
Normal 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"
|
||||
409
services/swarm/stacks/full-stack-complete.yml
Normal file
409
services/swarm/stacks/full-stack-complete.yml
Normal 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"
|
||||
104
services/swarm/stacks/gitea-stack.yml
Normal file
104
services/swarm/stacks/gitea-stack.yml
Normal 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
|
||||
170
services/swarm/stacks/infrastructure.yml
Normal file
170
services/swarm/stacks/infrastructure.yml
Normal 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"
|
||||
5
services/swarm/stacks/media-stack.env
Normal file
5
services/swarm/stacks/media-stack.env
Normal 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.
|
||||
235
services/swarm/stacks/media-stack.yml
Normal file
235
services/swarm/stacks/media-stack.yml
Normal 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
|
||||
233
services/swarm/stacks/monitoring-stack.yml
Normal file
233
services/swarm/stacks/monitoring-stack.yml
Normal 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"
|
||||
54
services/swarm/stacks/n8n-stack.yml
Normal file
54
services/swarm/stacks/n8n-stack.yml
Normal 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"
|
||||
110
services/swarm/stacks/networking-stack.yml
Normal file
110
services/swarm/stacks/networking-stack.yml
Normal 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"
|
||||
38
services/swarm/stacks/node-exporter-stack.yml
Normal file
38
services/swarm/stacks/node-exporter-stack.yml
Normal 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"
|
||||
133
services/swarm/stacks/portainer-stack.yml
Normal file
133
services/swarm/stacks/portainer-stack.yml
Normal 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"
|
||||
4
services/swarm/stacks/productivity-stack.env
Normal file
4
services/swarm/stacks/productivity-stack.env
Normal file
@@ -0,0 +1,4 @@
|
||||
# Please replace these with your actual credentials
|
||||
POSTGRES_PASSWORD=nextcloud
|
||||
NEXTCLOUD_ADMIN_USER=admin
|
||||
NEXTCLOUD_ADMIN_PASSWORD=password
|
||||
112
services/swarm/stacks/productivity-stack.yml
Normal file
112
services/swarm/stacks/productivity-stack.yml
Normal 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"
|
||||
253
services/swarm/stacks/productivity.yml
Normal file
253
services/swarm/stacks/productivity.yml
Normal 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"
|
||||
45
services/swarm/stacks/tools-stack.yml
Normal file
45
services/swarm/stacks/tools-stack.yml
Normal 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"
|
||||
2
services/swarm/stacks/tsdproxy-stack.env
Normal file
2
services/swarm/stacks/tsdproxy-stack.env
Normal file
@@ -0,0 +1,2 @@
|
||||
# Please replace with your actual TSDPROXY_AUTHKEY
|
||||
TSDPROXY_AUTHKEY=tskey-auth-kUFWCyDau321CNTRL-Vdt9PFUDUqAb7iQYLvCjqAkhcnq3aTTtg
|
||||
32
services/swarm/stacks/tsdproxy-stack.yml
Normal file
32
services/swarm/stacks/tsdproxy-stack.yml
Normal 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"
|
||||
29
services/swarm/traefik/stack.yml
Normal file
29
services/swarm/traefik/stack.yml
Normal 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
|
||||
54
services/swarm/traefik/traefik.yml
Normal file
54
services/swarm/traefik/traefik.yml
Normal 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"
|
||||
Reference in New Issue
Block a user