feat: create net stack consolidating proxy, netdata, watchtower, and umami

- Create net/compose.yaml with 4 services (traefik, netdata, watchtower, umami)
- Update arty.yml with NET_* environment variables
- Update compose.yaml to include net instead of individual stacks
- Update restic volume references to net_letsencrypt_data and net_netdata_config
- Copy configuration files to net/ directory (Dockerfile, dynamic/, go.d/, etc.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-15 17:00:36 +01:00
parent f0ab11502a
commit 23fbae0228
10 changed files with 404 additions and 25 deletions

View File

@@ -19,12 +19,6 @@ envs:
CORE_REDIS_PORT: 6379 CORE_REDIS_PORT: 6379
CORE_REDIS_IMAGE: redis:7-alpine CORE_REDIS_IMAGE: redis:7-alpine
CORE_POSTGRES_IMAGE: postgres:16-alpine CORE_POSTGRES_IMAGE: postgres:16-alpine
# Track
TRACK_TRAEFIK_ENABLED: true
TRACK_COMPOSE_PROJECT_NAME: track
TRACK_DOCKER_IMAGE: ghcr.io/umami-software/umami:postgresql-latest
TRACK_TRAEFIK_HOST: umami.pivoine.art
TRACK_DB_NAME: umami
# Sexy # Sexy
SEXY_TRAEFIK_ENABLED: true SEXY_TRAEFIK_ENABLED: true
SEXY_COMPOSE_PROJECT_NAME: sexy SEXY_COMPOSE_PROJECT_NAME: sexy
@@ -123,20 +117,23 @@ envs:
DEV_ASCIINEMA_TRAEFIK_HOST: asciinema.dev.pivoine.art DEV_ASCIINEMA_TRAEFIK_HOST: asciinema.dev.pivoine.art
DEV_ASCIINEMA_DB_NAME: asciinema DEV_ASCIINEMA_DB_NAME: asciinema
DEV_ASCIINEMA_SIGN_UP_DISABLED: true DEV_ASCIINEMA_SIGN_UP_DISABLED: true
# PairDrop # Net (Traefik Proxy, Netdata, Watchtower, Umami)
DROP_TRAEFIK_ENABLED: true NET_TRAEFIK_ENABLED: true
DROP_COMPOSE_PROJECT_NAME: drop NET_COMPOSE_PROJECT_NAME: net
DROP_TRAEFIK_HOST: drop.pivoine.art # Traefik Proxy
NET_PROXY_DOCKER_IMAGE: traefik:latest
NET_PROXY_TRAEFIK_HOST: proxy.pivoine.art
NET_PROXY_PORT_HTTP: 80
NET_PROXY_PORT_HTTPS: 443
NET_PROXY_LOG_LEVEL: INFO
# Netdata # Netdata
NETDATA_TRAEFIK_ENABLED: true NET_NETDATA_IMAGE: netdata/netdata:latest
NETDATA_COMPOSE_PROJECT_NAME: netdata NET_NETDATA_TRAEFIK_HOST: netdata.pivoine.art
NETDATA_IMAGE: netdata/netdata:latest NET_NETDATA_HOSTNAME: netdata.pivoine.art
NETDATA_TRAEFIK_HOST: netdata.pivoine.art # Umami Analytics
NETDATA_HOSTNAME: netdata.pivoine.art NET_TRACK_DOCKER_IMAGE: ghcr.io/umami-software/umami:postgresql-latest
# Proxy NET_TRACK_TRAEFIK_HOST: umami.pivoine.art
PROXY_COMPOSE_PROJECT_NAME: proxy NET_TRACK_DB_NAME: umami
PROXY_DOCKER_IMAGE: traefik:latest
PROXY_TRAEFIK_HOST: proxy.pivoine.art
# AI Stack # AI Stack
AI_TRAEFIK_ENABLED: true AI_TRAEFIK_ENABLED: true
AI_COMPOSE_PROJECT_NAME: ai AI_COMPOSE_PROJECT_NAME: ai

View File

@@ -5,10 +5,7 @@ include:
- util/compose.yaml - util/compose.yaml
- ai/compose.yaml - ai/compose.yaml
- restic/compose.yaml - restic/compose.yaml
- netdata/compose.yaml - net/compose.yaml
- umami/compose.yaml
- proxy/compose.yaml
- watch/compose.yaml
- media/compose.yaml - media/compose.yaml
- dev/compose.yaml - dev/compose.yaml

11
net/Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
# Dockerfile for Netdata with msmtp support for email alerts
FROM netdata/netdata:latest
# Install msmtp for sending emails
RUN apt-get update && \
apt-get install -y msmtp msmtp-mta ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Set proper permissions for msmtp config
RUN chmod 600 /etc/msmtprc || true

238
net/compose.yaml Normal file
View File

@@ -0,0 +1,238 @@
services:
# Traefik - Reverse proxy and load balancer
traefik:
image: ${NET_PROXY_DOCKER_IMAGE}
container_name: ${NET_COMPOSE_PROJECT_NAME}_traefik
restart: unless-stopped
command:
# API & Dashboard
- '--api.dashboard=true'
- '--api.insecure=false'
# Ping endpoint for healthcheck
- '--ping=true'
# Experimental plugins
- '--experimental.plugins.sablier.modulename=github.com/acouvreur/sablier'
- '--experimental.plugins.sablier.version=v1.8.0'
# Logging
- '--log.level=${NET_PROXY_LOG_LEVEL:-INFO}'
- '--accesslog=true'
# Global
- '--global.sendAnonymousUsage=false'
- '--global.checkNewVersion=true'
# Docker Provider
- '--providers.docker=true'
- '--providers.docker.exposedbydefault=false'
- '--providers.docker.network=${NETWORK_NAME}'
# File Provider for dynamic configuration
- '--providers.file.directory=/etc/traefik/dynamic'
- '--providers.file.watch=true'
# Entrypoints
- '--entrypoints.web.address=:${NET_PROXY_PORT_HTTP:-80}'
- '--entrypoints.web-secure.address=:${NET_PROXY_PORT_HTTPS:-443}'
# Global HTTP to HTTPS redirect
- '--entrypoints.web.http.redirections.entryPoint.to=web-secure'
- '--entrypoints.web.http.redirections.entryPoint.scheme=https'
- '--entrypoints.web.http.redirections.entryPoint.permanent=true'
# Security Headers (applied globally)
- '--entrypoints.web-secure.http.middlewares=security-headers@file'
# Let's Encrypt
- '--certificatesresolvers.resolver.acme.tlschallenge=true'
- '--certificatesresolvers.resolver.acme.email=${ADMIN_EMAIL}'
- '--certificatesresolvers.resolver.acme.storage=/letsencrypt/acme.json'
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
environment:
AUTH_USERS: ${AUTH_USERS}
networks:
- compose_network
ports:
- "${NET_PROXY_PORT_HTTP:-80}:80"
- "${NET_PROXY_PORT_HTTPS:-443}:443"
volumes:
- letsencrypt_data:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./dynamic:/etc/traefik/dynamic:ro
labels:
- 'traefik.enable=true'
# HTTP to HTTPS redirect
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-traefik-redirect-web-secure.redirectscheme.scheme=https'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-traefik-redirect-web-secure'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.rule=Host(`${NET_PROXY_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.entrypoints=web'
# HTTPS router with auth
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.rule=Host(`${NET_PROXY_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.tls.certresolver=resolver'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.entrypoints=web-secure'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.service=api@internal'
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-traefik-auth.basicauth.users=${AUTH_USERS}'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.middlewares=${NET_COMPOSE_PROJECT_NAME}-traefik-auth'
- 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.loadbalancer.server.port=8080'
- 'traefik.docker.network=${NETWORK_NAME}'
# Netdata - Real-time monitoring
netdata:
build:
context: .
dockerfile: Dockerfile
image: ${NET_NETDATA_IMAGE:-netdata/netdata:latest}
container_name: ${NET_COMPOSE_PROJECT_NAME}_netdata
restart: unless-stopped
hostname: ${NET_NETDATA_HOSTNAME:-netdata.pivoine.art}
cap_add:
- SYS_PTRACE
- SYS_ADMIN
security_opt:
- apparmor:unconfined
volumes:
- netdata_config:/etc/netdata
- netdata_lib:/var/lib/netdata
- netdata_cache:/var/cache/netdata
- ./go.d/postgres.conf:/etc/netdata/go.d/postgres.conf:ro
- ./go.d/filecheck.conf:/etc/netdata/go.d/filecheck.conf:ro
- ./health_alarm_notify.conf:/etc/netdata/health_alarm_notify.conf:ro
- ./msmtprc:/etc/netdata/msmtprc:ro
- /mnt/hidrive/users/valknar/Backup:/mnt/hidrive/users/valknar/Backup:ro
- /etc/passwd:/host/etc/passwd:ro
- /etc/group:/host/etc/group:ro
- /etc/localtime:/etc/localtime:ro
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /etc/os-release:/host/etc/os-release:ro
- /var/log:/host/var/log:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- NETDATA_CLAIM_TOKEN=${NETDATA_CLAIM_TOKEN:-}
- NETDATA_CLAIM_URL=${NETDATA_CLAIM_URL:-}
- NETDATA_CLAIM_ROOMS=${NETDATA_CLAIM_ROOMS:-}
- MATTERMOST_WEBHOOK_URL=${MATTERMOST_WEBHOOK_URL:-}
networks:
- compose_network
labels:
- 'traefik.enable=${NET_TRAEFIK_ENABLED}'
# HTTP to HTTPS redirect
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-netdata-redirect-web-secure.redirectscheme.scheme=https'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-netdata-redirect-web-secure'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.rule=Host(`${NET_NETDATA_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.entrypoints=web'
# HTTPS router
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.rule=Host(`${NET_NETDATA_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.tls.certresolver=resolver'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.entrypoints=web-secure'
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-netdata-compress.compress=true'
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-netdata-auth.basicauth.users=${AUTH_USERS}'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.middlewares=${NET_COMPOSE_PROJECT_NAME}-netdata-auth,${NET_COMPOSE_PROJECT_NAME}-netdata-compress,security-headers@file'
# Service
- 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-netdata.loadbalancer.server.port=19999'
- 'traefik.docker.network=${NETWORK_NAME}'
# Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}'
# Watchtower - Automatic container updates
watchtower:
image: containrrr/watchtower:latest
container_name: ${NET_COMPOSE_PROJECT_NAME}_watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
# Check for updates every 5 minutes (300 seconds)
WATCHTOWER_POLL_INTERVAL: ${WATCHTOWER_POLL_INTERVAL:-300}
# Only update containers with the watchtower label
WATCHTOWER_LABEL_ENABLE: ${WATCHTOWER_LABEL_ENABLE:-true}
# Clean up old images after update
WATCHTOWER_CLEANUP: ${WATCHTOWER_CLEANUP:-true}
# Include stopped containers
WATCHTOWER_INCLUDE_STOPPED: ${WATCHTOWER_INCLUDE_STOPPED:-false}
# Include restarting containers
WATCHTOWER_INCLUDE_RESTARTING: ${WATCHTOWER_INCLUDE_RESTARTING:-true}
# Run once and exit (set to false for continuous monitoring)
WATCHTOWER_RUN_ONCE: ${WATCHTOWER_RUN_ONCE:-false}
# Notifications via Shoutrrr
WATCHTOWER_NOTIFICATIONS: ${WATCHTOWER_NOTIFICATIONS:-}
WATCHTOWER_NOTIFICATION_URL: ${WATCHTOWER_NOTIFICATION_URL:-}
# Log level (trace, debug, info, warn, error, fatal, panic)
WATCHTOWER_LOG_LEVEL: ${WATCHTOWER_LOG_LEVEL:-info}
# Rolling restart (update one container at a time)
WATCHTOWER_ROLLING_RESTART: ${WATCHTOWER_ROLLING_RESTART:-false}
labels:
# Allow watchtower to update itself
- com.centurylinklabs.watchtower.enable=true
# Umami - Web analytics
umami:
image: ${NET_TRACK_DOCKER_IMAGE}
container_name: ${NET_COMPOSE_PROJECT_NAME}_umami
restart: unless-stopped
environment:
TZ: ${TIMEZONE:-Europe/Amsterdam}
# Database Configuration
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@${CORE_DB_HOST}:${CORE_DB_PORT}/${NET_TRACK_DB_NAME}
DATABASE_TYPE: postgresql
# Application Secret
APP_SECRET: ${TRACK_APP_SECRET}
# Redis Cache Integration
REDIS_URL: redis://${CORE_REDIS_HOST}:${CORE_REDIS_PORT}
CACHE_ENABLED: true
networks:
- compose_network
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 40s
labels:
# Traefik Configuration
- 'traefik.enable=${NET_TRAEFIK_ENABLED}'
# HTTP to HTTPS redirect
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-umami-redirect-web-secure.redirectscheme.scheme=https'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-umami-redirect-web-secure'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.rule=Host(`${NET_TRACK_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.entrypoints=web'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.rule=Host(`${NET_TRACK_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.tls.certresolver=resolver'
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.entrypoints=web-secure'
- 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.loadbalancer.server.port=3000'
- 'traefik.docker.network=${NETWORK_NAME}'
volumes:
letsencrypt_data:
name: ${NET_COMPOSE_PROJECT_NAME}_letsencrypt_data
netdata_config:
name: ${NET_COMPOSE_PROJECT_NAME}_netdata_config
netdata_lib:
name: ${NET_COMPOSE_PROJECT_NAME}_netdata_lib
netdata_cache:
name: ${NET_COMPOSE_PROJECT_NAME}_netdata_cache
networks:
compose_network:
name: ${NETWORK_NAME}
external: true

61
net/dynamic/security.yaml Normal file
View File

@@ -0,0 +1,61 @@
tls:
options:
default:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
- TLS_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
curvePreferences:
- CurveP521
- CurveP384
sniStrict: true
http:
middlewares:
# Security Headers Middleware
security-headers:
headers:
# HSTS (HTTP Strict Transport Security)
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
# Force HTTPS
forceSTSHeader: true
# Clickjacking protection
customFrameOptionsValue: "SAMEORIGIN"
# XSS Protection
browserXssFilter: true
# Content Type sniffing protection
contentTypeNosniff: true
# Referrer Policy
referrerPolicy: "strict-origin-when-cross-origin"
# Permissions Policy (formerly Feature Policy)
customResponseHeaders:
X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex"
Permissions-Policy: "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), accelerometer=(), gyroscope=()"
X-Content-Type-Options: "nosniff"
X-Frame-Options: "SAMEORIGIN"
# Rate Limiting Middleware (optional, can be applied per service)
rate-limit:
rateLimit:
average: 100
burst: 50
period: 1s
# Rate Limiting for API endpoints (stricter)
api-rate-limit:
rateLimit:
average: 30
burst: 15
period: 1s

5
net/go.d/filecheck.conf Normal file
View File

@@ -0,0 +1,5 @@
jobs:
- name: restic_repository
dirs:
include:
- '/mnt/hidrive/users/valknar/Backup'

3
net/go.d/postgres.conf Normal file
View File

@@ -0,0 +1,3 @@
jobs:
- name: docker_core_postgres
dsn: 'postgres://netdata:netdata_monitor_password@172.18.0.5:5432/postgres'

View File

@@ -0,0 +1,47 @@
# Netdata health alarm notification configuration
# This file configures where to send alarm notifications
# Enable/disable sending email notifications
SEND_EMAIL="YES"
# Recipient email address for all alarms
DEFAULT_RECIPIENT_EMAIL="${ADMIN_EMAIL}"
# Email sender address
EMAIL_SENDER="${EMAIL_FROM}"
# SMTP configuration
SENDMAIL=""
EMAIL_SENDER="${EMAIL_FROM}"
# Custom send email command using msmtp
EMAIL_COMMAND="msmtp -t"
# Enable specific notification types
role_recipients_email[sysadmin]="${ADMIN_EMAIL}"
role_recipients_email[domainadmin]="${ADMIN_EMAIL}"
role_recipients_email[dba]="${ADMIN_EMAIL}"
role_recipients_email[webmaster]="${ADMIN_EMAIL}"
role_recipients_email[proxyadmin]="${ADMIN_EMAIL}"
role_recipients_email[sitemgr]="${ADMIN_EMAIL}"
###############################################################################
# Mattermost notifications via Slack-compatible webhook
###############################################################################
# Enable Slack notifications (Mattermost supports Slack-compatible webhooks)
SEND_SLACK="YES"
# Mattermost incoming webhook URL (Slack-compatible)
SLACK_WEBHOOK_URL="${MATTERMOST_WEBHOOK_URL}"
# Slack channel (optional, webhook default channel will be used if empty)
DEFAULT_RECIPIENT_SLACK=""
# Slack notification recipients per role
role_recipients_slack[sysadmin]="notifications"
role_recipients_slack[domainadmin]="notifications"
role_recipients_slack[dba]="notifications"
role_recipients_slack[webmaster]="notifications"
role_recipients_slack[proxyadmin]="notifications"
role_recipients_slack[sitemgr]="notifications"

20
net/msmtprc Normal file
View File

@@ -0,0 +1,20 @@
# MSMTP configuration for Netdata email alerts
# Set default values for all accounts
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile /var/log/msmtp.log
# IONOS SMTP account
account ionos
host smtp.ionos.de
port 465
tls_starttls off
from hi@pivoine.art
user hi@pivoine.art
password jaquoment
# Set default account
account default : ionos

View File

@@ -119,7 +119,7 @@ volumes:
name: util_linkwarden_meili_data name: util_linkwarden_meili_data
external: true external: true
backup_letsencrypt_data: backup_letsencrypt_data:
name: proxy_letsencrypt_data name: net_letsencrypt_data
external: true external: true
backup_util_vaultwarden_data: backup_util_vaultwarden_data:
name: util_vaultwarden_data name: util_vaultwarden_data
@@ -132,7 +132,7 @@ volumes:
name: jelly_config name: jelly_config
external: true external: true
backup_netdata_config: backup_netdata_config:
name: netdata_config name: net_netdata_config
external: true external: true
backup_ai_postgres_data: backup_ai_postgres_data:
name: ai_postgres_data name: ai_postgres_data