Compare commits

..

234 Commits

Author SHA1 Message Date
git c89b8129ec Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-05-13 14:52:27 +10:00
git c98b2d8232 Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-05-13 14:51:42 +10:00
beatzaplenty 3a9b3786c5 merge 2026-05-13 14:47:26 +10:00
beatzaplenty 1131eceb94 add socket proxy support 2026-05-13 14:45:32 +10:00
git 586be50567 Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-05-13 14:34:03 +10:00
beatzaplenty d0e08b8fc1 corrected docker-compose package name 2026-05-13 14:33:40 +10:00
git 487117b31a Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-05-13 14:29:09 +10:00
beatzaplenty 82fa90d843 install docker tooling to generate docs 2026-05-13 14:28:28 +10:00
git ef1e7432ee Merge branch 'main' of https://gitea.lan.ddnsgeek.com/beatzaplenty/docker 2026-05-13 14:16:08 +10:00
beatzaplenty 427ea20e43 updated gitea validation workflow 2026-05-13 14:15:05 +10:00
beatzaplenty 3298219ccf Update .gitea/workflows/generate-docs.yml
Validate Docs (Gitea) / validate (push) Failing after 3s
2026-05-13 04:05:39 +00:00
beatzaplenty b30c7f3a8a Update .gitea/workflows/validate-docs.yml
Validate Docs (Gitea) / validate (push) Failing after 5s
2026-05-13 04:05:12 +00:00
beatz174-bit 374511c123 Merge pull request #77 from beatz174-bit/codex/implement-dual-ci-workflow-for-docs-generation
Validate Docs (Gitea) / validate (push) Has been cancelled
ci: split docs generation to Gitea and docs validation/publish to GitHub
2026-05-13 14:01:16 +10:00
beatz174-bit 313f7f1c21 ci: use extended regex for docs secret scan 2026-05-13 14:00:52 +10:00
beatz174-bit 6aa78525c2 ci: split docs generation and publishing across gitea/github 2026-05-13 13:55:02 +10:00
beatz174-bit 5e0de23ff7 Merge pull request #75 from beatz174-bit/codex/update-docs-generation-workflow-for-reliability
Harden docs generation pipeline to refresh compose and Terraform data first
2026-05-13 13:42:20 +10:00
beatz174-bit 3c655dabcf Harden docs generation pipeline with strict source refresh 2026-05-13 13:40:50 +10:00
git 60e59f95a8 Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-05-13 10:32:02 +10:00
git cdf37fb2c9 Regenerate docs 2026-05-13 10:30:45 +10:00
beatz174-bit f56b734cb4 Merge pull request #74 from beatz174-bit/codex/fix-public-diagrams-generation-scripts
Fix generated public topology and routing diagrams to use inventory-aware concepts
2026-05-13 10:18:20 +10:00
beatz174-bit 10c4373c0e Fix invalid DOT cluster label emission for host inventory 2026-05-13 10:17:37 +10:00
beatz174-bit 2619d86dc1 Fix public topology/routing diagram generation and layout 2026-05-13 10:08:42 +10:00
git 1b679a4f09 Refine public docs diagrams 2026-05-13 09:48:45 +10:00
beatz174-bit 02033cd3f9 Merge pull request #73 from beatz174-bit/codex/refine-github-pages-documentation-and-diagrams
Remove Prometheus Rules from public docs; simplify physical and Traefik/DNS diagrams
2026-05-13 09:37:07 +10:00
beatz174-bit d932bc57d0 Remove stale Prometheus Rules nav from public MkDocs config 2026-05-13 09:36:23 +10:00
beatz174-bit c4cfa8081f Refine public docs generation and simplify topology diagrams 2026-05-13 09:33:29 +10:00
beatz174-bit 90c9094a6f Merge pull request #72 from beatz174-bit/codex/improve-documentation-with-generated-diagrams
Generate physical and Docker/Traefik/Dynu diagrams; require Graphviz and validate in CI
2026-05-13 09:18:29 +10:00
beatz174-bit 36c5f2b1e3 Restore legacy docker-compose diagram semantics 2026-05-13 09:17:47 +10:00
beatz174-bit 7bc2729ef6 Fix Traefik router backend targets in generated diagrams 2026-05-13 09:17:41 +10:00
beatz174-bit 9d79f828e4 Improve docs pipeline with generated topology diagrams 2026-05-13 09:08:47 +10:00
beatz174-bit 22b3659cdf Update publish-docs.yml 2026-05-13 08:53:35 +10:00
git 4440fddb2b docs: regenerate public docs 2026-05-13 08:51:51 +10:00
beatz174-bit d74000fbd0 Merge pull request #71 from beatz174-bit/codex/refactor-docs-pipeline-for-local-generation
docs: publish only committed public docs from GitHub Actions
2026-05-13 08:49:01 +10:00
beatz174-bit 9efdb5c781 Merge branch 'main' into codex/refactor-docs-pipeline-for-local-generation 2026-05-13 08:48:43 +10:00
beatz174-bit e7fd52616d docs: move public docs generation to local workflow 2026-05-13 08:47:23 +10:00
beatz174-bit b7b4b4d36d Merge pull request #70 from beatz174-bit/codex/fix-github-pages-to-deploy-public-docs-only
docs: publish GitHub Pages from docs/public only
2026-05-13 08:45:54 +10:00
beatz174-bit 40f5a3ce0d docs: publish only docs/public to GitHub Pages 2026-05-13 08:43:57 +10:00
beatz174-bit 8e43118661 docs: regenerate environment documentation 2026-05-12 22:35:56 +00:00
beatz174-bit 0e76f3cef1 Merge pull request #69 from beatz174-bit/codex/fix-docs-generation-for-docker-compose-profiles
Fix docs compose rendering by enabling `all` profile and add fail-fast check
2026-05-13 08:35:26 +10:00
beatz174-bit 8d18ab7059 Fix docs compose rendering to include all profiles 2026-05-13 08:33:26 +10:00
beatz174-bit 6a959d85c3 Merge pull request #68 from beatz174-bit/codex/add-mkdocs-publishing-to-github-pages
Add GitHub Pages MkDocs publish workflow
2026-05-13 08:29:06 +10:00
beatz174-bit 79c583eca3 Add MkDocs GitHub Pages publish workflow 2026-05-13 08:25:31 +10:00
beatz174-bit 7fcafed3f9 docs: regenerate environment documentation 2026-05-12 22:21:06 +00:00
beatz174-bit 0b67830d7f Merge pull request #67 from beatz174-bit/codex/update-github-actions-for-automated-docs-generation
docs: auto-commit generated docs on push and add browsable docs site structure
2026-05-13 08:20:21 +10:00
beatz174-bit d878a0f9b8 docs: make compose inventory generation deterministic 2026-05-13 08:19:39 +10:00
beatz174-bit 9c38910a67 docs: automate generated docs commits and add docs site structure 2026-05-13 08:09:51 +10:00
beatz174-bit 4a38a9421d Merge pull request #66 from beatz174-bit/codex/add-github-actions-for-docs-generation
docs: add GitHub Actions pipeline for compose documentation generation
2026-05-13 07:54:48 +10:00
beatz174-bit 5d32693925 docs: remove rg dependency and avoid archive compose fallback 2026-05-13 07:52:53 +10:00
beatz174-bit 696cecfecb ci: make compose installation resilient on ubuntu runners 2026-05-13 07:49:34 +10:00
beatz174-bit c0360a14b9 docs: add automated compose documentation generation pipeline 2026-05-13 07:45:57 +10:00
beatz174-bit 5589594d2c Merge pull request #65 from beatz174-bit/codex/add-dynu-dns-records-to-architecture-docs
Add optional Dynu DNS inventory support to architecture docs generation
2026-05-13 07:37:33 +10:00
beatz174-bit 0bd41b2fb1 Migrate legacy architecture heading during section upsert 2026-05-13 07:36:57 +10:00
beatz174-bit 5f10d0366e Add optional Dynu DNS inventory to architecture doc generator 2026-05-13 07:31:20 +10:00
git 9f98101c5d first dynu record generation 2026-05-13 07:23:20 +10:00
git 0c20676590 update .gitignore 2026-05-13 07:22:55 +10:00
beatz174-bit 59e2c2b9a3 Merge pull request #64 from beatz174-bit/codex/add-interactive-picker-for-terraform-outputs
Add interactive Terraform output picker for Dynu brownfield generator
2026-05-13 06:52:37 +10:00
beatz174-bit 61d49e85a3 Add interactive Terraform output picker for Dynu generator 2026-05-13 06:52:24 +10:00
beatz174-bit 8f84ed6d83 Merge pull request #63 from beatz174-bit/codex/fix-regexreplace-usage-in-terraform
Remove unsupported `regexreplace` usage from Dynu Terraform inventory
2026-05-13 06:42:07 +10:00
beatz174-bit 24cbb02bff Fix Dynu inventory by removing unsupported regexreplace usage 2026-05-13 06:40:25 +10:00
beatz174-bit 685a472572 Merge pull request #62 from beatz174-bit/codex/fix-dynu-brownfield-generator-keyerror
Fix Dynu brownfield generator output handling and state->enabled mapping
2026-05-13 06:37:33 +10:00
beatz174-bit 9de1bee542 Fix Dynu brownfield generator output handling 2026-05-13 06:37:10 +10:00
beatz174-bit 306e2c14db Merge pull request #61 from beatz174-bit/codex/update-terraform-for-dynu-dns-reconciliation
Add Dynu brownfield DNS inventory, outputs, and generator
2026-05-13 06:23:27 +10:00
beatz174-bit 63b47b59b5 Align wildcard DNS name sanitization across outputs and generator 2026-05-13 06:23:03 +10:00
beatz174-bit 52bd2d9fa2 Add Dynu brownfield DNS inventory outputs and generator 2026-05-13 06:03:32 +10:00
git 034ad17cf9 updated dynu terraform configuration 2026-05-13 05:37:59 +10:00
beatz174-bit ef2846562b Merge pull request #60 from beatz174-bit/codex/add-dynu-dns-terraform-documentation-layer-3vpdzo
dynu: add configurable root domain, import-ready domain resource, unpin provider version, and docs updates
2026-05-13 05:29:50 +10:00
beatz174-bit 52e2102f93 Merge branch 'main' into codex/add-dynu-dns-terraform-documentation-layer-3vpdzo 2026-05-13 05:29:40 +10:00
beatz174-bit a9b21912db Derive Dynu record FQDNs from configured root domain 2026-05-13 05:28:32 +10:00
beatz174-bit c9e4aeb8d6 Add dynu_record_import_id variable for record imports 2026-05-13 05:28:27 +10:00
beatz174-bit 96a44d5da6 Add dynu_root_domain variable for domain import flow 2026-05-13 05:18:02 +10:00
beatz174-bit 0d936677e5 Merge pull request #59 from beatz174-bit/codex/add-dynu-dns-terraform-documentation-layer-0fccdt
dynu: unpin provider, add domain skeleton, update imports/docs and records
2026-05-13 05:09:36 +10:00
beatz174-bit 5c38b92bc8 Remove Dynu provider version constraint 2026-05-13 05:09:17 +10:00
beatz174-bit fc5c882193 Merge pull request #58 from beatz174-bit/codex/add-host-topology-documentation-generator
Include VM-to-host mappings in Proxmox inventory output
2026-05-13 04:36:56 +10:00
beatz174-bit a4501d4034 Add VM inventory to proxmox outputs for host topology 2026-05-13 04:36:38 +10:00
beatz174-bit c0958d8f02 Merge pull request #57 from beatz174-bit/codex/add-dynu-dns-terraform-documentation-layer
Add Dynu Terraform brownfield DNS documentation layer
2026-05-13 03:36:03 +10:00
beatz174-bit 2a97864a06 Add Dynu Terraform brownfield DNS documentation layer 2026-05-13 03:17:37 +10:00
beatz174-bit bd23339edd Merge pull request #56 from beatz174-bit/codex/add-gitea-actions-runner-with-socket-proxy
Add Gitea Actions runner and configuration (compose, env, README)
2026-05-12 14:04:22 +10:00
beatz174-bit f8091a5c76 Use DOCKER_SOCKET_PROXY_HOST for gitea-runner Docker host 2026-05-12 14:03:57 +10:00
beatz174-bit 6a2639f931 Merge pull request #55 from beatz174-bit/codex/improve-dynu-dns-inventory-correlation
Harden Dynu/Traefik DNS correlation parsing and validation
2026-04-21 14:11:36 +10:00
beatz174-bit fae5e119d1 Harden Dynu/Traefik DNS correlation and validation 2026-04-21 14:11:25 +10:00
git 872038d0c9 first succesful build of dynu inventory 2026-04-21 13:57:16 +10:00
beatz174-bit 7a6db9fcfd Merge pull request #54 from beatz174-bit/codex/fix-dynu-api-authentication-error
Handle Dynu API auth failures without Python traceback
2026-04-21 13:52:16 +10:00
beatz174-bit d6a8979d55 Handle Dynu API auth failures without traceback 2026-04-21 13:52:01 +10:00
beatz174-bit a9a8a708d3 Merge pull request #53 from beatz174-bit/codex/fix-dynu-api-authentication-issue
Auto-load secrets/dynu.env, harden Dynu credential handling, and update docs
2026-04-21 13:38:52 +10:00
beatz174-bit 749c0d500d Improve Dynu env handling and document secrets/dynu.env 2026-04-21 13:38:33 +10:00
beatz174-bit 8f112af65b Merge pull request #52 from beatz174-bit/codex/integrate-dynu-dns-in-read-only-mode
Add read-only Dynu DNS inventory and Traefik correlation scripts
2026-04-21 12:45:50 +10:00
beatz174-bit 580e9b9aed Add strict read-only Dynu DNS inventory integration 2026-04-21 12:31:52 +10:00
beatz174-bit c77db36865 Merge pull request #51 from beatz174-bit/codex/add-basic-ansible-foundation
Add phase-1 Ansible foundation and safe validation hooks
2026-04-21 12:10:07 +10:00
beatz174-bit e11dc22999 Add phase-1 Ansible foundation and validation scaffolding 2026-04-21 12:07:29 +10:00
beatz174-bit 862ddd42f8 Merge pull request #50 from beatz174-bit/codex/update-documentation-for-codex-setup
docs: document Codex setup and maintenance scripts
2026-04-21 11:51:18 +10:00
beatz174-bit d0e7e52150 docs: add codex setup and maintenance script guidance 2026-04-21 11:51:02 +10:00
git f0aa9941d8 Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-04-21 11:34:21 +10:00
git 6789451bd6 moved codex environment scripts to repo 2026-04-21 11:33:50 +10:00
beatz174-bit b8d9e62954 Merge pull request #49 from beatz174-bit/codex/refactor-secret-inventory-and-documentation-structure
Refactor secrets inventory into docs and JSON source of truth
2026-04-21 11:25:04 +10:00
beatz174-bit 9f36dabcdc Refactor secrets inventory into docs + machine-readable JSON 2026-04-21 11:24:45 +10:00
beatz174-bit 3c78e9c140 Merge pull request #48 from beatz174-bit/codex/remove-unsupported-labels-from-containers
terraform(docker): remove unsupported docker_container labels arg and ignore label drift
2026-04-21 11:20:17 +10:00
beatz174-bit 40a976f712 terraform: remove docker_container labels args and ignore label drift 2026-04-21 11:20:08 +10:00
beatz174-bit 451be4ab0d Update AGENTS.md 2026-04-21 11:00:31 +10:00
beatz174-bit b848d6b807 Merge pull request #46 from beatz174-bit/codex/complete-terraform-documentation-for-docker
Document full Docker compose container inventory in Terraform docker layer
2026-04-21 09:46:46 +10:00
beatz174-bit 4695839df4 Manage container labels and expose labels in inventory output 2026-04-21 09:43:38 +10:00
beatz174-bit cb92ebc70e Fix Terraform container address and traefik runtime wiring 2026-04-21 09:43:33 +10:00
beatz174-bit 5c3bc0317c Merge pull request #47 from beatz174-bit/codex/perform-documentation-overhaul-for-repo
docs: refresh and expand repository documentation model
2026-04-21 09:35:33 +10:00
beatz174-bit c7dd9f2229 docs: overhaul repo documentation and workflow guides 2026-04-21 09:28:55 +10:00
beatz174-bit 7258d150ad Document full Docker compose container inventory in Terraform 2026-04-21 09:26:36 +10:00
git 020d6ecb79 created AGENTS.md 2026-04-21 09:04:59 +10:00
git b3cc235164 added physical machines 2026-04-21 08:43:15 +10:00
git b422a55c02 Updated terraform configurations 2026-04-20 22:23:20 +10:00
beatz174-bit b6d2e4ee62 Merge pull request #45 from beatz174-bit/codex/update-node-red-flow-for-idempotency
Make Grafana Docker Safe Update flow idempotent for repeated alerts
2026-04-15 07:42:34 +10:00
beatz174-bit 438cf57127 Make Grafana docker update flow idempotent after success 2026-04-15 07:42:00 +10:00
beatz174-bit 6ada434618 Merge pull request #44 from beatz174-bit/codex/create-script-to-automate-terraform-configuration-seiyl9
Add script to generate Terraform config from plan and document its usage
2026-04-14 20:19:10 +10:00
beatz174-bit 2a6c905eb8 Merge branch 'main' into codex/create-script-to-automate-terraform-configuration-seiyl9 2026-04-14 20:19:02 +10:00
beatz174-bit 9ad21c5846 Harden generated config temp path creation 2026-04-14 20:18:16 +10:00
beatz174-bit 2bab864d5e Merge pull request #43 from beatz174-bit/codex/create-script-to-automate-terraform-configuration
Add Terraform plan-to-config reconciliation script
2026-04-14 18:20:07 +10:00
beatz174-bit ebae1ed990 Add Terraform plan-to-config reconciliation script 2026-04-14 18:19:51 +10:00
beatz174-bit 5e49b6774b Merge pull request #42 from beatz174-bit/codex/scaffold-terraform-structure-for-homelab
Scaffold Terraform foundations for incremental Docker and Proxmox adoption
2026-04-14 18:09:15 +10:00
beatz174-bit 228413d780 Scaffold incremental Terraform foundations for docker and proxmox 2026-04-14 18:08:56 +10:00
git 7279ade925 new file: infrastructure/.gitignore
new file:   infrastructure/terraform/bootstrap/backend.tf
	new file:   infrastructure/terraform/bootstrap/main.tf
	new file:   infrastructure/terraform/bootstrap/outputs.tf
	new file:   infrastructure/terraform/bootstrap/providers.tf
	new file:   infrastructure/terraform/bootstrap/variables.tf
	new file:   infrastructure/terraform/docker/.terraform.lock.hcl
	new file:   infrastructure/terraform/docker/main.tf
	modified:   monitoring/node-red/data/context/00b02bbd01c91485/flow.json
	modified:   monitoring/node-red/data/update-events.ndjson
2026-04-14 17:42:30 +10:00
git a826c85e08 modified: apps/gramps/docker-compose.yml
modified:   default-environment.env
2026-04-14 16:24:42 +10:00
beatz174-bit 1918b83061 Merge pull request #41 from beatz174-bit/codex/update-gramps-deployment-to-use-gramps-web-u6wwj9
Replace Gramps Postgres service with GrampsWeb + Celery + Redis and update secrets/docs
2026-04-14 14:36:08 +10:00
beatz174-bit fa54b956a7 Merge branch 'main' into codex/update-gramps-deployment-to-use-gramps-web-u6wwj9 2026-04-14 14:35:33 +10:00
beatz174-bit d69c74e537 Add healthchecks for Gramps celery and valkey services 2026-04-14 14:34:31 +10:00
git 0654d9673f modified: apps/gramps/docker-compose.yml 2026-04-14 14:30:09 +10:00
beatz174-bit ff0c413d06 Merge pull request #40 from beatz174-bit/codex/update-gramps-deployment-to-use-gramps-web
Update Gramps to file-backed Gramps Web stack with Celery and Valkey
2026-04-14 14:26:32 +10:00
beatz174-bit f69028963b Update Gramps to file-backed Gramps Web stack with celery and valkey 2026-04-14 14:25:54 +10:00
git 624d7be17c modified: default-environment.env
modified:   monitoring/node-red/data/.flows.json.backup
	modified:   monitoring/node-red/data/update-events.ndjson
2026-04-14 12:33:18 +10:00
beatz174-bit b17adee7cc Merge pull request #39 from beatz174-bit/codex/refactor-and-extend-mtls-bridge-for-dynamic-endpoints
mtls-bridge: preserve request paths, add allow-list and configurable health endpoint
2026-04-14 12:08:25 +10:00
beatz174-bit 27c5c3f631 Add optional allow-list support for mtls-bridge paths 2026-04-14 12:07:17 +10:00
git 361d2dc87b modified: core/traefik/traefik.yml
modified:   default-environment.env
	modified:   monitoring/mtls-bridge/docker-compose.yml
	modified:   monitoring/node-red/data/context/00b02bbd01c91485/flow.json
	modified:   monitoring/node-red/data/update-events.ndjson
2026-04-14 11:32:19 +10:00
git 2499924afc Merge branch 'main' of https://github.com/beatz174-bit/docker
monitoring/mtls-bridge/docker-compose.yml

	modified:   monitoring/mtls-bridge/README.md
	modified:   monitoring/mtls-bridge/docker-compose.yml
2026-04-14 11:11:47 +10:00
git b2ff514a71 modified: core/traefik/traefik.yml
modified:   default-environment.env
	modified:   monitoring/mtls-bridge/docker-compose.yml
	modified:   monitoring/node-red/data/context/00b02bbd01c91485/flow.json
	modified:   monitoring/node-red/data/update-events.ndjson
2026-04-14 11:08:57 +10:00
beatz174-bit b5cfdde00f Merge pull request #38 from beatz174-bit/codex/add-traefik-label-for-basic-authentication-s6q3tm
Add Traefik basic-auth and CORS middlewares to mtls-bridge and document env vars
2026-04-14 11:08:40 +10:00
beatz174-bit d99e2767b5 Merge branch 'main' into codex/add-traefik-label-for-basic-authentication-s6q3tm 2026-04-14 11:08:34 +10:00
beatz174-bit 7f8e920fa1 Add CORS and OPTIONS handling for mtls-bridge panel actions 2026-04-14 11:06:45 +10:00
git c10b834be0 Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-04-14 09:34:26 +10:00
git f08a567933 modified: monitoring/mtls-bridge/docker-compose.yml
modified:   monitoring/node-red/data/update-events.ndjson
	modified:   update-containers.log
2026-04-14 09:34:12 +10:00
beatz174-bit 85d7859b14 Merge pull request #37 from beatz174-bit/codex/add-traefik-label-for-basic-authentication
Add Traefik basic-auth labels to mtls-bridge
2026-04-14 09:32:54 +10:00
beatz174-bit d06c53ef0b Add Traefik basic auth middleware for mtls-bridge 2026-04-14 09:32:18 +10:00
beatz174-bit 85cf56fcaf Merge pull request #36 from beatz174-bit/codex/investigate-unknown-image-reporting-issue
Fix compose file discovery and path normalization in docker-exporter
2026-04-13 17:35:12 +10:00
beatz174-bit a576dfdaa0 Fix compose image resolution from services-up script 2026-04-13 17:34:44 +10:00
beatz174-bit cf516ab1f4 Update README.md 2026-04-13 17:05:19 +10:00
beatz174-bit e7feec9146 Update architecture.md 2026-04-13 17:04:37 +10:00
git 49caca5700 deleted: docs/diagrams/architecture.mmd
deleted:    docs/diagrams/monitoring-coverage.mmd
2026-04-13 17:02:43 +10:00
git 97f0bac724 Merge branch 'main' of https://github.com/beatz174-bit/docker
new file:   docs/diagrams/architecture.mmd
	new file:   docs/diagrams/monitoring-coverage.mmd
	new file:   scripts/render_prometheus_docs.py
2026-04-13 17:01:36 +10:00
git f8370b6a99 deleted: docs/diagrams/architecture.mmd
deleted:    docs/diagrams/monitoring-coverage.mmd
	deleted:    docs/monitoring-coverage.md
	deleted:    docs/network.md
	deleted:    docs/prometheus-inventory.md
	deleted:    docs/runtime/prometheus-inventory.json
	deleted:    docs/runtime/prometheus-query-exposure.json
	deleted:    docs/runtime/prometheus-query-hypervisor.json
	deleted:    docs/runtime/prometheus-query-job-instance.json
	deleted:    docs/runtime/prometheus-query-jobs.json
	deleted:    docs/runtime/prometheus-query-network.json
	deleted:    docs/runtime/prometheus-query-role.json
	deleted:    docs/runtime/prometheus-query-service.json
	deleted:    docs/runtime/prometheus-query-up.json
	deleted:    docs/runtime/prometheus-targets.json
	deleted:    scripts/export_prometheus_inventory.py
	deleted:    scripts/render_prometheus_docs.py
2026-04-13 17:00:53 +10:00
beatz174-bit d27a994fe6 Merge pull request #35 from beatz174-bit/codex/create-prometheus-documentation-and-diagram-updater-py36tm
Add Prometheus docs renderer and generated monitoring docs/diagrams
2026-04-13 16:55:37 +10:00
beatz174-bit 937c6164d8 Merge branch 'main' into codex/create-prometheus-documentation-and-diagram-updater-py36tm 2026-04-13 16:55:29 +10:00
beatz174-bit 4eed2fd710 docs: fix Mermaid labels for GitHub parser compatibility 2026-04-13 16:54:37 +10:00
beatz174-bit ba41ce7eb0 Merge pull request #34 from beatz174-bit/codex/create-prometheus-documentation-and-diagram-updater
docs: generate Prometheus-driven monitoring docs and Mermaid diagrams
2026-04-13 16:47:40 +10:00
beatz174-bit 26b7b461a3 docs: generate Prometheus-driven monitoring docs and Mermaid diagrams 2026-04-13 16:47:26 +10:00
git 72cda2dc92 modified: default-environment.env
modified:   docs/prometheus-inventory.md
	new file:   docs/runtime/prometheus-inventory.json
	new file:   docs/runtime/prometheus-query-exposure.json
	new file:   docs/runtime/prometheus-query-hypervisor.json
	new file:   docs/runtime/prometheus-query-job-instance.json
	new file:   docs/runtime/prometheus-query-jobs.json
	new file:   docs/runtime/prometheus-query-network.json
	new file:   docs/runtime/prometheus-query-role.json
	new file:   docs/runtime/prometheus-query-service.json
	new file:   docs/runtime/prometheus-query-up.json
	new file:   docs/runtime/prometheus-targets.json
	modified:   monitoring/docker-socket-proxy/docker-compose.yml
	modified:   monitoring/mtls-bridge/docker-compose.yml
	modified:   monitoring/node-red/data/context/00b02bbd01c91485/flow.json
	modified:   monitoring/node-red/data/update-events.ndjson
2026-04-13 16:40:32 +10:00
beatz174-bit 2b83cd5599 Merge pull request #33 from beatz174-bit/codex/create-prometheus-inventory-export-script
docs: add Prometheus inventory export script for diagram/documentation pipeline
2026-04-13 16:30:04 +10:00
beatz174-bit c0ed8cfc5f docs: add Prometheus inventory export script for diagram/documentation pipeline 2026-04-13 16:29:10 +10:00
beatz174-bit 7646f8187b Merge pull request #32 from beatz174-bit/codex/create-infrastructure-diagrams-for-docker/traefik-xvw0xz
Add README and architecture overview for Docker + Traefik homelab stack
2026-04-13 15:54:26 +10:00
beatz174-bit 8d462a83c7 Merge branch 'main' into codex/create-infrastructure-diagrams-for-docker/traefik-xvw0xz 2026-04-13 15:54:20 +10:00
beatz174-bit 29856c4d1c docs: fix Mermaid edge label parsing in architecture diagram 2026-04-13 15:53:45 +10:00
beatz174-bit ce626ee0c8 Merge pull request #31 from beatz174-bit/codex/create-infrastructure-diagrams-for-docker/traefik-ivyntw
Add initial README and architecture documentation for Docker + Traefik homelab stack
2026-04-13 15:51:58 +10:00
beatz174-bit 18104468aa Merge branch 'main' into codex/create-infrastructure-diagrams-for-docker/traefik-ivyntw 2026-04-13 15:51:50 +10:00
beatz174-bit f136f49e51 docs: fix Mermaid parse error in architecture diagram 2026-04-13 15:51:21 +10:00
beatz174-bit ee609201b3 Merge pull request #30 from beatz174-bit/codex/create-infrastructure-diagrams-for-docker/traefik
docs: add inferred Docker/Traefik architecture diagrams and summary
2026-04-13 15:47:09 +10:00
beatz174-bit 25f91e301c docs: add Docker/Traefik architecture diagrams and summary 2026-04-13 15:46:48 +10:00
beatz174-bit cbdf9c9562 Merge pull request #29 from beatz174-bit/codex/add-logging-and-env-var-for-log-level-xiw6bv
mtls-bridge: add upstream CA handling, request timing, and improved logging
2026-04-13 15:02:12 +10:00
beatz174-bit de82d295fb Merge branch 'main' into codex/add-logging-and-env-var-for-log-level-xiw6bv 2026-04-13 15:02:03 +10:00
beatz174-bit 8224009aa6 Add backward-compatible CA_CERT alias to prevent startup NameError 2026-04-13 15:01:29 +10:00
beatz174-bit d98f74a9d0 Merge pull request #28 from beatz174-bit/codex/add-logging-and-env-var-for-log-level-q2b7yp
mtls-bridge: Add upstream TLS verification options, request timing, and enhanced logging
2026-04-13 14:55:49 +10:00
beatz174-bit 3d49ebdeee Merge branch 'main' into codex/add-logging-and-env-var-for-log-level-q2b7yp 2026-04-13 14:55:40 +10:00
beatz174-bit a515e3e25b Proxy OPTIONS requests and warn on http upstream target 2026-04-13 14:54:30 +10:00
beatz174-bit 0a3cfa4631 Merge pull request #27 from beatz174-bit/codex/add-logging-and-env-var-for-log-level-nwi0f7
mtls-bridge: add upstream TLS verify handling, timing, and improved logging
2026-04-13 14:06:23 +10:00
beatz174-bit 15b349604c Merge branch 'main' into codex/add-logging-and-env-var-for-log-level-nwi0f7 2026-04-13 14:06:13 +10:00
beatz174-bit 4a0ab9d184 Fix upstream TLS verification configuration for mtls-bridge 2026-04-13 14:05:26 +10:00
beatz174-bit 155373a171 Merge pull request #26 from beatz174-bit/codex/add-logging-and-env-var-for-log-level-lkuozx
mtls-bridge: enhance logging/timing and fix docker-compose cert/env
2026-04-13 13:58:30 +10:00
beatz174-bit a29fcc85d0 Fix mtls-bridge CA path and reduce healthcheck log noise 2026-04-13 13:58:05 +10:00
beatz174-bit b6ff09513f Merge pull request #25 from beatz174-bit/codex/add-logging-and-env-var-for-log-level
Add richer mtls-bridge request logging and configurable log level
2026-04-13 13:42:10 +10:00
beatz174-bit a0b9dd980b Add configurable logging for mtls-bridge proxy 2026-04-13 13:41:53 +10:00
git 649965e97a modified: monitoring/mtls-bridge/docker-compose.yml 2026-04-13 13:29:04 +10:00
git db57390bf9 Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-04-13 13:19:28 +10:00
beatz174-bit 4e61ac701f Merge pull request #24 from beatz174-bit/codex/implement-internal-mtls-bridge-service
Add internal mTLS bridge service for monitoring (mtls-bridge)
2026-04-13 13:18:56 +10:00
beatz174-bit cd47fe324e Add internal mTLS bridge service for monitoring stack 2026-04-13 13:18:40 +10:00
git d6baa39bf4 deleted: core/docker-compose.yml
modified:   monitoring/node-red/data/.flows.json.backup
	modified:   monitoring/node-red/data/.flows_cred.json.backup
	modified:   monitoring/node-red/data/context/00b02bbd01c91485/flow.json
	modified:   monitoring/node-red/data/flows.json
	modified:   monitoring/node-red/data/flows_cred.json
	modified:   monitoring/node-red/data/update-events.ndjson
2026-04-13 13:15:46 +10:00
beatz174-bit 6f47e654a8 Merge pull request #23 from beatz174-bit/codex/implement-mtls-for-private-admin-services
Enforce mTLS for private-admin services via Traefik
2026-04-13 12:06:14 +10:00
beatz174-bit 24047b0eaa Enforce mTLS on private-admin Traefik routes 2026-04-13 12:05:43 +10:00
git 0ddbb7d7ad modified: .gitignore
new file:   monitoring/influxdb/docker-compose.yml
2026-04-13 11:53:24 +10:00
git 43f25321d7 modified: core/authelia/docker-compose.yml
modified:   core/crowdsec/docker-compose.yml
	modified:   core/error-pages/docker-compose.yml
	modified:   monitoring/docker-exporter/docker-compose.yml
	modified:   monitoring/docker-socket-proxy/docker-compose.yml
	deleted:    monitoring/influxdb-service/docker-compose.yml
	modified:   monitoring/node-exporter/docker-compose.yml
	modified:   monitoring/pihole-exporter/docker-compose.yml
	modified:   monitoring/telegraf/docker-compose.yml
	new file:   service-access-policy.md
2026-04-13 11:51:45 +10:00
beatz174-bit 9678c6a8f1 Merge pull request #22 from beatz174-bit/codex/update-profiles-in-docker-compose.yml-files
Normalize Docker Compose service profiles by folder hierarchy
2026-04-13 11:28:17 +10:00
beatz174-bit c1401e3e08 Normalize compose service profiles by folder hierarchy 2026-04-13 11:27:27 +10:00
beatz174-bit 5c600d0af0 Merge pull request #21 from beatz174-bit/codex/split-compose-files-by-service
Split core and monitoring compose files into single-service compose files
2026-04-13 10:23:36 +10:00
beatz174-bit e3d463d511 Merge branch 'main' into codex/split-compose-files-by-service 2026-04-13 10:23:24 +10:00
beatz174-bit ff2d323309 Split core and prometheus compose files by service 2026-04-13 10:22:42 +10:00
beatz174-bit 47a5908430 Merge pull request #20 from beatz174-bit/codex/update-traefik-configuration-for-trusted-proxies
Restrict Traefik forwarded headers to trusted IPs, enable Authelia trustForwardHeader, and add traefik network subnet
2026-04-13 10:16:25 +10:00
beatz174-bit 8448f2bb94 Narrow trusted proxy CIDRs and pin Traefik subnet 2026-04-13 10:16:06 +10:00
beatz174-bit cfbefed2e3 Merge pull request #19 from beatz174-bit/codex/update-security_secrets_inventory.md
docs: align secrets inventory with current .env example
2026-04-13 09:55:30 +10:00
beatz174-bit 3b3f06a727 docs: align secrets inventory with example env keys 2026-04-13 09:55:14 +10:00
git 8c82830af8 modified: secrets/.env.secrets.example 2026-04-13 09:49:41 +10:00
git 9ebb3c6c93 new file: monitoring/node-red/data/update-events.ndjson 2026-04-13 09:42:04 +10:00
git 86fba4f43f modified: default-environment.env
modified:   monitoring/node-red/Dockerfile
	modified:   monitoring/node-red/data/.flows.json.backup
	modified:   monitoring/node-red/data/context/00b02bbd01c91485/flow.json
	modified:   monitoring/node-red/data/flows.json
	modified:   monitoring/node-red/data/test-container.sh
	modified:   monitoring/node-red/docker-compose.yml
	modified:   services-up.sh
	monitoring/node-red/data/update-events.ndjson
2026-04-13 09:41:16 +10:00
beatz174-bit d6325494c7 Merge pull request #18 from beatz174-bit/codex/fix-node-red-container-startup-issue-m9hwhm
Make services-up.sh project-root aware and pass --project-directory to docker compose
2026-04-13 08:50:11 +10:00
beatz174-bit 2021ef37ae Merge branch 'main' into codex/fix-node-red-container-startup-issue-m9hwhm 2026-04-13 08:50:01 +10:00
beatz174-bit aae70d93b4 Default PROJECT_ROOT to script directory 2026-04-13 08:49:22 +10:00
beatz174-bit 7bc4d6699d Merge pull request #17 from beatz174-bit/codex/fix-node-red-container-startup-issue
Respect PROJECT_ROOT and use --project-directory in services-up.sh
2026-04-13 08:27:20 +10:00
beatz174-bit e4dd0394fe Silence find permission errors in services-up 2026-04-13 08:26:25 +10:00
beatz174-bit 1a65661474 Make services-up use PROJECT_ROOT for compose paths 2026-04-13 08:23:36 +10:00
beatz174-bit 01279edc5b Merge pull request #16 from beatz174-bit/codex/dynamically-find-compose-files-in-script
services-up.sh: add strict mode, dynamic compose file discovery, and safer arg quoting
2026-04-13 07:33:35 +10:00
beatz174-bit 4d713b02f3 Make services-up compose file discovery dynamic 2026-04-13 07:33:14 +10:00
beatz174-bit 4d681021e1 Merge pull request #15 from beatz174-bit/codex/add-logging-output-for-grafana-zolp0e
Fix Node-RED unknown-project errors when Grafana alert labels are missing
2026-04-13 06:20:19 +10:00
beatz174-bit 30f53eb668 Harden unknown-project Node-RED functions for missing labels 2026-04-13 06:19:57 +10:00
beatz174-bit a9593d7589 Merge pull request #14 from beatz174-bit/codex/add-logging-output-for-grafana
Add Node-RED update-event logging and Telegraf ingestion for Grafana
2026-04-13 06:13:29 +10:00
beatz174-bit 232fdfbb36 Wire Node-RED update events to structured update log file 2026-04-13 06:12:59 +10:00
beatz174-bit 9f9cfaf4be Merge pull request #13 from beatz174-bit/codex/review-grafana-docker-safe-update-flow
Fix Node-RED Grafana safe update flow parsing and lockout notifications
2026-04-13 05:51:43 +10:00
beatz174-bit 8337b53da3 Fix Grafana docker update flow label parsing and lockout notifications 2026-04-13 05:51:28 +10:00
git d519139615 new file: monitoring/node-red/data/.config.nodes.json
new file:   monitoring/node-red/data/.config.runtime.json
	new file:   monitoring/node-red/data/.config.runtime.json.backup
	new file:   monitoring/node-red/data/.config.users.json
	new file:   monitoring/node-red/data/.config.users.json.backup
	new file:   monitoring/node-red/data/.flows.json.backup
	new file:   monitoring/node-red/data/.flows_cred.json.backup
	new file:   monitoring/node-red/data/.npm/_cacache/content-v2/sha512/b0/47/c1458664fa9b6a08e9035110b523127a96bd7285d19472dc702f5dc498b927412b0ecd3273708fbf9d61754520599ac0b0e11f3e4c4d4ac784e78d7d97fe
	new file:   monitoring/node-red/data/.npm/_cacache/content-v2/sha512/c2/c2/b64870ea5c5a42b5772106f51123cf684d3c8381de10ccc07d01168d111d0a1ab79ee26fda320b3027c76cfc856119f7b440845a83c9f22d7d731643e62f
	new file:   monitoring/node-red/data/.npm/_cacache/index-v5/15/a4/2638498d877ec2c8c3d88cb9c08d7867c52d3fceb6fc64cc5abde73b01a9
	new file:   monitoring/node-red/data/.npm/_cacache/index-v5/48/03/b8903b717bbc1ad41b3f37148db48f54e0828c1aef870973f6672895d689
	new file:   monitoring/node-red/data/.npm/_logs/2026-04-05T01_36_15_515Z-debug-0.log
	new file:   monitoring/node-red/data/context/00b02bbd01c91485/flow.json
	new file:   monitoring/node-red/data/context/global/global.json
	new file:   monitoring/node-red/data/flows.json
	new file:   monitoring/node-red/data/flows_cred.json
	new file:   monitoring/node-red/data/node_modules/.package-lock.json
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/CHANGELOG.md
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/LICENSE
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/README.md
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/flow-debugger.html
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/flow-debugger.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/flow-debugger.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/MessageQueue.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/MessageQueue.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/debugger.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/debugger.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/location.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/location.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/types.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/types.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/locales/en-US/flow-debugger.json
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/locales/ja/flow-debugger.json
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/nr-types.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/nr-types.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/package.json
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/resources/style.css
	new file:   monitoring/node-red/data/package-lock.json
	new file:   monitoring/node-red/data/package.json
	new file:   monitoring/node-red/data/settings.js
	new file:   monitoring/node-red/data/test-container.sh
	new file:   monitoring/node-red/data/test-container.sh.old
	new file:   monitoring/node-red/data/webhook.json
2026-04-13 05:36:20 +10:00
git 8c5a902613 modified: .gitignore
modified:   default-environment.env
	modified:   monitoring/portainer/docker-compose.yml
	modified:   monitoring/prometheus/docker-compose.yml
	modified:   update-containers.log
2026-04-13 05:35:12 +10:00
beatz174-bit f09ac96e06 Merge pull request #12 from beatz174-bit/codex/add-api-toggles-for-portainer
Enable missing docker-socket-proxy API toggles required by Portainer
2026-04-08 10:17:24 +10:00
beatz174-bit 4ff815e73e Enable additional docker-socket-proxy APIs for Portainer 2026-04-08 10:17:10 +10:00
git 2d17c0a70d modified: core/docker-compose.yml
modified:   core/traefik/dynamic.yml
	modified:   core/traefik/traefik.yml
	modified:   monitoring/portainer/docker-compose.yml
	modified:   monitoring/prometheus/docker-compose.yml
	modified:   monitoring/uptime-kuma/docker-compose.yml
2026-04-08 09:57:39 +10:00
git 4e4ffa7f97 Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-04-08 09:19:39 +10:00
git 9b96c94d5d modified: default-environment.env
modified:   monitoring/docker-exporter/exporter.py
	modified:   monitoring/node-red/docker-compose.yml
	modified:   monitoring/prometheus/docker-compose.yml
2026-04-08 09:18:33 +10:00
beatz174-bit ab38abf661 Merge pull request #11 from beatz174-bit/codex/refactor-services-to-use-docker-socket-proxy
Switch services from bind-mounting /var/run/docker.sock to docker-socket-proxy (use TCP DOCKER_HOST)
2026-04-08 09:18:22 +10:00
beatz174-bit 94565837f1 use shared prometheus docker-socket-proxy across services 2026-04-08 09:17:44 +10:00
beatz174-bit 700328d677 Merge pull request #10 from beatz174-bit/codex/check-docker-exporter.py-for-excessive-requests
Reduce upstream registry lookups in docker exporter
2026-04-07 22:21:46 +10:00
beatz174-bit 23315dd5ad Reduce registry calls in docker exporter checks 2026-04-07 22:21:27 +10:00
beatz174-bit 7945518a47 Merge pull request #9 from beatz174-bit/codex/enhance-docker-security-configurations
Use docker-socket-proxy and drop container privileges for monitoring services
2026-04-07 22:05:14 +10:00
beatz174-bit 037e3fc46b Merge branch 'main' into codex/enhance-docker-security-configurations 2026-04-07 22:04:32 +10:00
beatz174-bit 8930cb8459 Use single shared docker socket proxy service 2026-04-07 22:01:23 +10:00
git f221b12f8d modified: apps/gramps/docker-compose.yml
modified:   apps/nextcloud/docker-compose.yml
	modified:   apps/passbolt/docker-compose.yml
	modified:   core/authelia/configuration.yml
	modified:   core/docker-compose.yml
	modified:   default-environment.env
	modified:   monitoring/gotify/docker-compose.yml
	modified:   monitoring/prometheus/docker-compose.yml
	modified:   monitoring/prometheus/prometheus.yml
	modified:   services-up.sh
2026-04-07 21:57:22 +10:00
git 39debaf4b4 Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-04-07 19:40:06 +10:00
git 8bed8fdcb2 new file: .env.example
new file:   DEPLOYMENT.md
	new file:   SECURITY_SECRETS_INVENTORY.md
	new file:   secrets/.env.secrets.example
2026-04-07 19:39:48 +10:00
git 3b1e0efa19 modified: .gitignore
modified:   apps/gramps/docker-compose.yml
	modified:   apps/nextcloud/docker-compose.yml
	modified:   apps/passbolt/docker-compose.yml
	modified:   core/docker-compose.yml
	modified:   monitoring/gotify/docker-compose.yml
	modified:   monitoring/prometheus/docker-compose.yml
	modified:   monitoring/prometheus/prometheus.yml
	.env.example
	DEPLOYMENT.md
	SECURITY_SECRETS_INVENTORY.md
	secrets/
2026-04-07 19:38:51 +10:00
beatz174-bit cf45a16c67 Merge pull request #8 from beatz174-bit/codex/refactor-credential-management-for-docker
Harden compose secret handling and require secret provisioning
2026-04-07 16:18:16 +10:00
beatz174-bit 698fc19e82 Merge branch 'main' into codex/refactor-credential-management-for-docker 2026-04-07 16:18:03 +10:00
beatz174-bit 3c2d28c763 Harden compose secrets and add required provisioning docs 2026-04-07 16:12:50 +10:00
git 8d0ecf0adf modified: default-environment.env
modified:   monitoring/gotify/docker-health-to-gotify.sh
2026-04-07 15:34:41 +10:00
git f7b4cc22b9 Merge branch 'main' of https://github.com/beatz174-bit/docker 2026-04-07 15:15:33 +10:00
git b7983b30d3 modified: apps/gitea/docker-compose.yml
modified:   apps/gramps/docker-compose.yml
	modified:   apps/nextcloud/docker-compose.yml
	modified:   apps/passbolt/docker-compose.yml
	modified:   core/docker-compose.yml
	modified:   default-environment.env
	modified:   monitoring/prometheus/docker-compose.yml
2026-04-07 15:12:42 +10:00
beatz174-bit 1d395287dc Merge pull request #7 from beatz174-bit/codex/move-hard-coded-environment-variables
Move hard-coded compose environment values into default-environment.env
2026-04-07 15:09:26 +10:00
beatz174-bit 634abe4b39 Move hard-coded env values into default-environment.env 2026-04-07 15:08:59 +10:00
253 changed files with 20476 additions and 516 deletions
+9
View File
@@ -0,0 +1,9 @@
# Copy this file to default-environment.env (non-secret defaults) and update values.
PROJECT_ROOT=/path/to/docker
DOMAIN=example.com
TZ=Etc/UTC
EMAIL=admin@example.com
# Required secret file path used by compose services.
# Create this file from secrets/.env.secrets.example and keep it out of git.
SECRETS_ENV_FILE=${PROJECT_ROOT}/secrets/stack-secrets.env
+71
View File
@@ -0,0 +1,71 @@
name: Generate Docs
on:
workflow_dispatch:
schedule:
- cron: "0 */6 * * *"
jobs:
generate:
runs-on: ubuntu-latest
env:
DOCKER_HOST: tcp://docker-socket-proxy:2375
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install docker CLI
run: |
apt-get update
apt-get install -y docker.io docker-compose
- name: Generate docs
run: |
scripts/docs/generate-all.sh
- name: Validate generated docs
run: |
set -e
test -s docs/generated/docker-compose.resolved.yml
test -s docs/generated/host-topology.md
test -s docs/public/physical-topology.svg
test -s docs/public/docker-traefik-dynu.svg
! grep -R "Host inventory JSON not found" docs/public docs/diagrams
! grep -R "Generate terraform inventory" docs/public docs/diagrams
# Ensure no obvious secrets leaked
! grep -R -E -i "password|token|api[_-]?key|secret" docs/public \
|| (echo "Secret-like string detected"; exit 1)
- name: Commit changes
run: |
git config user.name "docs-bot"
git config user.email "docs-bot@local"
git add docs/generated docs/diagrams docs/public data/terraform/proxmox-inventory.json || true
if git diff --cached --quiet; then
echo "No changes to commit"
exit 0
fi
git commit -m "docs: regenerate documentation artifacts"
- name: Push to Gitea
run: |
git push origin HEAD:main
- name: Push to GitHub mirror
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_PUSH_TOKEN }}
GITHUB_MIRROR_REPO: ${{ vars.GITHUB_MIRROR_REPO }}
run: |
test -n "$GITHUB_TOKEN"
test -n "$GITHUB_MIRROR_REPO"
git remote add github "https://$GITHUB_TOKEN@github.com/$GITHUB_MIRROR_REPO.git" || true
git push github HEAD:main
+24
View File
@@ -0,0 +1,24 @@
name: Validate Docs (Gitea)
on:
push:
branches: [ main ]
paths:
- "docs/**"
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate docs
run: |
set -e
test -d docs/public
test -s docs/public/physical-topology.svg
! grep -R "Host inventory JSON not found" docs/public
! grep -R "Generate terraform inventory" docs/public
+35
View File
@@ -0,0 +1,35 @@
name: Validate committed public docs
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
jobs:
validate-public-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Ensure committed docs/public exists
run: |
test -d docs/public
test -n "$(find docs/public -mindepth 1 -print -quit)"
- name: Install MkDocs
run: |
python3 -m pip install --user mkdocs
- name: Validate docs content
run: |
set -e
test -s docs/public/physical-topology.svg
test -s docs/public/docker-traefik-dynu.svg
! grep -R "Host inventory JSON not found" docs/public
! grep -R "Generate terraform inventory" docs/public
! rg -n -i "password|token|api[_-]?key|secret" docs/public
- name: Build MkDocs site
run: |
python3 -m mkdocs build -f mkdocs-public.yml --strict
+84
View File
@@ -0,0 +1,84 @@
name: Publish documentation site
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: github-pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Ensure committed docs/public exists
run: |
test -d docs/public
test -n "$(find docs/public -mindepth 1 -print -quit)"
- name: Install Graphviz
run: |
sudo apt-get update
sudo apt-get install -y graphviz
dot -V
- name: Validate sanitized diagram artifacts
run: |
test -f docs/public/physical-topology.svg
test -f docs/public/docker-traefik-dynu.svg
! rg -n "Graphviz dot not found" docs/public/*.svg
! rg -n "lan\.ddnsgeek\.com" docs/public/*.svg docs/public/*.md
! rg -n -i "password|token|api_key|secret" docs/public/*.svg
- name: Install MkDocs
run: |
python3 -m pip install --user mkdocs
- name: Build public MkDocs site
run: |
python3 -m mkdocs build -f mkdocs-public.yml --strict
- name: Verify published content excludes internal/generated docs
run: |
test -d site-public
test ! -e site-public/generated
test ! -e site-public/docker
- name: Verify expected 404-only paths are not generated
run: |
test ! -e site-public/generated/compose-inventory/index.html
test ! -e site-public/generated/prometheus-rules/index.html
test ! -e site-public/docker/index.html
- name: Configure GitHub Pages
uses: actions/configure-pages@v5
- name: Upload GitHub Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: site-public
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+30 -1
View File
@@ -4,12 +4,13 @@
#!**/Dockerfile
#!docker-compose.yml
**/data/
apps/gitea/runner-data/
**/db/
**/database/
apps/nextcloud/config/
core/crowdsec/config/
**/.env
!monitoring/node-red/data/
apps/stockfill/
apps/shift-recorder/
@@ -21,4 +22,32 @@ apps/searxng/*
venv/
core/authelia/users_database.yml
monitoring/influxdb/*
!monitoring/influxdb/docker-compose.yml
secrets/*
!secrets/.env.secrets.example
!secrets/inventory.json
!.env.example
core/traefik/certs/*
!core/traefik/certs/.gitkeep
site/
# Docs generation artifacts intentionally tracked
!data/terraform/proxmox-inventory.json
!infrastructure/terraform/dynu/generated/
!infrastructure/terraform/dynu/generated/dynu_dns_records_inventory.json
!docs/generated/
!docs/generated/docker-compose.resolved.yml
!docs/generated/host-topology.md
!docs/diagrams/
!docs/diagrams/*.svg
!docs/public/
!docs/public/*.md
!docs/public/*.svg
# Terraform local/state artifacts
**/.terraform/
**/.terraform.lock.hcl
*.tfstate
*.tfstate.*
*.tfvars
*.tfvars.json
+80
View File
@@ -0,0 +1,80 @@
# Codex instructions for this repository
This repository contains:
- Docker Compose infrastructure
- Terraform configuration
- Ansible configuration
## General rules
Prefer validation and linting over execution.
Do not make assumptions about runtime access.
Do not run destructive commands.
Do not install repo changes unless explicitly requested.
## Docker / Compose rules
This environment does not have Docker daemon access.
Do not use commands that require `/var/run/docker.sock`.
Allowed:
- `docker compose config`
- `docker compose -f <file> config`
- `./services-up.sh --profile all config`
Not allowed:
- `docker compose up`
- `docker compose down`
- `docker compose run`
- `docker compose exec`
- `docker build`
- `docker pull`
When validating Docker changes:
1. Prefer `./services-up.sh --profile all config` if available.
2. If that does not fit the task, use `docker compose -f ... config`.
3. Only create temporary placeholder env files if validation requires them.
4. Do not commit placeholder env files unless explicitly requested.
## Terraform rules
Allowed:
- `terraform fmt -check -recursive`
- `terraform init -backend=false -input=false`
- `terraform validate`
- `tflint`
Do not apply infrastructure changes unless explicitly requested.
Do not run:
- `terraform apply`
- `terraform destroy`
If `terraform init` fails because access to `registry.terraform.io` is forbidden, do not summarize the error vaguely. Report the exact stderr. Continue with:
- `terraform fmt -check -recursive`
- static review of changed `.tf` files
Only run `terraform validate` when provider installation is available locally or registry access succeeds.
## Ansible rules
Allowed:
- `ansible-lint`
- `ansible-playbook --syntax-check <playbook>`
Do not run playbooks against real hosts unless explicitly requested.
## Shell / YAML rules
Allowed:
- `shellcheck`
- `yamllint`
- `yq`
- `jq`
## Expected workflow
When making changes:
1. Edit the smallest necessary set of files.
2. Run the safest available validation commands.
3. Report validation results clearly.
4. If validation is blocked by missing secrets, env files, or remote/provider access, say so explicitly instead of guessing.
+188
View File
@@ -0,0 +1,188 @@
# Homelab Docker + Terraform Inventory Repository
This repository is both:
1. **operational** (Docker Compose application/runtime definition), and
2. **documentary/inventory-oriented** (Terraform capture of Proxmox VMs, host metadata, and selected Docker objects).
If you only read one section, read **[Source-of-truth boundaries](docs/source-of-truth.md)** first.
---
## Quick navigation
- Architecture overview: [docs/architecture.md](docs/architecture.md)
- Repository layout: [docs/repo-structure.md](docs/repo-structure.md)
- Source-of-truth boundaries and guardrails: [docs/source-of-truth.md](docs/source-of-truth.md)
- Docker environment composition and `services-up.sh`: [docs/docker-environment.md](docs/docker-environment.md)
- Terraform workflows (brownfield import/reconciliation): [docs/terraform-workflows.md](docs/terraform-workflows.md)
- Infrastructure inventory intent and outputs: [docs/infrastructure-inventory.md](docs/infrastructure-inventory.md)
- Dynu DNS read-only inventory workflow: [docs/dynu-dns-inventory.md](docs/dynu-dns-inventory.md)
- Generated host topology doc: [docs/generated/host-topology.md](docs/generated/host-topology.md)
- Ansible bootstrap workflows: [docs/ansible-workflows.md](docs/ansible-workflows.md)
- Deployment prerequisites and secrets setup: [docs/deployment-prerequisites.md](docs/deployment-prerequisites.md)
- Secrets inventory: [docs/security-secrets.md](docs/security-secrets.md)
Codex helper scripts:
- Initial Codex environment/bootstrap setup: [scripts/codex-setup.sh](scripts/codex-setup.sh)
- Codex environment maintenance/refresh: [scripts/codex-maintenance.sh](scripts/codex-maintenance.sh)
Infrastructure subtrees:
- Ansible foundation docs: [infrastructure/ansible/README.md](infrastructure/ansible/README.md)
- Terraform root docs: [infrastructure/terraform/README.md](infrastructure/terraform/README.md)
- Terraform Docker mirror: [infrastructure/terraform/docker/README.md](infrastructure/terraform/docker/README.md)
- Terraform Proxmox inventory: [infrastructure/terraform/proxmox/README.md](infrastructure/terraform/proxmox/README.md)
---
## Operating model
### Docker Compose (runtime authority)
- Compose files under `core/`, `apps/`, and `monitoring/` describe runtime services.
- `services-up.sh` composes the environment by discovering compose files and applying common env/network inputs.
- For service runtime behavior, start from Compose files and `services-up.sh` (not Terraform).
### Ansible (bootstrap foundation)
- Ansible under `infrastructure/ansible/` is a phase-1 foundation for inventory/configuration scaffolding.
- It supports safe validation (inventory parsing and playbook syntax checks) while hosts/devices are onboarded gradually.
- It does not replace Compose runtime authority or Terraform reconciliation authority at this stage.
### Terraform (inventory and reconciliation authority)
- Terraform under `infrastructure/terraform/` is used to codify and reconcile existing infrastructure.
- Current repo usage emphasizes **brownfield import-first workflows** and safe reconciliation.
- Terraform captures:
- Proxmox VM configuration for existing VMs.
- Physical host metadata in locals/outputs.
- Documentation-oriented Docker container mirroring (limited, selective).
Terraform here is **not** a replacement for Docker Compose deployment.
---
## Guardrails
- Do not run destructive Terraform commands casually.
- Do not treat generated Terraform config as final without manual review.
- Do not commit real secrets, credentials, or local state.
- Keep one-resource-per-file patterns where already established in Terraform subdirectories.
- Prefer shaping outputs for documentation/tooling consumption over dumping raw provider objects.
See [docs/source-of-truth.md](docs/source-of-truth.md) and [docs/terraform-workflows.md](docs/terraform-workflows.md) for concrete do/don't guidance.
---
## High-level architecture
```mermaid
flowchart TB
Internet((Internet Clients)) -->|HTTPS 443 / HTTP 80| Traefik[Traefik Ingress\nACME TLS + Security Middlewares]
subgraph DockerHost[Primary Docker Host]
Traefik
Authelia[Authelia SSO / ForwardAuth]
CrowdSec[CrowdSec + Traefik Bouncer]
ErrPages[Error Pages Fallback]
subgraph Apps[Business / User Applications]
Nextcloud[Nextcloud]
Passbolt[Passbolt]
Gitea[Gitea]
FamilyTree[Gramps Web]
Searxng[SearXNG]
end
subgraph Ops[Operations & Monitoring]
Grafana[Grafana]
Prometheus[Prometheus]
InfluxDB[InfluxDB]
NodeRED[Node-RED]
Portainer[Portainer]
UptimeKuma[Uptime Kuma]
Gotify[Gotify Notifications]
end
end
Traefik --> Apps
Traefik --> Ops
Traefik -->|ForwardAuth for selected routes| Authelia
Traefik -->|Threat decisions| CrowdSec
Traefik -->|4xx/5xx fallback| ErrPages
Prometheus --> Grafana
Prometheus --> Gotify
```
For request-flow and network detail, see [docs/architecture.md](docs/architecture.md).
## Public docs publication workflow
Public docs are generated on the Docker host and committed to this repository. GitHub Actions only publishes committed content from `docs/public`.
1. Generate public docs locally from the repository root:
```bash
./scripts/generate-public-docs.sh
```
2. Inspect the generated changes:
```bash
git diff -- docs/public docs/generated docs/diagrams
```
3. Commit the generated public docs (and any supporting generated files you intend to version):
```bash
git add docs/public docs/generated docs/diagrams
git commit -m "docs: regenerate public docs"
```
4. Push your branch:
```bash
git push
```
Only files under `docs/public` are published by GitHub Pages. Internal/generated documentation is not published unless it is deliberately copied/sanitized into `docs/public`.
### Regenerating architecture docs (Prometheus + Dynu DNS)
```bash
# Refresh Dynu live inventory and generated resources/import helpers
cd infrastructure/terraform/dynu
terraform apply -refresh-only
python3 scripts/generate-brownfield-records.py --overwrite
# Regenerate architecture docs from Prometheus + Dynu inventory
cd ../../..
python3 scripts/render_prometheus_docs.py \
--inventory-file docs/runtime/prometheus-inventory.json \
--dynu-dns-inventory-file infrastructure/terraform/dynu/generated/dynu_dns_records_inventory.json
```
---
## Codex setup and maintenance scripts
The repository includes helper scripts for Codex sessions that need local tooling and safe placeholder secret material for validation-only workflows:
- `scripts/codex-setup.sh`
- Installs baseline CLI dependencies (shell/yaml/terraform/ansible tooling).
- Prepares `secrets/stack-secrets.env` from templates and creates dummy file-based secret placeholders based on `secrets/inventory.json`.
- Installs/refreshed baseline Ansible collections when `infrastructure/ansible/collections/requirements.yml` is present.
- Runs safe Ansible bootstrap checks (version, inventory parse, playbook syntax check) without live connectivity operations.
- Prints installed tool versions for quick verification.
- `scripts/codex-maintenance.sh`
- Refreshes Python-based linting/automation tooling.
- Reconciles placeholder secret files against current `secrets/inventory.json` (creates missing, removes stale).
- Rebuilds `secrets/stack-secrets.env` with dummy values for compose-config validation.
- Refreshes Ansible collections and repeats safe inventory/syntax validation checks.
Both scripts are intended for local validation environments and should not be treated as production provisioning automation.
+55
View File
@@ -0,0 +1,55 @@
# Gitea
## Gitea Actions
Gitea Actions is enabled by setting:
- `GITEA__actions__ENABLED=true`
## Runner service
The repository includes a dedicated Gitea Actions runner service named:
- `gitea-runner`
The runner uses Docker through the existing Docker socket proxy:
- `DOCKER_HOST=tcp://docker-socket-proxy:2375`
The runner intentionally **does not** mount:
- `/var/run/docker.sock`
## Registration token
Generate a runner registration token from the Gitea UI:
- Site Administration → Actions → Runners
- or Repo → Settings → Actions → Runners
Put the token in your env/secrets file:
- `GITEA_RUNNER_REGISTRATION_TOKEN=...`
## Start the runner
- `./services-up.sh --profile gitea up -d gitea-runner`
- or `./services-up.sh --profile all up -d gitea-runner`
## Logs
- `docker logs -f gitea-runner`
## Labels
Common workflow label:
- `runs-on: ubuntu-latest`
This should match the configured labels, for example:
- `GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bookworm,...`
## Security note
The runner can control Docker through `docker-socket-proxy`. This is safer than mounting the raw Docker socket directly, but workflows still have meaningful control over Docker. Only trusted repositories/users should be allowed to run workflows on this runner.
+24 -4
View File
@@ -5,10 +5,11 @@ services:
image: gitea/gitea:latest # change to 1-rootless once find out how to move data.
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=sqlite3
- GITEA__server__ROOT_URL=https://gitea.lan.ddnsgeek.com/
- USER_UID=${GITEA_USER_UID}
- USER_GID=${GITEA_USER_GID}
- GITEA__database__DB_TYPE=${GITEA_DB_TYPE}
- GITEA__server__ROOT_URL=${GITEA_ROOT_URL}
- GITEA__actions__ENABLED=true
volumes:
- ${PROJECT_ROOT}/apps/gitea/data:/data
networks:
@@ -31,6 +32,25 @@ services:
retries: 6
start_period: 120s
gitea-runner:
profiles: ["apps","all","gitea","ci"]
container_name: gitea-runner
image: gitea/act_runner:latest
restart: always
depends_on:
- gitea
- docker-socket-proxy
environment:
- GITEA_INSTANCE_URL=${GITEA_ROOT_URL}
- GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_REGISTRATION_TOKEN}
- GITEA_RUNNER_NAME=${GITEA_RUNNER_NAME}
- GITEA_RUNNER_LABELS=${GITEA_RUNNER_LABELS}
- DOCKER_HOST=${DOCKER_SOCKET_PROXY_HOST}
volumes:
- ${PROJECT_ROOT}/apps/gitea/runner-data:/data
networks:
- traefik
#volumes:
# gitea_data:
+85 -36
View File
@@ -1,47 +1,39 @@
services:
gramps-db:
profiles: ["apps","all","gramps"]
image: postgres:13
container_name: gramps-db
restart: always
environment:
POSTGRES_USER: gramps
POSTGRES_PASSWORD: grampspassword
POSTGRES_DB: gramps
volumes:
- ${PROJECT_ROOT}/apps/gramps/db:/var/lib/postgresql
networks:
- gramps
healthcheck:
test: ["CMD-SHELL", "pg_isready -h db -p 5432 -U gramps -d gramps"]
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
grampsweb:
profiles: ["apps","all","gramps"]
image: ghcr.io/gramps-project/grampsweb:latest
container_name: gramps-web
depends_on:
- gramps-db
- gramps-redis
- grampsweb_celery
restart: always
# ports:
# - "5000:5000" # access via http://localhost:5000
# env_file:
# - ${SECRETS_ENV_FILE}
environment:
DB_URI: postgresql://gramps:grampspassword@db:5432/gramps
GRAMPSWEB_LOGLEVEL: INFO
# default admin user created on first run:
INITIAL_ADMIN: admin
INITIAL_ADMIN_PASSWORD: admin
# optional: storage paths inside container
GRAMPSWEB_MEDIAPATH: /app/media
GRAMPSWEB_TREE: "main"
TZ: ${TZ}
GRAMPSWEB_TREE: ${GRAMPSWEB_TREE}
GRAMPSWEB_BASE_URL: ${GRAMPSWEB_BASE_URL}
GRAMPSWEB_SECRET_KEY: ${GRAMPSWEB_SECRET_KEY}
GRAMPSWEB_REGISTRATION_DISABLED: ${GRAMPSWEB_REGISTRATION_DISABLED}
GRAMPSWEB_EMAIL_HOST: ${GRAMPSWEB_EMAIL_HOST}
GRAMPSWEB_EMAIL_PORT: ${GRAMPSWEB_EMAIL_PORT}
GRAMPSWEB_EMAIL_HOST_USER: ${GRAMPSWEB_EMAIL_HOST_USER}
GRAMPSWEB_EMAIL_HOST_PASSWORD: ${GRAMPSWEB_EMAIL_HOST_PASSWORD}
GRAMPSWEB_EMAIL_USE_SSL: ${GRAMPSWEB_EMAIL_USE_SSL}
GRAMPSWEB_EMAIL_USE_STARTTLS: ${GRAMPSWEB_EMAIL_USE_STARTTLS}
GRAMPSWEB_DEFAULT_FROM_EMAIL: ${GRAMPSWEB_DEFAULT_FROM_EMAIL}
GRAMPSWEB_CELERY_CONFIG__broker_url: redis://gramps-redis:6379/0
GRAMPSWEB_CELERY_CONFIG__result_backend: redis://gramps-redis:6379/0
GRAMPSWEB_RATELIMIT_STORAGE_URI: redis://gramps-redis:6379/1
volumes:
- ${PROJECT_ROOT}/apps/gramps/data/users:/app/users
- ${PROJECT_ROOT}/apps/gramps/data/media:/app/media
- ${PROJECT_ROOT}/apps/gramps/data/index:/app/indexdir
- ${PROJECT_ROOT}/apps/gramps/data/thumbnail_cache:/app/thumbnail_cache
- ${PROJECT_ROOT}/apps/gramps/data/cache:/app/cache
- ${PROJECT_ROOT}/apps/gramps/data/secret:/app/secret
- ${PROJECT_ROOT}/apps/gramps/data/db:/root/.gramps/grampsdb
- ${PROJECT_ROOT}/apps/gramps/data/media:/app/media
- ${PROJECT_ROOT}/apps/gramps/data/tmp:/tmp
labels:
- "traefik.http.routers.gramps.rule=Host(`familytree.lan.ddnsgeek.com`)"
- "traefik.enable=true"
@@ -62,10 +54,67 @@ services:
retries: 6
start_period: 60s
grampsweb_celery:
profiles: ["apps","all","gramps"]
image: ghcr.io/gramps-project/grampsweb:latest
container_name: gramps-web-celery
command: celery -A gramps_webapi.celery worker --loglevel=INFO --concurrency=2
depends_on:
- gramps-redis
restart: always
# env_file:
# - ${SECRETS_ENV_FILE}
environment:
TZ: ${TZ}
GRAMPSWEB_TREE: ${GRAMPSWEB_TREE}
GRAMPSWEB_BASE_URL: ${GRAMPSWEB_BASE_URL}
GRAMPSWEB_SECRET_KEY: ${GRAMPSWEB_SECRET_KEY}
GRAMPSWEB_REGISTRATION_DISABLED: ${GRAMPSWEB_REGISTRATION_DISABLED}
GRAMPSWEB_EMAIL_HOST: ${GRAMPSWEB_EMAIL_HOST}
GRAMPSWEB_EMAIL_PORT: ${GRAMPSWEB_EMAIL_PORT}
GRAMPSWEB_EMAIL_HOST_USER: ${GRAMPSWEB_EMAIL_HOST_USER}
GRAMPSWEB_EMAIL_HOST_PASSWORD: ${GRAMPSWEB_EMAIL_HOST_PASSWORD}
GRAMPSWEB_EMAIL_USE_SSL: ${GRAMPSWEB_EMAIL_USE_SSL}
GRAMPSWEB_EMAIL_USE_STARTTLS: ${GRAMPSWEB_EMAIL_USE_STARTTLS}
GRAMPSWEB_DEFAULT_FROM_EMAIL: ${GRAMPSWEB_DEFAULT_FROM_EMAIL}
GRAMPSWEB_CELERY_CONFIG__broker_url: redis://gramps-redis:6379/0
GRAMPSWEB_CELERY_CONFIG__result_backend: redis://gramps-redis:6379/0
GRAMPSWEB_RATELIMIT_STORAGE_URI: redis://gramps-redis:6379/1
volumes:
- ${PROJECT_ROOT}/apps/gramps/data/users:/app/users
- ${PROJECT_ROOT}/apps/gramps/data/index:/app/indexdir
- ${PROJECT_ROOT}/apps/gramps/data/thumbnail_cache:/app/thumbnail_cache
- ${PROJECT_ROOT}/apps/gramps/data/cache:/app/cache
- ${PROJECT_ROOT}/apps/gramps/data/secret:/app/secret
- ${PROJECT_ROOT}/apps/gramps/data/db:/root/.gramps/grampsdb
- ${PROJECT_ROOT}/apps/gramps/data/media:/app/media
- ${PROJECT_ROOT}/apps/gramps/data/tmp:/tmp
networks:
- gramps
healthcheck:
test:
- CMD-SHELL
- pgrep -f "celery.*gramps_webapi.celery.*worker" >/dev/null
interval: 30s
timeout: 5s
retries: 6
start_period: 60s
gramps-redis:
profiles: ["apps","all","gramps"]
image: valkey/valkey:8-alpine
container_name: gramps-redis
restart: always
networks:
- gramps
healthcheck:
test:
- CMD-SHELL
- valkey-cli -h 127.0.0.1 -p 6379 ping | grep -q PONG
interval: 10s
timeout: 5s
retries: 6
start_period: 10s
networks:
# traefik_reverse_proxy:
# external: true
gramps:
# driver: bridge
+56 -44
View File
@@ -1,12 +1,13 @@
services:
nextcloud-webapp:
# image: nextcloud:production
profiles: ["apps","all","nextcloud"]
build:
context: ${PROJECT_ROOT}/apps/nextcloud
container_name: nextcloud-webapp
restart: always
hostname: nextcloud.lan.ddnsgeek.com
hostname: ${NEXTCLOUD_TRUSTED_DOMAINS}
# env_file:
# - ${SECRETS_ENV_FILE}
volumes:
- ${PROJECT_ROOT}/apps/nextcloud/data:/var/www/html/data:rw
- ${PROJECT_ROOT}/apps/nextcloud/config:/var/www/html/config:rw
@@ -16,31 +17,33 @@ services:
- nextcloud-db
- nextcloud-redis
environment:
- MYSQL_PASSWORD=R1m@dmin
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_HOST=nextcloud_db:3306
- NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.lan.ddnsgeek.com
- OVERWRITEPROTOCOL=https
- OVERWRITECLIURL=https://nextcloud.lan.ddnsgeek.com
- SMTP_HOST=smtp.gmail.com
- SMTP_SECURE=tls
- SMTP_PORT=587
- SMTP_AUTHTYPE=login
- MAIL_FROM_ADDRESS=beatz174
- MAIL_DOMAIN=gmail.com
- SMTP_NAME=beatz174@gmail.com
- SMTP_PASSWORD=kqdw fvml wlag ldgv
- REDIS_HOST=redis
- REDIS_HOST_PORT=6379
- REDIS_HOST_PASSWORD=TzBF8wcJNmVd9p2CTmBejPS9dpye6kWQeH3DmrQS9TPfTRriSHFN5VqH4CgzcuVZYWH2GBb7QU5GuEpNDGYdKjM6hjmLyjSgCFMiPms3Hv9n
- MYSQL_PASSWORD_FILE=/run/secrets/nextcloud_db_password
- MYSQL_DATABASE=${NEXTCLOUD_MYSQL_DATABASE}
- MYSQL_USER=${NEXTCLOUD_DB_USER}
- MYSQL_HOST=${NEXTCLOUD_MYSQL_HOST}
- NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_TRUSTED_DOMAINS}
- OVERWRITEPROTOCOL=${NEXTCLOUD_OVERWRITEPROTOCOL}
- OVERWRITECLIURL=${NEXTCLOUD_OVERWRITECLIURL}
- SMTP_HOST=${NEXTCLOUD_SMTP_HOST}
- SMTP_SECURE=${NEXTCLOUD_SMTP_SECURE}
- SMTP_PORT=${NEXTCLOUD_SMTP_PORT}
- SMTP_AUTHTYPE=${NEXTCLOUD_SMTP_AUTHTYPE}
- MAIL_FROM_ADDRESS=${NEXTCLOUD_SMTP_FROM_ADDRESS}
- MAIL_DOMAIN=${NEXTCLOUD_SMTP_DOMAIN}
- SMTP_NAME=${NEXTCLOUD_SMTP_NAME}
- SMTP_PASSWORD_FILE=/run/secrets/nextcloud_smtp_password
- REDIS_HOST=${NEXTCLOUD_REDIS_HOST}
- REDIS_HOST_PORT=${NEXTCLOUD_REDIS_HOST_PORT}
- REDIS_HOST_PASSWORD_FILE=/run/secrets/nextcloud_redis_password
secrets:
- nextcloud_db_password
- nextcloud_smtp_password
- nextcloud_redis_password
networks:
- traefik
- nextcloud
labels:
- "traefik.http.routers.nextcloud.rule=Host(`nextcloud.lan.ddnsgeek.com`)"
- "traefik.http.routers.nextcloud.rule=Host(`${NEXTCLOUD_TRUSTED_DOMAINS}`)"
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=myresolver"
@@ -54,7 +57,6 @@ services:
- "traefik.http.middlewares.nextcloud-webfinger.redirectregex.regex=https://(.*)/.well-known/webfinger"
- "traefik.http.middlewares.nextcloud-webfinger.redirectregex.replacement=https://$${1}/nextcloud/index.php/.well-known/webfinger"
- "traefik.docker.network=core_traefik"
healthcheck:
test:
- CMD-SHELL
@@ -68,9 +70,6 @@ services:
retries: 6
start_period: 180s
nextcloud-db:
image: mariadb:11.4
restart: always
@@ -78,36 +77,41 @@ services:
container_name: nextcloud-db
hostname: nextcloud_db
command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
# env_file:
# - ${PROJECT_ROOT}/secrets/stack-secrets.env
volumes:
- ${PROJECT_ROOT}/apps/nextcloud/database:/var/lib/mysql:rw
environment:
- MYSQL_ROOT_PASSWORD=R1m@dmin
- MYSQL_PASSWORD=R1m@dmin
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MARIADB_AUTO_UPGRADE=1
- NEXTCLOUD_ADMIN_USER=admin
- NEXTCLOUD_ADMIN_PASSWORD=R1m@dmin
- MYSQL_ROOT_PASSWORD_FILE=/run/secrets/nextcloud_db_root_password
- MYSQL_PASSWORD_FILE=/run/secrets/nextcloud_db_password
- MYSQL_DATABASE=${NEXTCLOUD_MYSQL_DATABASE}
- MYSQL_USER=${NEXTCLOUD_DB_USER}
- MARIADB_AUTO_UPGRADE=${NEXTCLOUD_MARIADB_AUTO_UPGRADE}
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER}
- NEXTCLOUD_ADMIN_PASSWORD_FILE=/run/secrets/nextcloud_admin_password
secrets:
- nextcloud_db_root_password
- nextcloud_db_password
- nextcloud_admin_password
networks:
- nextcloud
labels:
- "io.portainer.accesscontrol.public"
healthcheck:
test: ["CMD-SHELL", "mariadb-admin ping -u nextcloud --password=R1m@dmin --silent"]
test: ["CMD-SHELL", "mariadb-admin ping -u $$MYSQL_USER --password=$$(cat /run/secrets/nextcloud_db_password) --silent"]
interval: 10s
timeout: 5s
retries: 12
start_period: 60s
nextcloud-redis:
image: "redis"
profiles: ["apps","all","nextcloud"]
command: ["redis-server", "--requirepass", "TzBF8wcJNmVd9p2CTmBejPS9dpye6kWQeH3DmrQS9TPfTRriSHFN5VqH4CgzcuVZYWH2GBb7QU5GuEpNDGYdKjM6hjmLyjSgCFMiPms3Hv9n", "--appendonly", "yes", "--save", "60", "1000"]
command: ["sh", "-c", "redis-server --requirepass \"$$(cat /run/secrets/nextcloud_redis_password)\" --appendonly yes --save 60 1000"]
hostname: redis
container_name: nextcloud-redis
environment:
- REDIS_HOST_PASSWORD=TzBF8wcJNmVd9p2CTmBejPS9dpye6kWQeH3DmrQS9TPfTRriSHFN5VqH4CgzcuVZYWH2GBb7QU5GuEpNDGYdKjM6hjmLyjSgCFMiPms3Hv9n
secrets:
- nextcloud_redis_password
volumes:
- ${PROJECT_ROOT}/apps/nextcloud/data/redis:/data:rw
restart: always
@@ -116,15 +120,23 @@ services:
labels:
- "io.portainer.accesscontrol.public"
healthcheck:
test: ["CMD-SHELL", "redis-cli -a TzBF8wcJNmVd9p2CTmBejPS9dpye6kWQeH3DmrQS9TPfTRriSHFN5VqH4CgzcuVZYWH2GBb7QU5GuEpNDGYdKjM6hjmLyjSgCFMiPms3Hv9n PING | grep -q PONG"]
test: ["CMD-SHELL", "redis-cli -a \"$$(cat /run/secrets/nextcloud_redis_password)\" PING | grep -q PONG"]
interval: 10s
timeout: 5s
retries: 6
start_period: 10s
networks:
# traefik_reverse_proxy:
# external: true
nextcloud:
# driver: bridge
secrets:
nextcloud_db_root_password:
file: ${PROJECT_ROOT}/secrets/nextcloud_db_root_password.txt
nextcloud_db_password:
file: ${PROJECT_ROOT}/secrets/nextcloud_db_password.txt
nextcloud_admin_password:
file: ${PROJECT_ROOT}/secrets/nextcloud_admin_password.txt
nextcloud_smtp_password:
file: ${PROJECT_ROOT}/secrets/nextcloud_smtp_password.txt
nextcloud_redis_password:
file: ${PROJECT_ROOT}/secrets/nextcloud_redis_password.txt
+23 -21
View File
@@ -4,17 +4,21 @@ services:
container_name: passbolt-db
image: mariadb:12
restart: always
# env_file:
# - ${PROJECT_ROOT}/secrets/stack-secrets.env
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "true"
MYSQL_DATABASE: "passbolt"
MYSQL_USER: "passbolt"
MYSQL_PASSWORD: "P4ssb0lt"
MYSQL_RANDOM_ROOT_PASSWORD: ${PASSBOLT_MYSQL_RANDOM_ROOT_PASSWORD}
MYSQL_DATABASE: ${PASSBOLT_DB_NAME}
MYSQL_USER: ${PASSBOLT_DB_USER}
MYSQL_PASSWORD_FILE: /run/secrets/passbolt_db_password
secrets:
- passbolt_db_password
volumes:
- ${PROJECT_ROOT}/apps/passbolt/data/database:/var/lib/mysql
networks:
- passbolt
healthcheck:
test: ["CMD-SHELL", "mariadb-admin ping -h 127.0.0.1 -u\"$$MARIADB_USER\" -p\"$$MARIADB_PASSWORD\" --silent"]
test: ["CMD-SHELL", "mariadb-admin ping -h 127.0.0.1 -u\"$$MYSQL_USER\" -p\"$$(cat /run/secrets/passbolt_db_password)\" --silent"]
interval: 10s
timeout: 5s
retries: 12
@@ -22,22 +26,24 @@ services:
labels:
- "io.portainer.accesscontrol.public"
passbolt-webapp:
image: passbolt/passbolt:latest-ce
profiles: ["apps","all","passbolt"]
container_name: passbolt-webapp
#Alternatively you can use rootless:
restart: always
depends_on:
- passbolt-db
# env_file:
# - ${PROJECT_ROOT}/secrets/stack-secrets.env
environment:
APP_FULL_BASE_URL: https://passbolt.lan.ddnsgeek.com
DATASOURCES_DEFAULT_HOST: "passbolt-db"
DATASOURCES_DEFAULT_USERNAME: "passbolt"
DATASOURCES_DEFAULT_PASSWORD: "P4ssb0lt"
DATASOURCES_DEFAULT_DATABASE: "passbolt"
PASSBOLT_GPG_SERVER_KEY_FINGERPRINT: "CBBB2B8F3E9FACA114537ACB8965B750F7363586"
APP_FULL_BASE_URL: ${PASSBOLT_APP_FULL_BASE_URL}
DATASOURCES_DEFAULT_HOST: ${PASSBOLT_DATASOURCES_DEFAULT_HOST}
DATASOURCES_DEFAULT_USERNAME: ${PASSBOLT_DB_USER}
DATASOURCES_DEFAULT_PASSWORD_FILE: /run/secrets/passbolt_db_password
DATASOURCES_DEFAULT_DATABASE: ${PASSBOLT_DB_NAME}
PASSBOLT_GPG_SERVER_KEY_FINGERPRINT: ${PASSBOLT_GPG_SERVER_KEY_FINGERPRINT}
secrets:
- passbolt_db_password
volumes:
- ${PROJECT_ROOT}/apps/passbolt/data/gpg:/etc/passbolt/gpg
- ${PROJECT_ROOT}/apps/passbolt/data/jwt:/etc/passbolt/jwt
@@ -60,20 +66,16 @@ services:
- "traefik.http.routers.passbolt.tls.certresolver=myresolver"
- "io.portainer.accesscontrol.public"
- "traefik.docker.network=core_traefik"
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost/healthcheck/status | grep -qx OK"]
# su -s /bin/sh -c "/usr/share/php/passbolt/bin/cake passbolt healthcheck" www-data
# | grep -q "No error found"
interval: 30s
timeout: 10s
retries: 6
start_period: 120s
networks:
# traefik_reverse_proxy:
# external: true
# internal:
# driver: bridge
passbolt:
secrets:
passbolt_db_password:
file: ${PROJECT_ROOT}/secrets/passbolt_db_password.txt
-3
View File
@@ -23,8 +23,6 @@ authentication_backend:
access_control:
default_policy: deny
rules:
# - domain: "*.lan.ddnsgeek.com"
# policy: two_factor
- domain: alertmanager.lan.ddnsgeek.com
resources:
- "^/api/.*"
@@ -52,7 +50,6 @@ access_control:
- "^/metrics"
policy: bypass
- domain: "*.lan.ddnsgeek.com"
policy: two_factor
+30
View File
@@ -0,0 +1,30 @@
services:
authelia:
profiles: ["core","all","authelia", "traefik"]
image: authelia/authelia
restart: always
build:
context: ${PROJECT_ROOT}/core/authelia
# env_file:
# - ${PROJECT_ROOT}/secrets/stack-secrets.env
# environment:
# - AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET:${AUTHELIA_JWT_SECRET}
# - AUTHELIA_SESSION_SECRET:${AUTHELIA_SESSION_SECRET}
# - AUTHELIA_STORAGE_ENCRYPTION_KEY:${AUTHELIA_STORAGE_ENCRYPTION_KEY}
volumes:
- ${PROJECT_ROOT}/core/authelia:/config
networks:
# - reverse_proxy
- traefik
container_name: authelia
labels:
- traefik.enable=true
- traefik.http.routers.authelia.rule=Host(`auth.lan.ddnsgeek.com`)
- traefik.http.routers.authelia.entrypoints=websecure
- traefik.http.routers.authelia.tls=true
- traefik.http.routers.authelia.tls.certresolver=myresolver
- io.portainer.accesscontrol.public
- traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.lan.ddnsgeek.com/
- traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true
- traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups
- traefik.http.middlewares.authelia.forwardauth.maxResponseBodySize=2097152
+23
View File
@@ -0,0 +1,23 @@
services:
crowdsec:
profiles: ["core","all","crowdsec", "traefik"]
# image: crowdsecurity/crowdsec:latest
build: ${PROJECT_ROOT}/core/crowdsec
container_name: crowdsec
restart: always
environment:
- COLLECTIONS=crowdsecurity/traefik
# - CROWDSEC_LAPI_KEY=${CROWDSEC_LAPI_KEY}
volumes:
- ${PROJECT_ROOT}/core/crowdsec/logs:/logs:ro
- ${PROJECT_ROOT}/core/crowdsec/data:/var/lib/crowdsec/data
- ${PROJECT_ROOT}/core/crowdsec/config:/etc/crowdsec
networks:
# - reverse_proxy
- traefik
healthcheck:
test: ["CMD-SHELL", "cscli metrics || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
-123
View File
@@ -1,123 +0,0 @@
services:
traefik:
profiles: ["core","all","traefik"]
image: traefik:3
container_name: traefik
restart: always
read_only: true
hostname: traefik.lan.ddnsgeek.com
depends_on:
- error-pages
- authelia
- crowdsec
ports:
- "80:80"
- "443:443"
build:
context: ${PROJECT_ROOT}/core
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${PROJECT_ROOT}/core/traefik/data/letsencrypt:/letsencrypt
- ${PROJECT_ROOT}/core/traefik/data/logs:/logs
- ${PROJECT_ROOT}/core/traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
- ${PROJECT_ROOT}/core/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ${PROJECT_ROOT}/core/traefik/data/plugins:/plugins-storage
healthcheck:
test: traefik healthcheck --ping
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`traefik.lan.ddnsgeek.com`)"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=myresolver"
- "traefik.http.routers.traefik.middlewares=authelia"
- "io.portainer.accesscontrol.public"
- "traefik.docker.network=core_traefik"
- "traefik.http.routers.traefik.observability.tracing=true"
networks:
# - reverse_proxy
# - prometheus_edge
- traefik
crowdsec:
# image: crowdsecurity/crowdsec:latest
profiles: ["core","all","traefik"]
build: ${PROJECT_ROOT}/core/crowdsec
container_name: crowdsec
restart: always
environment:
- COLLECTIONS=crowdsecurity/traefik
volumes:
- ${PROJECT_ROOT}/core/crowdsec/logs:/logs:ro
- ${PROJECT_ROOT}/core/crowdsec/data:/var/lib/crowdsec/data
- ${PROJECT_ROOT}/core/crowdsec/config:/etc/crowdsec
networks:
# - reverse_proxy
- traefik
healthcheck:
test: ["CMD-SHELL", "cscli metrics || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
error-pages:
profiles: ["core","all","traefik"]
image: tarampampam/error-pages:3
restart: always
container_name: error-pages
read_only: true
environment:
TEMPLATE_NAME: app-down
networks:
# - reverse_proxy
- traefik
hostname: error-pages
labels:
- "traefik.enable=true"
# use as "fallback" for any NON-registered services (with priority below normal)
- "traefik.http.routers.error-pages-router.rule=HostRegexp(`{host:.+}`)"
# should say that all of your services work on https
- "traefik.http.routers.error-pages-router.entrypoints=web"
- "traefik.http.routers.error-pages-router.middlewares=error-pages-middleware"
# "errors" middleware settings
- "traefik.http.middlewares.error-pages-middleware.errors.status=400-599"
- "traefik.http.middlewares.error-pages-middleware.errors.service=error-pages-service"
- "traefik.http.middlewares.error-pages-middleware.errors.query=/{status}.html"
# define service properties
- "traefik.http.services.error-pages-service.loadbalancer.server.port=8080"
- "io.portainer.accesscontrol.public"
authelia:
profiles: ["core","all","traefik"]
image: authelia/authelia
restart: always
build:
context: ${PROJECT_ROOT}/core/authelia
volumes:
- ${PROJECT_ROOT}/core/authelia:/config
networks:
# - reverse_proxy
- traefik
container_name: authelia
labels:
- traefik.enable=true
- traefik.http.routers.authelia.rule=Host(`auth.lan.ddnsgeek.com`)
- traefik.http.routers.authelia.entrypoints=websecure
- traefik.http.routers.authelia.tls=true
- traefik.http.routers.authelia.tls.certresolver=myresolver
- io.portainer.accesscontrol.public
- traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.lan.ddnsgeek.com/
- traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true
- traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups
- traefik.http.middlewares.authelia.forwardauth.maxResponseBodySize=2097152
#networks:
# reverse_proxy:
# driver: bridge
# prometheus_edge:
# external: true
+27
View File
@@ -0,0 +1,27 @@
services:
error-pages:
profiles: ["core","all","error-pages", "traefik"]
image: tarampampam/error-pages:3
restart: always
container_name: error-pages
read_only: true
environment:
TEMPLATE_NAME: ${ERROR_PAGES_TEMPLATE_NAME}
networks:
# - reverse_proxy
- traefik
hostname: error-pages
labels:
- "traefik.enable=true"
# use as "fallback" for any NON-registered services (with priority below normal)
- "traefik.http.routers.error-pages-router.rule=HostRegexp(`{host:.+}`)"
# should say that all of your services work on https
- "traefik.http.routers.error-pages-router.entrypoints=web"
- "traefik.http.routers.error-pages-router.middlewares=error-pages-middleware"
# "errors" middleware settings
- "traefik.http.middlewares.error-pages-middleware.errors.status=400-599"
- "traefik.http.middlewares.error-pages-middleware.errors.service=error-pages-service"
- "traefik.http.middlewares.error-pages-middleware.errors.query=/{status}.html"
# define service properties
- "traefik.http.services.error-pages-service.loadbalancer.server.port=8080"
- "io.portainer.accesscontrol.public"
+4 -1
View File
@@ -17,8 +17,11 @@ services:
container_name: docker-update-exporter-test
stdin_open: true
tty: true
# depends_on:
# - docker-socket-proxy
environment:
DOCKER_HOST: tcp://docker-socket-proxy:2375
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ${PROJECT_ROOT}/monitoring/docker-exporter/data:/data:rw
# - ${PROJECT_ROOT}/services-up.sh:/app/services-up.sh:ro
+38
View File
@@ -0,0 +1,38 @@
# Private-admin mTLS for Traefik
`private-admin` routers are configured to require client certificates via the Traefik TLS option `mtls-private-admin@file`.
## Certificate paths
- Trusted client CA bundle expected by Traefik:
- `core/traefik/certs/ca/clients-ca.crt`
- CA private key (keep secret, never commit):
- `core/traefik/certs/ca/clients-ca.key`
- Issued client certs:
- `core/traefik/certs/clients/<client-name>/`
## Bootstrap
From repository root:
```bash
./core/traefik/scripts/init-mtls-ca.sh
./core/traefik/scripts/issue-mtls-client-cert.sh admin-laptop
```
The second command exports a PKCS#12 bundle (`.p12`) for browser import and also leaves PEM `.crt`/`.key` artifacts for CLI usage.
## Revocation workflow
Because Traefik is configured with `clientAuth.caFiles`, revoked cert serials are not enforced by default.
- Use `./core/traefik/scripts/revoke-mtls-client-cert.sh <client-name>` to quarantine a client cert bundle.
- For strict revocation, rotate the CA (`init-mtls-ca.sh` after removing old CA) and re-issue all trusted client certs.
## Deploy
After CA/certs are in place, restart Traefik to ensure updated files are loaded:
```bash
docker compose -f core/traefik/docker-compose.yml up -d traefik
```
View File
+50
View File
@@ -0,0 +1,50 @@
services:
traefik:
profiles: ["core","all","traefik"]
image: traefik:3
container_name: traefik
restart: always
read_only: true
hostname: traefik.lan.ddnsgeek.com
depends_on:
- docker-socket-proxy
- error-pages
- authelia
- crowdsec
ports:
- "80:80"
- "443:443"
build:
context: ${PROJECT_ROOT}/core
# env_file:
# - ${PROJECT_ROOT}/secrets/stack-secrets.env
volumes:
- ${PROJECT_ROOT}/core/traefik/data/letsencrypt:/letsencrypt
- ${PROJECT_ROOT}/core/traefik/data/logs:/logs
- ${PROJECT_ROOT}/core/traefik/certs:/etc/traefik/certs:ro
- ${PROJECT_ROOT}/core/traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
- ${PROJECT_ROOT}/core/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ${PROJECT_ROOT}/core/traefik/data/plugins:/plugins-storage
healthcheck:
test: traefik healthcheck --ping
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`traefik.lan.ddnsgeek.com`)"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=myresolver"
- "traefik.http.routers.traefik.tls.options=mtls-private-admin@file"
- "traefik.http.routers.traefik.middlewares=authelia"
- "io.portainer.accesscontrol.public"
- "traefik.docker.network=core_traefik"
- "traefik.http.routers.traefik.observability.tracing=true"
networks:
# - reverse_proxy
# - prometheus_edge
- traefik
+10
View File
@@ -31,3 +31,13 @@ http:
- crowdsec@file
# - tracing-middleware@file
- error-pages-middleware@docker
tls:
options:
mtls-private-admin:
minVersion: VersionTLS12
sniStrict: true
clientAuth:
caFiles:
- /etc/traefik/certs/ca/clients-ca.crt
clientAuthType: RequireAndVerifyClientCert
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TRAEFIK_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
CA_DIR="${TRAEFIK_ROOT}/certs/ca"
CA_KEY="${CA_DIR}/clients-ca.key"
CA_CERT="${CA_DIR}/clients-ca.crt"
CA_SERIAL="${CA_DIR}/clients-ca.srl"
DAYS="${DAYS:-3650}"
SUBJECT="${SUBJECT:-/CN=Traefik Private Admin Client CA/O=Homelab}"
mkdir -p "${CA_DIR}"
chmod 700 "${CA_DIR}"
if [[ -f "${CA_KEY}" || -f "${CA_CERT}" ]]; then
echo "Refusing to overwrite existing CA material in ${CA_DIR}."
echo "Delete existing files first if you intentionally want to rotate the CA."
exit 1
fi
openssl genrsa -out "${CA_KEY}" 4096
chmod 600 "${CA_KEY}"
openssl req -x509 -new -nodes \
-key "${CA_KEY}" \
-sha256 \
-days "${DAYS}" \
-subj "${SUBJECT}" \
-out "${CA_CERT}"
chmod 644 "${CA_CERT}"
rm -f "${CA_SERIAL}"
echo "Created mTLS client CA: ${CA_CERT}"
echo "Use issue-mtls-client-cert.sh to issue client certificates signed by this CA."
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <client-name> [days]"
exit 1
fi
CLIENT_NAME="$1"
DAYS="${2:-825}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TRAEFIK_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
CA_DIR="${TRAEFIK_ROOT}/certs/ca"
CLIENT_DIR="${TRAEFIK_ROOT}/certs/clients/${CLIENT_NAME}"
CA_KEY="${CA_DIR}/clients-ca.key"
CA_CERT="${CA_DIR}/clients-ca.crt"
CA_SERIAL="${CA_DIR}/clients-ca.srl"
CLIENT_KEY="${CLIENT_DIR}/${CLIENT_NAME}.key"
CLIENT_CSR="${CLIENT_DIR}/${CLIENT_NAME}.csr"
CLIENT_CERT="${CLIENT_DIR}/${CLIENT_NAME}.crt"
CLIENT_P12="${CLIENT_DIR}/${CLIENT_NAME}.p12"
OPENSSL_EXT="${CLIENT_DIR}/client.ext"
if [[ ! -f "${CA_KEY}" || ! -f "${CA_CERT}" ]]; then
echo "Missing CA material. Run init-mtls-ca.sh first."
exit 1
fi
if [[ -d "${CLIENT_DIR}" ]]; then
echo "Client directory already exists (${CLIENT_DIR}); refusing to overwrite."
exit 1
fi
mkdir -p "${CLIENT_DIR}"
chmod 700 "${CLIENT_DIR}"
cat > "${OPENSSL_EXT}" <<EXT
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
subjectAltName = DNS:${CLIENT_NAME}
EXT
openssl genrsa -out "${CLIENT_KEY}" 2048
chmod 600 "${CLIENT_KEY}"
openssl req -new -key "${CLIENT_KEY}" -subj "/CN=${CLIENT_NAME}" -out "${CLIENT_CSR}"
openssl x509 -req \
-in "${CLIENT_CSR}" \
-CA "${CA_CERT}" \
-CAkey "${CA_KEY}" \
-CAcreateserial \
-out "${CLIENT_CERT}" \
-days "${DAYS}" \
-sha256 \
-extfile "${OPENSSL_EXT}"
chmod 644 "${CLIENT_CERT}"
openssl pkcs12 -export \
-inkey "${CLIENT_KEY}" \
-in "${CLIENT_CERT}" \
-certfile "${CA_CERT}" \
-name "${CLIENT_NAME}" \
-out "${CLIENT_P12}"
chmod 600 "${CLIENT_P12}"
rm -f "${CLIENT_CSR}" "${OPENSSL_EXT}"
echo "Issued client certificate for ${CLIENT_NAME}."
echo "CRT: ${CLIENT_CERT}"
echo "KEY: ${CLIENT_KEY}"
echo "P12: ${CLIENT_P12}"
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <client-name>"
exit 1
fi
CLIENT_NAME="$1"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TRAEFIK_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
CLIENT_DIR="${TRAEFIK_ROOT}/certs/clients/${CLIENT_NAME}"
REVOKED_DIR="${TRAEFIK_ROOT}/certs/revoked"
if [[ ! -d "${CLIENT_DIR}" ]]; then
echo "No certificate directory found for client '${CLIENT_NAME}'."
exit 1
fi
mkdir -p "${REVOKED_DIR}"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
mv "${CLIENT_DIR}" "${REVOKED_DIR}/${CLIENT_NAME}-${STAMP}"
echo "Moved client certificate material to ${REVOKED_DIR}/${CLIENT_NAME}-${STAMP}."
echo "Note: Traefik clientAuth with a CA file does not enforce revocation lists by default."
echo "For immediate hard revocation, rotate the client CA and re-issue trusted client certificates."
+16 -2
View File
@@ -13,6 +13,7 @@ ping: {}
providers:
docker:
endpoint: "tcp://docker-socket-proxy:2375"
exposedByDefault: false
file:
@@ -23,7 +24,16 @@ entryPoints:
web:
address: ":80"
forwardedHeaders:
insecure: true
# Trust forwarding headers only from upstream proxies/LBs under our control.
# Network assumptions for this stack:
# - 127.0.0.1/32: local host-side reverse-proxy hops
# - 192.168.2.0/24: LAN edge proxies
# - 172.21.0.0/16: pinned Docker subnet for the traefik bridge network
insecure: false
trustedIPs:
- "127.0.0.1/32"
- "192.168.2.0/24"
- "172.21.0.0/16"
http:
redirections:
entryPoint:
@@ -33,7 +43,11 @@ entryPoints:
websecure:
address: ":443"
forwardedHeaders:
insecure: true
insecure: false
trustedIPs:
- "127.0.0.1/32"
- "192.168.2.0/24"
- "172.21.0.0/16"
http:
middlewares:
- default-chain@file
+80
View File
@@ -2,3 +2,83 @@ PROJECT_ROOT=/home/nixos/docker
DOMAIN=lan.ddnsgeek.com
TZ=Australia/Brisbane
EMAIL=wayne.bennett@live.com
SECRETS_ENV_FILE=${PROJECT_ROOT}/secrets/stack-secrets.env
# Core
CROWDSEC_COLLECTIONS=crowdsecurity/traefik
ERROR_PAGES_TEMPLATE_NAME=app-down
# Gitea
GITEA_USER_UID=1000
GITEA_USER_GID=1000
GITEA_DB_TYPE=sqlite3
GITEA_ROOT_URL=https://gitea.lan.ddnsgeek.com/
# Generate a token in Gitea: Site Administration → Actions → Runners
# or Repo → Settings → Actions → Runners
GITEA_RUNNER_REGISTRATION_TOKEN=vYDNxzMvayREkXoaAR3x3UREkxQB2PU4eORzmkZ9
GITEA_RUNNER_NAME=docker-runner-01
GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bookworm,ubuntu-22.04:docker://node:20-bookworm,linux:docker://node:20-bookworm,docker:docker://docker:cli
# Grafana
GRAFANA_ROOT_URL=https://grafana.lan.ddnsgeek.com/
# Nextcloud
NEXTCLOUD_MYSQL_DATABASE=nextcloud
NEXTCLOUD_MYSQL_HOST=nextcloud_db:3306
NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.lan.ddnsgeek.com
NEXTCLOUD_OVERWRITEPROTOCOL=https
NEXTCLOUD_OVERWRITECLIURL=https://${NEXTCLOUD_TRUSTED_DOMAINS}
NEXTCLOUD_SMTP_HOST=smtp.gmail.com
NEXTCLOUD_SMTP_SECURE=tls
NEXTCLOUD_SMTP_PORT=587
NEXTCLOUD_SMTP_AUTHTYPE=login
NEXTCLOUD_REDIS_HOST=redis
NEXTCLOUD_REDIS_HOST_PORT=6379
NEXTCLOUD_MARIADB_AUTO_UPGRADE=1
# Passbolt
PASSBOLT_MYSQL_RANDOM_ROOT_PASSWORD=true
PASSBOLT_MYSQL_DATABASE=passbolt
PASSBOLT_MYSQL_USER=passbolt
PASSBOLT_APP_FULL_BASE_URL=https://passbolt.lan.ddnsgeek.com
PASSBOLT_DATASOURCES_DEFAULT_HOST=passbolt-db
# Gramps
GRAMPSWEB_TREE=main
GRAMPSWEB_BASE_URL=https://familytree.lan.ddnsgeek.com
GRAMPSWEB_REGISTRATION_DISABLED=true
GRAMPSWEB_EMAIL_HOST=smtp.gmail.com
GRAMPSWEB_EMAIL_PORT=587
GRAMPSWEB_EMAIL_USE_SSL=false
GRAMPSWEB_EMAIL_USE_STARTTLS=true
GRAMPSWEB_DEFAULT_FROM_EMAIL=beatz174@gmail.com
# Prometheus stack
INFLUXDB_INIT_MODE=setup
INFLUXDB_INIT_ORG=pbs
INFLUXDB_INIT_BUCKET=telemetry
DOCKER_EXPORTER_LOG_LEVEL=INFO
PIHOLE_HOSTNAME=pihole.sweet.home
PIHOLE_EXPORTER_PORT=9617
# Gotify
GOTIFY_REGISTRATION=false
# Portainer
PORTAINER_GODEBUG=netdns=cgo
# Node-red
DOCKER_SOCKET_PROXY_HOST=tcp://docker-socket-proxy:2375
DOCKER_SOCKET_PROXY_LOG_LEVEL=info
NODE_COMPOSE_ROOT=/compose
# mTLS bridge
MTLS_BRIDGE_LOG_LEVEL=DEBUG
MTLS_BRIDGE_TIMEOUT=5
MTLS_BRIDGE_CLIENT_KEY=/certs/clients/office-pc/office-pc.key
MTLS_BRIDGE_CLIENT_CERT=/certs/clients/office-pc/office-pc.crt
MTLS_BRIDGE_TARGET_URL=http://node-red:1880
MTLS_BRIDGE_ALLOWED_PATHS_FILE=
MTLS_BRIDGE_CORS_ALLOW_ORIGIN=https://grafana.lan.ddnsgeek.com
+3 -1
View File
@@ -1,5 +1,7 @@
networks:
traefik:
driver: bridge
ipam:
config:
- subnet: 172.21.0.0/16
monitor:
+41
View File
@@ -0,0 +1,41 @@
# Generated Documentation
## Local generation
Install prerequisites:
```bash
sudo apt-get update
sudo apt-get install -y graphviz
```
Then generate and validate public docs:
```bash
chmod +x scripts/docs/*.sh
scripts/docs/generate-all.sh
python3 -m mkdocs build -f mkdocs-public.yml --strict
```
NixOS-friendly alternative:
```bash
nix shell nixpkgs#graphviz nixpkgs#python3 nixpkgs#python3Packages.pyyaml
```
This pipeline only runs `docker compose config` and static parsing. It does **not** start containers.
## CI behaviour
GitHub Actions workflow `.github/workflows/generate-docs.yml` validates committed public docs and diagrams and runs a strict public MkDocs build.
## Outputs
- `docs/generated`: resolved compose config and markdown inventories
- `docs/diagrams`: generated DOT and SVG diagrams
- `docs/public`: sanitized copy for public sharing
## Publication safety
- `docs/public` is intended for public sharing after sanitization.
- `docs/generated` and `docs/diagrams` may include internal details and should be treated as internal by default.
+72
View File
@@ -0,0 +1,72 @@
# Ansible Workflows (Bootstrap / Phase 1)
Ansible is being introduced as a minimal, maintainable foundation for host/device inventory and future configuration workflows.
## Why introduce Ansible now
- The repository already has strong runtime and infrastructure boundaries (Compose + Terraform).
- A small Ansible baseline allows gradual host onboarding without forcing immediate large-scale automation.
- It enables safe validation workflows (`inventory --list`, playbook syntax checks) before real execution.
## What Ansible is for in this repository (right now)
- YAML inventory structure for hosts/devices to be onboarded over time.
- Group and host variable scaffolding for future incremental adoption.
- Validation-oriented starter playbook and local tooling checks.
## What Ansible is not for yet
- Replacing Docker Compose runtime authority.
- Replacing Terraform inventory/reconciliation authority.
- Becoming the current source of truth for NixOS host management.
- Becoming the current source of truth for all network automation.
## Directory layout
- `infrastructure/ansible/ansible.cfg`
- `infrastructure/ansible/inventory/hosts.yml`
- `infrastructure/ansible/inventory/group_vars/`
- `infrastructure/ansible/inventory/host_vars/`
- `infrastructure/ansible/playbooks/ping.yml`
- `infrastructure/ansible/collections/requirements.yml`
## Add a host (gradual onboarding)
1. Open `infrastructure/ansible/inventory/hosts.yml`.
2. Add the host under an appropriate group (`linux`, `network`, `virtualization`, or `nixos`).
3. Add non-sensitive defaults under group vars only when shared across hosts.
4. Add host-specific values in `inventory/host_vars/<hostname>.yml`.
5. Keep secrets out of committed files.
Example pattern:
```yaml
linux:
hosts:
my-host:
ansible_host: my-host.local
```
## Validation commands
Run from repository root:
```bash
ansible --version
ansible-lint --version
ansible-inventory -i infrastructure/ansible/inventory/hosts.yml --list
ansible-playbook -i infrastructure/ansible/inventory/hosts.yml infrastructure/ansible/playbooks/ping.yml --syntax-check
```
Install/update baseline collections:
```bash
ansible-galaxy collection install -r infrastructure/ansible/collections/requirements.yml -p infrastructure/ansible/collections
```
## Guardrails for future expansion
- Keep changes incremental (one host/group/playbook change at a time).
- Prefer simple playbooks before introducing roles.
- Add network-platform/NixOS-specific logic only when those boundaries are explicitly adopted.
- Keep documentation aligned with source-of-truth boundaries when Ansible authority evolves.
+95
View File
@@ -0,0 +1,95 @@
# Architecture Summary
## Overview
This stack uses **Traefik v3** as internet-facing ingress for application and operations UIs. Service routing is label-driven from Docker Compose files, with shared Docker networks (`traefik`, `monitor`) connecting reverse-proxied and telemetry services.
TLS is terminated at Traefik (ACME HTTP challenge), with hardening via middleware chains, Authelia forward-auth for selected routes, CrowdSec integration, and mTLS options for private-admin paths.
## Network / Request Flow
```mermaid
flowchart LR
C[Internet Client] -->|80/443| T[Traefik Ingress]
T -->|HTTP->HTTPS redirect| T
T -->|ACME HTTP challenge| LE[Let's Encrypt ACME]
subgraph TraefikNet[Docker network: traefik]
A[Authelia]
CS[CrowdSec LAPI]
EP[Error Pages]
NC[Nextcloud]
PB[Passbolt]
GT[Gitea]
GW[Gramps Web]
SX[SearXNG]
GF[Grafana]
PR[Prometheus]
NR[Node-RED]
PT[Portainer]
UK[Uptime Kuma]
IF[InfluxDB]
GO[Gotify]
end
T -->|forwardAuth for selected services| A
T -->|plugin decisions| CS
T -->|4xx/5xx middleware| EP
T --> NC
T --> PB
T --> GT
T --> GW
T --> SX
T --> GF
T --> PR
T --> NR
T --> PT
T --> UK
T --> IF
T --> GO
subgraph MonitorNet[Docker network: monitor]
NE[Node Exporter]
TE[Telegraf]
DE[Docker Update Exporter]
PE[Pi-hole Exporter]
DSP[Docker Socket Proxy]
end
PR --> NE
PR --> TE
PR --> DE
PR --> PE
PR --> UK
PR -->|remote scrape| RH[Remote Hosts]
TE --> DSP
NR --> DSP
PT --> DSP
T --> DSP
```
## Key components
- **Ingress/security plane:** Traefik, Authelia, CrowdSec, Error Pages.
- **User-facing apps:** Nextcloud, Passbolt, Gitea, Gramps Web, SearXNG.
- **Monitoring/ops:** Prometheus, Grafana, InfluxDB, Node-RED, Uptime Kuma, Portainer, Gotify.
- **Support plane:** Docker Socket Proxy for controlled Docker API access.
## Relationship to Terraform inventory
Terraform in `infrastructure/terraform/` captures infrastructure inventory and reconciliation state for Proxmox VMs, physical host metadata, and selected Docker mirrors.
Use architecture docs together with:
- [docs/source-of-truth.md](source-of-truth.md)
- [docs/terraform-workflows.md](terraform-workflows.md)
- [docs/infrastructure-inventory.md](infrastructure-inventory.md)
- [docs/generated/host-topology.md](generated/host-topology.md)
## Notes on runtime vs declared state
Runtime scrape targets and health signals are useful observed-state inputs, but they do not replace declared config authority from Compose/Terraform sources.
+14
View File
@@ -0,0 +1,14 @@
# Automation
This section describes automation systems associated with this environment.
Current state:
- CI documentation generation in GitHub Actions.
- Static parsing and rendering from repository configuration.
- No live service interaction during documentation generation.
Future content can document:
- Docker update automation.
- Node-RED automation flows and operational patterns.
+49
View File
@@ -0,0 +1,49 @@
# Deployment Prerequisites
Before running compose operations, provision local secret material.
## 1) Create non-committed secret env file
```bash
cp secrets/.env.secrets.example secrets/stack-secrets.env
chmod 600 secrets/stack-secrets.env
```
## 2) Create required Docker secret files
All files below are expected locally and are gitignored:
- `secrets/nextcloud_db_root_password.txt`
- `secrets/nextcloud_db_password.txt`
- `secrets/nextcloud_admin_password.txt`
- `secrets/nextcloud_smtp_password.txt`
- `secrets/nextcloud_redis_password.txt`
- `secrets/passbolt_db_password.txt`
- `secrets/influxdb_init_password.txt`
- `secrets/prometheus_kuma_basic_auth_password.txt`
Recommended permissions:
```bash
chmod 600 secrets/*.txt
```
## 3) Validate composed configuration
Use the repository composition entrypoint:
```bash
./services-up.sh --profile all config
```
This confirms compose rendering with shared env/network inputs before any runtime operation.
## 4) Rotate previously committed credentials
If migrating from older states where secrets were committed, rotate upstream values immediately (DB credentials, app passwords, auth keys, and API tokens).
## Related docs
- [`./security-secrets.md`](./security-secrets.md)
- [`./docker-environment.md`](./docker-environment.md)
- [`./source-of-truth.md`](./source-of-truth.md)
View File
+75
View File
@@ -0,0 +1,75 @@
digraph Compose {
rankdir=LR;
node [fontname="Helvetica"];
"svc:authelia" [label="authelia", shape=box, style=filled, fillcolor="#dfefff"];
"svc:crowdsec" [label="crowdsec", shape=box, style=filled, fillcolor="#dfefff"];
"svc:docker-socket-proxy" [label="docker-socket-proxy", shape=box, style=filled, fillcolor="#dfefff"];
"svc:docker-update-exporter" [label="docker-update-exporter", shape=box, style=filled, fillcolor="#dfefff"];
"svc:error-pages" [label="error-pages", shape=box, style=filled, fillcolor="#dfefff"];
"svc:gitea" [label="gitea", shape=box, style=filled, fillcolor="#dfefff"];
"svc:gitea-runner" [label="gitea-runner", shape=box, style=filled, fillcolor="#dfefff"];
"svc:gotify" [label="gotify", shape=box, style=filled, fillcolor="#dfefff"];
"svc:grafana" [label="grafana", shape=box, style=filled, fillcolor="#dfefff"];
"svc:gramps-redis" [label="gramps-redis", shape=box, style=filled, fillcolor="#dfefff"];
"svc:grampsweb" [label="grampsweb", shape=box, style=filled, fillcolor="#dfefff"];
"svc:grampsweb_celery" [label="grampsweb_celery", shape=box, style=filled, fillcolor="#dfefff"];
"svc:influxdb" [label="influxdb", shape=box, style=filled, fillcolor="#dfefff"];
"svc:monitor-kuma" [label="monitor-kuma", shape=box, style=filled, fillcolor="#dfefff"];
"svc:mtls-bridge" [label="mtls-bridge", shape=box, style=filled, fillcolor="#dfefff"];
"svc:nextcloud-db" [label="nextcloud-db", shape=box, style=filled, fillcolor="#dfefff"];
"svc:nextcloud-redis" [label="nextcloud-redis", shape=box, style=filled, fillcolor="#dfefff"];
"svc:nextcloud-webapp" [label="nextcloud-webapp", shape=box, style=filled, fillcolor="#dfefff"];
"svc:node-exporter" [label="node-exporter", shape=box, style=filled, fillcolor="#dfefff"];
"svc:node-red" [label="node-red", shape=box, style=filled, fillcolor="#dfefff"];
"svc:passbolt-db" [label="passbolt-db", shape=box, style=filled, fillcolor="#dfefff"];
"svc:passbolt-webapp" [label="passbolt-webapp", shape=box, style=filled, fillcolor="#dfefff"];
"svc:pihole-exporter" [label="pihole-exporter", shape=box, style=filled, fillcolor="#dfefff"];
"svc:portainer" [label="portainer", shape=box, style=filled, fillcolor="#dfefff"];
"svc:prometheus" [label="prometheus", shape=box, style=filled, fillcolor="#dfefff"];
"svc:searxng-webapp" [label="searxng-webapp", shape=box, style=filled, fillcolor="#dfefff"];
"svc:telegraf" [label="telegraf", shape=box, style=filled, fillcolor="#dfefff"];
"svc:traefik" [label="traefik", shape=box, style=filled, fillcolor="#dfefff"];
"net:gramps" [label="gramps", shape=ellipse, style=filled, fillcolor="#f4f4f4"];
"net:monitor" [label="monitor", shape=ellipse, style=filled, fillcolor="#f4f4f4"];
"net:nextcloud" [label="nextcloud", shape=ellipse, style=filled, fillcolor="#f4f4f4"];
"net:passbolt" [label="passbolt", shape=ellipse, style=filled, fillcolor="#f4f4f4"];
"net:traefik" [label="traefik", shape=ellipse, style=filled, fillcolor="#f4f4f4"];
"svc:authelia" -> "net:traefik";
"svc:crowdsec" -> "net:traefik";
"svc:docker-socket-proxy" -> "net:monitor";
"svc:docker-socket-proxy" -> "net:traefik";
"svc:docker-update-exporter" -> "net:monitor";
"svc:error-pages" -> "net:traefik";
"svc:gitea" -> "net:traefik";
"svc:gitea-runner" -> "net:traefik";
"svc:gotify" -> "net:traefik";
"svc:grafana" -> "net:monitor";
"svc:grafana" -> "net:traefik";
"svc:gramps-redis" -> "net:gramps";
"svc:grampsweb" -> "net:gramps";
"svc:grampsweb" -> "net:traefik";
"svc:grampsweb_celery" -> "net:gramps";
"svc:influxdb" -> "net:monitor";
"svc:influxdb" -> "net:traefik";
"svc:monitor-kuma" -> "net:monitor";
"svc:monitor-kuma" -> "net:traefik";
"svc:mtls-bridge" -> "net:monitor";
"svc:mtls-bridge" -> "net:traefik";
"svc:nextcloud-db" -> "net:nextcloud";
"svc:nextcloud-redis" -> "net:nextcloud";
"svc:nextcloud-webapp" -> "net:nextcloud";
"svc:nextcloud-webapp" -> "net:traefik";
"svc:node-exporter" -> "net:monitor";
"svc:node-red" -> "net:monitor";
"svc:node-red" -> "net:traefik";
"svc:passbolt-db" -> "net:passbolt";
"svc:passbolt-webapp" -> "net:passbolt";
"svc:passbolt-webapp" -> "net:traefik";
"svc:pihole-exporter" -> "net:monitor";
"svc:portainer" -> "net:traefik";
"svc:prometheus" -> "net:monitor";
"svc:prometheus" -> "net:traefik";
"svc:searxng-webapp" -> "net:traefik";
"svc:telegraf" -> "net:monitor";
"svc:traefik" -> "net:traefik";
}
+439
View File
@@ -0,0 +1,439 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: Compose Pages: 1 -->
<svg width="334pt" height="1502pt"
viewBox="0.00 0.00 334.49 1502.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1498)">
<title>Compose</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1498 330.49,-1498 330.49,4 -4,4"/>
<!-- svc:authelia -->
<g id="node1" class="node">
<title>svc:authelia</title>
<polygon fill="#dfefff" stroke="black" points="126,-738 54,-738 54,-702 126,-702 126,-738"/>
<text text-anchor="middle" x="90" y="-716.3" font-family="Helvetica,sans-Serif" font-size="14.00">authelia</text>
</g>
<!-- net:traefik -->
<g id="node33" class="node">
<title>net:traefik</title>
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-774" rx="40.09" ry="18"/>
<text text-anchor="middle" x="271.25" y="-770.3" font-family="Helvetica,sans-Serif" font-size="14.00">traefik</text>
</g>
<!-- svc:authelia&#45;&gt;net:traefik -->
<g id="edge1" class="edge">
<title>svc:authelia&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M126.41,-730.67C155.5,-739.43 196.8,-751.87 227.69,-761.18"/>
<polygon fill="black" stroke="black" points="226.74,-764.55 237.32,-764.08 228.76,-757.85 226.74,-764.55"/>
</g>
<!-- svc:crowdsec -->
<g id="node2" class="node">
<title>svc:crowdsec</title>
<polygon fill="#dfefff" stroke="black" points="130.5,-684 49.5,-684 49.5,-648 130.5,-648 130.5,-684"/>
<text text-anchor="middle" x="90" y="-662.3" font-family="Helvetica,sans-Serif" font-size="14.00">crowdsec</text>
</g>
<!-- svc:crowdsec&#45;&gt;net:traefik -->
<g id="edge2" class="edge">
<title>svc:crowdsec&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M130.61,-674.06C146.59,-678.3 164.81,-684.44 180,-693 206.25,-707.78 231.35,-731.35 248.38,-749.26"/>
<polygon fill="black" stroke="black" points="246.24,-752.1 255.62,-757.03 251.37,-747.33 246.24,-752.1"/>
</g>
<!-- svc:docker&#45;socket&#45;proxy -->
<g id="node3" class="node">
<title>svc:docker&#45;socket&#45;proxy</title>
<polygon fill="#dfefff" stroke="black" points="167.5,-954 12.5,-954 12.5,-918 167.5,-918 167.5,-954"/>
<text text-anchor="middle" x="90" y="-932.3" font-family="Helvetica,sans-Serif" font-size="14.00">docker&#45;socket&#45;proxy</text>
</g>
<!-- net:monitor -->
<g id="node30" class="node">
<title>net:monitor</title>
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-1206" rx="46.29" ry="18"/>
<text text-anchor="middle" x="271.25" y="-1202.3" font-family="Helvetica,sans-Serif" font-size="14.00">monitor</text>
</g>
<!-- svc:docker&#45;socket&#45;proxy&#45;&gt;net:monitor -->
<g id="edge3" class="edge">
<title>svc:docker&#45;socket&#45;proxy&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M167.53,-953.71C172.02,-956.38 176.24,-959.46 180,-963 242.3,-1021.6 261.83,-1127.12 267.77,-1177.6"/>
<polygon fill="black" stroke="black" points="264.31,-1178.21 268.86,-1187.78 271.27,-1177.46 264.31,-1178.21"/>
</g>
<!-- svc:docker&#45;socket&#45;proxy&#45;&gt;net:traefik -->
<g id="edge4" class="edge">
<title>svc:docker&#45;socket&#45;proxy&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M165.31,-917.9C170.5,-915.32 175.46,-912.37 180,-909 218.01,-880.82 244.97,-831.56 259.03,-800.98"/>
<polygon fill="black" stroke="black" points="262.27,-802.31 263.14,-791.76 255.87,-799.46 262.27,-802.31"/>
</g>
<!-- svc:docker&#45;update&#45;exporter -->
<g id="node4" class="node">
<title>svc:docker&#45;update&#45;exporter</title>
<polygon fill="#dfefff" stroke="black" points="180,-1494 0,-1494 0,-1458 180,-1458 180,-1494"/>
<text text-anchor="middle" x="90" y="-1472.3" font-family="Helvetica,sans-Serif" font-size="14.00">docker&#45;update&#45;exporter</text>
</g>
<!-- svc:docker&#45;update&#45;exporter&#45;&gt;net:monitor -->
<g id="edge5" class="edge">
<title>svc:docker&#45;update&#45;exporter&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M168.37,-1457.79C172.55,-1455.23 176.47,-1452.32 180,-1449 242.3,-1390.4 261.83,-1284.88 267.77,-1234.4"/>
<polygon fill="black" stroke="black" points="271.27,-1234.54 268.86,-1224.22 264.31,-1233.79 271.27,-1234.54"/>
</g>
<!-- svc:error&#45;pages -->
<g id="node5" class="node">
<title>svc:error&#45;pages</title>
<polygon fill="#dfefff" stroke="black" points="137.5,-630 42.5,-630 42.5,-594 137.5,-594 137.5,-630"/>
<text text-anchor="middle" x="90" y="-608.3" font-family="Helvetica,sans-Serif" font-size="14.00">error&#45;pages</text>
</g>
<!-- svc:error&#45;pages&#45;&gt;net:traefik -->
<g id="edge6" class="edge">
<title>svc:error&#45;pages&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M137.71,-619.76C152.25,-623.79 167.68,-629.86 180,-639 218.01,-667.18 244.97,-716.44 259.03,-747.02"/>
<polygon fill="black" stroke="black" points="255.87,-748.54 263.14,-756.24 262.27,-745.69 255.87,-748.54"/>
</g>
<!-- svc:gitea -->
<g id="node6" class="node">
<title>svc:gitea</title>
<polygon fill="#dfefff" stroke="black" points="117,-576 63,-576 63,-540 117,-540 117,-576"/>
<text text-anchor="middle" x="90" y="-554.3" font-family="Helvetica,sans-Serif" font-size="14.00">gitea</text>
</g>
<!-- svc:gitea&#45;&gt;net:traefik -->
<g id="edge7" class="edge">
<title>svc:gitea&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M117.07,-560.28C136.41,-563.2 162.37,-569.85 180,-585 229.98,-627.95 254.42,-704.8 264.44,-746.04"/>
<polygon fill="black" stroke="black" points="261.07,-747 266.73,-755.95 267.88,-745.42 261.07,-747"/>
</g>
<!-- svc:gitea&#45;runner -->
<g id="node7" class="node">
<title>svc:gitea&#45;runner</title>
<polygon fill="#dfefff" stroke="black" points="142,-522 38,-522 38,-486 142,-486 142,-522"/>
<text text-anchor="middle" x="90" y="-500.3" font-family="Helvetica,sans-Serif" font-size="14.00">gitea&#45;runner</text>
</g>
<!-- svc:gitea&#45;runner&#45;&gt;net:traefik -->
<g id="edge8" class="edge">
<title>svc:gitea&#45;runner&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M142.31,-511.04C155.9,-515.03 169.65,-521.26 180,-531 242.3,-589.6 261.83,-695.12 267.77,-745.6"/>
<polygon fill="black" stroke="black" points="264.31,-746.21 268.86,-755.78 271.27,-745.46 264.31,-746.21"/>
</g>
<!-- svc:gotify -->
<g id="node8" class="node">
<title>svc:gotify</title>
<polygon fill="#dfefff" stroke="black" points="118,-468 62,-468 62,-432 118,-432 118,-468"/>
<text text-anchor="middle" x="90" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">gotify</text>
</g>
<!-- svc:gotify&#45;&gt;net:traefik -->
<g id="edge9" class="edge">
<title>svc:gotify&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M118.17,-451.6C137.81,-454.16 163.67,-460.68 180,-477 254.99,-551.96 268.02,-687.13 270.03,-745.69"/>
<polygon fill="black" stroke="black" points="266.53,-745.79 270.29,-755.69 273.53,-745.61 266.53,-745.79"/>
</g>
<!-- svc:grafana -->
<g id="node9" class="node">
<title>svc:grafana</title>
<polygon fill="#dfefff" stroke="black" points="125.5,-1278 54.5,-1278 54.5,-1242 125.5,-1242 125.5,-1278"/>
<text text-anchor="middle" x="90" y="-1256.3" font-family="Helvetica,sans-Serif" font-size="14.00">grafana</text>
</g>
<!-- svc:grafana&#45;&gt;net:monitor -->
<g id="edge10" class="edge">
<title>svc:grafana&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M125.56,-1249.59C153.74,-1241.1 193.78,-1229.04 224.6,-1219.75"/>
<polygon fill="black" stroke="black" points="225.69,-1223.08 234.26,-1216.84 223.67,-1216.38 225.69,-1223.08"/>
</g>
<!-- svc:grafana&#45;&gt;net:traefik -->
<g id="edge11" class="edge">
<title>svc:grafana&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M125.54,-1257.93C144.23,-1254.97 166.25,-1248.21 180,-1233 238.17,-1168.67 262.1,-892.17 268.43,-802.32"/>
<polygon fill="black" stroke="black" points="271.93,-802.3 269.13,-792.08 264.95,-801.82 271.93,-802.3"/>
</g>
<!-- svc:gramps&#45;redis -->
<g id="node10" class="node">
<title>svc:gramps&#45;redis</title>
<polygon fill="#dfefff" stroke="black" points="144.5,-360 35.5,-360 35.5,-324 144.5,-324 144.5,-360"/>
<text text-anchor="middle" x="90" y="-338.3" font-family="Helvetica,sans-Serif" font-size="14.00">gramps&#45;redis</text>
</g>
<!-- net:gramps -->
<g id="node29" class="node">
<title>net:gramps</title>
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-342" rx="45.49" ry="18"/>
<text text-anchor="middle" x="271.25" y="-338.3" font-family="Helvetica,sans-Serif" font-size="14.00">gramps</text>
</g>
<!-- svc:gramps&#45;redis&#45;&gt;net:gramps -->
<g id="edge12" class="edge">
<title>svc:gramps&#45;redis&#45;&gt;net:gramps</title>
<path fill="none" stroke="black" d="M144.78,-342C167.14,-342 193.05,-342 215.51,-342"/>
<polygon fill="black" stroke="black" points="215.56,-345.5 225.56,-342 215.56,-338.5 215.56,-345.5"/>
</g>
<!-- svc:grampsweb -->
<g id="node11" class="node">
<title>svc:grampsweb</title>
<polygon fill="#dfefff" stroke="black" points="139,-414 41,-414 41,-378 139,-378 139,-414"/>
<text text-anchor="middle" x="90" y="-392.3" font-family="Helvetica,sans-Serif" font-size="14.00">grampsweb</text>
</g>
<!-- svc:grampsweb&#45;&gt;net:gramps -->
<g id="edge13" class="edge">
<title>svc:grampsweb&#45;&gt;net:gramps</title>
<path fill="none" stroke="black" d="M139.03,-381.53C165.7,-373.5 198.68,-363.56 224.9,-355.66"/>
<polygon fill="black" stroke="black" points="226.03,-358.98 234.59,-352.74 224.01,-352.27 226.03,-358.98"/>
</g>
<!-- svc:grampsweb&#45;&gt;net:traefik -->
<g id="edge14" class="edge">
<title>svc:grampsweb&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M139.27,-401.34C154.08,-405.21 169.28,-411.81 180,-423 225.12,-470.09 256.34,-671.08 266.59,-745.84"/>
<polygon fill="black" stroke="black" points="263.14,-746.48 267.95,-755.92 270.08,-745.55 263.14,-746.48"/>
</g>
<!-- svc:grampsweb_celery -->
<g id="node12" class="node">
<title>svc:grampsweb_celery</title>
<polygon fill="#dfefff" stroke="black" points="163.5,-306 16.5,-306 16.5,-270 163.5,-270 163.5,-306"/>
<text text-anchor="middle" x="90" y="-284.3" font-family="Helvetica,sans-Serif" font-size="14.00">grampsweb_celery</text>
</g>
<!-- svc:grampsweb_celery&#45;&gt;net:gramps -->
<g id="edge15" class="edge">
<title>svc:grampsweb_celery&#45;&gt;net:gramps</title>
<path fill="none" stroke="black" d="M151.18,-306.13C175.28,-313.39 202.57,-321.61 224.94,-328.35"/>
<polygon fill="black" stroke="black" points="223.94,-331.71 234.52,-331.24 225.96,-325 223.94,-331.71"/>
</g>
<!-- svc:influxdb -->
<g id="node13" class="node">
<title>svc:influxdb</title>
<polygon fill="#dfefff" stroke="black" points="127,-1224 53,-1224 53,-1188 127,-1188 127,-1224"/>
<text text-anchor="middle" x="90" y="-1202.3" font-family="Helvetica,sans-Serif" font-size="14.00">influxdb</text>
</g>
<!-- svc:influxdb&#45;&gt;net:monitor -->
<g id="edge16" class="edge">
<title>svc:influxdb&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M127.27,-1206C152.33,-1206 186.13,-1206 214.57,-1206"/>
<polygon fill="black" stroke="black" points="214.79,-1209.5 224.79,-1206 214.79,-1202.5 214.79,-1209.5"/>
</g>
<!-- svc:influxdb&#45;&gt;net:traefik -->
<g id="edge17" class="edge">
<title>svc:influxdb&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M127.17,-1203.5C145.42,-1200.36 166.5,-1193.56 180,-1179 231.57,-1123.4 259.36,-885.3 267.6,-802.51"/>
<polygon fill="black" stroke="black" points="271.1,-802.62 268.59,-792.33 264.14,-801.94 271.1,-802.62"/>
</g>
<!-- svc:monitor&#45;kuma -->
<g id="node14" class="node">
<title>svc:monitor&#45;kuma</title>
<polygon fill="#dfefff" stroke="black" points="146.5,-1170 33.5,-1170 33.5,-1134 146.5,-1134 146.5,-1170"/>
<text text-anchor="middle" x="90" y="-1148.3" font-family="Helvetica,sans-Serif" font-size="14.00">monitor&#45;kuma</text>
</g>
<!-- svc:monitor&#45;kuma&#45;&gt;net:monitor -->
<g id="edge18" class="edge">
<title>svc:monitor&#45;kuma&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M146.73,-1168.79C171.73,-1176.32 200.85,-1185.1 224.54,-1192.23"/>
<polygon fill="black" stroke="black" points="223.75,-1195.65 234.33,-1195.18 225.77,-1188.94 223.75,-1195.65"/>
</g>
<!-- svc:monitor&#45;kuma&#45;&gt;net:traefik -->
<g id="edge19" class="edge">
<title>svc:monitor&#45;kuma&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M146.69,-1144.49C159.02,-1140.46 171.06,-1134.33 180,-1125 225.12,-1077.91 256.34,-876.92 266.59,-802.16"/>
<polygon fill="black" stroke="black" points="270.08,-802.45 267.95,-792.08 263.14,-801.52 270.08,-802.45"/>
</g>
<!-- svc:mtls&#45;bridge -->
<g id="node15" class="node">
<title>svc:mtls&#45;bridge</title>
<polygon fill="#dfefff" stroke="black" points="138.5,-1116 41.5,-1116 41.5,-1080 138.5,-1080 138.5,-1116"/>
<text text-anchor="middle" x="90" y="-1094.3" font-family="Helvetica,sans-Serif" font-size="14.00">mtls&#45;bridge</text>
</g>
<!-- svc:mtls&#45;bridge&#45;&gt;net:monitor -->
<g id="edge20" class="edge">
<title>svc:mtls&#45;bridge&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M138.76,-1108.34C152.58,-1112.41 167.34,-1117.87 180,-1125 206.25,-1139.78 231.35,-1163.35 248.38,-1181.26"/>
<polygon fill="black" stroke="black" points="246.24,-1184.1 255.62,-1189.03 251.37,-1179.33 246.24,-1184.1"/>
</g>
<!-- svc:mtls&#45;bridge&#45;&gt;net:traefik -->
<g id="edge21" class="edge">
<title>svc:mtls&#45;bridge&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M138.53,-1092.51C153.46,-1088.63 168.92,-1082.07 180,-1071 254.99,-996.04 268.02,-860.87 270.03,-802.31"/>
<polygon fill="black" stroke="black" points="273.53,-802.39 270.29,-792.31 266.53,-802.21 273.53,-802.39"/>
</g>
<!-- svc:nextcloud&#45;db -->
<g id="node16" class="node">
<title>svc:nextcloud&#45;db</title>
<polygon fill="#dfefff" stroke="black" points="144,-90 36,-90 36,-54 144,-54 144,-90"/>
<text text-anchor="middle" x="90" y="-68.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud&#45;db</text>
</g>
<!-- net:nextcloud -->
<g id="node31" class="node">
<title>net:nextcloud</title>
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-180" rx="55.49" ry="18"/>
<text text-anchor="middle" x="271.25" y="-176.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud</text>
</g>
<!-- svc:nextcloud&#45;db&#45;&gt;net:nextcloud -->
<g id="edge22" class="edge">
<title>svc:nextcloud&#45;db&#45;&gt;net:nextcloud</title>
<path fill="none" stroke="black" d="M144.33,-84.04C156.48,-87.9 169.03,-92.82 180,-99 206.09,-113.7 231.04,-137.06 248.07,-154.93"/>
<polygon fill="black" stroke="black" points="245.93,-157.77 255.31,-162.7 251.06,-153 245.93,-157.77"/>
</g>
<!-- svc:nextcloud&#45;redis -->
<g id="node17" class="node">
<title>svc:nextcloud&#45;redis</title>
<polygon fill="#dfefff" stroke="black" points="152,-198 28,-198 28,-162 152,-162 152,-198"/>
<text text-anchor="middle" x="90" y="-176.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud&#45;redis</text>
</g>
<!-- svc:nextcloud&#45;redis&#45;&gt;net:nextcloud -->
<g id="edge23" class="edge">
<title>svc:nextcloud&#45;redis&#45;&gt;net:nextcloud</title>
<path fill="none" stroke="black" d="M152.18,-180C169.48,-180 188.35,-180 205.83,-180"/>
<polygon fill="black" stroke="black" points="205.94,-183.5 215.94,-180 205.94,-176.5 205.94,-183.5"/>
</g>
<!-- svc:nextcloud&#45;webapp -->
<g id="node18" class="node">
<title>svc:nextcloud&#45;webapp</title>
<polygon fill="#dfefff" stroke="black" points="162.5,-252 17.5,-252 17.5,-216 162.5,-216 162.5,-252"/>
<text text-anchor="middle" x="90" y="-230.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud&#45;webapp</text>
</g>
<!-- svc:nextcloud&#45;webapp&#45;&gt;net:nextcloud -->
<g id="edge24" class="edge">
<title>svc:nextcloud&#45;webapp&#45;&gt;net:nextcloud</title>
<path fill="none" stroke="black" d="M151.18,-215.87C173.57,-209.12 198.72,-201.55 220.11,-195.1"/>
<polygon fill="black" stroke="black" points="221.4,-198.37 229.97,-192.13 219.38,-191.67 221.4,-198.37"/>
</g>
<!-- svc:nextcloud&#45;webapp&#45;&gt;net:traefik -->
<g id="edge25" class="edge">
<title>svc:nextcloud&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M162.89,-247.71C169.31,-251.2 175.18,-255.56 180,-261 212.66,-297.85 255.07,-643.36 267,-745.62"/>
<polygon fill="black" stroke="black" points="263.55,-746.27 268.18,-755.8 270.51,-745.47 263.55,-746.27"/>
</g>
<!-- svc:node&#45;exporter -->
<g id="node19" class="node">
<title>svc:node&#45;exporter</title>
<polygon fill="#dfefff" stroke="black" points="148,-1440 32,-1440 32,-1404 148,-1404 148,-1440"/>
<text text-anchor="middle" x="90" y="-1418.3" font-family="Helvetica,sans-Serif" font-size="14.00">node&#45;exporter</text>
</g>
<!-- svc:node&#45;exporter&#45;&gt;net:monitor -->
<g id="edge26" class="edge">
<title>svc:node&#45;exporter&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M148.25,-1412.27C159.68,-1408.33 170.94,-1402.79 180,-1395 229.98,-1352.05 254.42,-1275.2 264.44,-1233.96"/>
<polygon fill="black" stroke="black" points="267.88,-1234.58 266.73,-1224.05 261.07,-1233 267.88,-1234.58"/>
</g>
<!-- svc:node&#45;red -->
<g id="node20" class="node">
<title>svc:node&#45;red</title>
<polygon fill="#dfefff" stroke="black" points="129.5,-1062 50.5,-1062 50.5,-1026 129.5,-1026 129.5,-1062"/>
<text text-anchor="middle" x="90" y="-1040.3" font-family="Helvetica,sans-Serif" font-size="14.00">node&#45;red</text>
</g>
<!-- svc:node&#45;red&#45;&gt;net:monitor -->
<g id="edge27" class="edge">
<title>svc:node&#45;red&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M129.57,-1049.69C146.29,-1053.6 165.34,-1060.13 180,-1071 218.01,-1099.18 244.97,-1148.44 259.03,-1179.02"/>
<polygon fill="black" stroke="black" points="255.87,-1180.54 263.14,-1188.24 262.27,-1177.69 255.87,-1180.54"/>
</g>
<!-- svc:node&#45;red&#45;&gt;net:traefik -->
<g id="edge28" class="edge">
<title>svc:node&#45;red&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M129.95,-1040.03C147.14,-1036.46 166.48,-1029.72 180,-1017 242.3,-958.4 261.83,-852.88 267.77,-802.4"/>
<polygon fill="black" stroke="black" points="271.27,-802.54 268.86,-792.22 264.31,-801.79 271.27,-802.54"/>
</g>
<!-- svc:passbolt&#45;db -->
<g id="node21" class="node">
<title>svc:passbolt&#45;db</title>
<polygon fill="#dfefff" stroke="black" points="139,-36 41,-36 41,0 139,0 139,-36"/>
<text text-anchor="middle" x="90" y="-14.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt&#45;db</text>
</g>
<!-- net:passbolt -->
<g id="node32" class="node">
<title>net:passbolt</title>
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-72" rx="48.99" ry="18"/>
<text text-anchor="middle" x="271.25" y="-68.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt</text>
</g>
<!-- svc:passbolt&#45;db&#45;&gt;net:passbolt -->
<g id="edge29" class="edge">
<title>svc:passbolt&#45;db&#45;&gt;net:passbolt</title>
<path fill="none" stroke="black" d="M139.03,-32.47C165.15,-40.34 197.32,-50.03 223.27,-57.85"/>
<polygon fill="black" stroke="black" points="222.3,-61.21 232.88,-60.74 224.32,-54.51 222.3,-61.21"/>
</g>
<!-- svc:passbolt&#45;webapp -->
<g id="node22" class="node">
<title>svc:passbolt&#45;webapp</title>
<polygon fill="#dfefff" stroke="black" points="157.5,-144 22.5,-144 22.5,-108 157.5,-108 157.5,-144"/>
<text text-anchor="middle" x="90" y="-122.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt&#45;webapp</text>
</g>
<!-- svc:passbolt&#45;webapp&#45;&gt;net:passbolt -->
<g id="edge30" class="edge">
<title>svc:passbolt&#45;webapp&#45;&gt;net:passbolt</title>
<path fill="none" stroke="black" d="M151.18,-107.87C174.64,-100.8 201.13,-92.82 223.16,-86.19"/>
<polygon fill="black" stroke="black" points="224.37,-89.48 232.94,-83.24 222.35,-82.77 224.37,-89.48"/>
</g>
<!-- svc:passbolt&#45;webapp&#45;&gt;net:traefik -->
<g id="edge31" class="edge">
<title>svc:passbolt&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M157.85,-136.93C166.26,-140.81 174,-146.02 180,-153 189.88,-164.49 250.79,-625.33 266.53,-745.56"/>
<polygon fill="black" stroke="black" points="263.1,-746.33 267.87,-755.8 270.04,-745.43 263.1,-746.33"/>
</g>
<!-- svc:pihole&#45;exporter -->
<g id="node23" class="node">
<title>svc:pihole&#45;exporter</title>
<polygon fill="#dfefff" stroke="black" points="151.5,-1386 28.5,-1386 28.5,-1350 151.5,-1350 151.5,-1386"/>
<text text-anchor="middle" x="90" y="-1364.3" font-family="Helvetica,sans-Serif" font-size="14.00">pihole&#45;exporter</text>
</g>
<!-- svc:pihole&#45;exporter&#45;&gt;net:monitor -->
<g id="edge32" class="edge">
<title>svc:pihole&#45;exporter&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M151.62,-1355.78C161.68,-1352.07 171.57,-1347.25 180,-1341 218.01,-1312.82 244.97,-1263.56 259.03,-1232.98"/>
<polygon fill="black" stroke="black" points="262.27,-1234.31 263.14,-1223.76 255.87,-1231.46 262.27,-1234.31"/>
</g>
<!-- svc:portainer -->
<g id="node24" class="node">
<title>svc:portainer</title>
<polygon fill="#dfefff" stroke="black" points="130,-900 50,-900 50,-864 130,-864 130,-900"/>
<text text-anchor="middle" x="90" y="-878.3" font-family="Helvetica,sans-Serif" font-size="14.00">portainer</text>
</g>
<!-- svc:portainer&#45;&gt;net:traefik -->
<g id="edge33" class="edge">
<title>svc:portainer&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M130.17,-874.06C146.25,-869.82 164.67,-863.64 180,-855 206.25,-840.22 231.35,-816.65 248.38,-798.74"/>
<polygon fill="black" stroke="black" points="251.37,-800.67 255.62,-790.97 246.24,-795.9 251.37,-800.67"/>
</g>
<!-- svc:prometheus -->
<g id="node25" class="node">
<title>svc:prometheus</title>
<polygon fill="#dfefff" stroke="black" points="140,-1008 40,-1008 40,-972 140,-972 140,-1008"/>
<text text-anchor="middle" x="90" y="-986.3" font-family="Helvetica,sans-Serif" font-size="14.00">prometheus</text>
</g>
<!-- svc:prometheus&#45;&gt;net:monitor -->
<g id="edge34" class="edge">
<title>svc:prometheus&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M140.37,-997.26C154.39,-1001.24 168.86,-1007.42 180,-1017 229.98,-1059.95 254.42,-1136.8 264.44,-1178.04"/>
<polygon fill="black" stroke="black" points="261.07,-1179 266.73,-1187.95 267.88,-1177.42 261.07,-1179"/>
</g>
<!-- svc:prometheus&#45;&gt;net:traefik -->
<g id="edge35" class="edge">
<title>svc:prometheus&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M140.37,-982.74C154.39,-978.76 168.86,-972.58 180,-963 229.98,-920.05 254.42,-843.2 264.44,-801.96"/>
<polygon fill="black" stroke="black" points="267.88,-802.58 266.73,-792.05 261.07,-801 267.88,-802.58"/>
</g>
<!-- svc:searxng&#45;webapp -->
<g id="node26" class="node">
<title>svc:searxng&#45;webapp</title>
<polygon fill="#dfefff" stroke="black" points="156,-846 24,-846 24,-810 156,-810 156,-846"/>
<text text-anchor="middle" x="90" y="-824.3" font-family="Helvetica,sans-Serif" font-size="14.00">searxng&#45;webapp</text>
</g>
<!-- svc:searxng&#45;webapp&#45;&gt;net:traefik -->
<g id="edge36" class="edge">
<title>svc:searxng&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M151.18,-809.87C176.26,-802.32 204.79,-793.72 227.63,-786.84"/>
<polygon fill="black" stroke="black" points="228.81,-790.14 237.37,-783.9 226.79,-783.44 228.81,-790.14"/>
</g>
<!-- svc:telegraf -->
<g id="node27" class="node">
<title>svc:telegraf</title>
<polygon fill="#dfefff" stroke="black" points="125.5,-1332 54.5,-1332 54.5,-1296 125.5,-1296 125.5,-1332"/>
<text text-anchor="middle" x="90" y="-1310.3" font-family="Helvetica,sans-Serif" font-size="14.00">telegraf</text>
</g>
<!-- svc:telegraf&#45;&gt;net:monitor -->
<g id="edge37" class="edge">
<title>svc:telegraf&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M125.8,-1307.18C142.85,-1302.93 163.26,-1296.43 180,-1287 206.25,-1272.22 231.35,-1248.65 248.38,-1230.74"/>
<polygon fill="black" stroke="black" points="251.37,-1232.67 255.62,-1222.97 246.24,-1227.9 251.37,-1232.67"/>
</g>
<!-- svc:traefik -->
<g id="node28" class="node">
<title>svc:traefik</title>
<polygon fill="#dfefff" stroke="black" points="121,-792 59,-792 59,-756 121,-756 121,-792"/>
<text text-anchor="middle" x="90" y="-770.3" font-family="Helvetica,sans-Serif" font-size="14.00">traefik</text>
</g>
<!-- svc:traefik&#45;&gt;net:traefik -->
<g id="edge38" class="edge">
<title>svc:traefik&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M121,-774C148.16,-774 188.69,-774 220.66,-774"/>
<polygon fill="black" stroke="black" points="220.71,-777.5 230.71,-774 220.71,-770.5 220.71,-777.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

+118
View File
@@ -0,0 +1,118 @@
digraph DockerTraefikDynu {
graph [rankdir=LR, compound=true, splines=polyline, nodesep=0.9, ranksep=1.6, fontname="Helvetica", concentrate=true, newrank=true];
node [fontname="Helvetica", fontsize=11, style="rounded,filled"];
edge [fontname="Helvetica", fontsize=9, color="#334155"];
"dynu" [label="Dynu / Public DNS", shape=box, fillcolor="#fde68a"];
"svc:traefik" [label="Traefik", shape=box, fillcolor="#bfdbfe"];
"dynu" -> "svc:traefik" [penwidth=1.6];
"svc:authelia" [label="authelia
[TLS]", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:authelia" [penwidth=1.4];
"dns:auth.<domain>" [label="auth.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:auth.<domain>" -> "dynu";
"svc:gitea" [label="gitea
:3000
[TLS]", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:gitea" [penwidth=1.4];
"dns:gitea.<domain>" [label="gitea.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:gitea.<domain>" -> "dynu";
"svc:gotify" [label="gotify
:80", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:gotify" [penwidth=1.4];
"dns:gotify.<domain>" [label="gotify.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:gotify.<domain>" -> "dynu";
"svc:grafana" [label="grafana
:3000", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:grafana" [penwidth=1.4];
"dns:grafana.<domain>" [label="grafana.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:grafana.<domain>" -> "dynu";
"svc:grampsweb" [label="grampsweb", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:grampsweb" [penwidth=1.4];
"dns:familytree.<domain>" [label="familytree.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:familytree.<domain>" -> "dynu";
"svc:influxdb" [label="influxdb
:8086
[authelia]", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:influxdb" [penwidth=1.4];
"dns:influxdb.<domain>" [label="influxdb.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:influxdb.<domain>" -> "dynu";
"svc:monitor-kuma" [label="monitor-kuma
[TLS]", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:monitor-kuma" [penwidth=1.4];
"dns:monitor-kuma.<domain>" [label="monitor-kuma.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:monitor-kuma.<domain>" -> "dynu";
"svc:mtls-bridge" [label="mtls-bridge
:8080
[mTLS]", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:mtls-bridge" [penwidth=1.4];
"dns:mtls-bridge.<domain>" [label="mtls-bridge.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:mtls-bridge.<domain>" -> "dynu";
"svc:nextcloud-webapp" [label="nextcloud-webapp", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:nextcloud-webapp" [penwidth=1.4];
"dns:nextcloud.<domain>" [label="nextcloud.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:nextcloud.<domain>" -> "dynu";
"svc:node-red" [label="node-red
:1880
[authelia]", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:node-red" [penwidth=1.4];
"dns:node-red.<domain>" [label="node-red.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:node-red.<domain>" -> "dynu";
"svc:passbolt-webapp" [label="passbolt-webapp", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:passbolt-webapp" [penwidth=1.4];
"dns:passbolt.<domain>" [label="passbolt.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:passbolt.<domain>" -> "dynu";
"svc:portainer" [label="portainer
:9000
[TLS]", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:portainer" [penwidth=1.4];
"dns:portainer.<domain>" [label="portainer.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:portainer.<domain>" -> "dynu";
"svc:prometheus" [label="prometheus
:9090
[authelia]", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:prometheus" [penwidth=1.4];
"dns:prometheus.<domain>" [label="prometheus.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:prometheus.<domain>" -> "dynu";
"svc:searxng-webapp" [label="searxng-webapp", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:searxng-webapp" [penwidth=1.4];
"dns:searxng.<domain>" [label="searxng.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:searxng.<domain>" -> "dynu";
"svc:traefik" [label="traefik
[authelia]", shape=box, fillcolor="#dcfce7"];
"svc:traefik" -> "svc:traefik" [penwidth=1.4];
"dns:traefik.<domain>" [label="traefik.<domain>", shape=note, fillcolor="#fef3c7"];
"dns:traefik.<domain>" -> "dynu";
{ rank=same; "dns:auth.<domain>"; "dns:familytree.<domain>"; "dns:gitea.<domain>"; "dns:gotify.<domain>"; "dns:grafana.<domain>"; "dns:influxdb.<domain>"; "dns:monitor-kuma.<domain>"; "dns:mtls-bridge.<domain>"; "dns:nextcloud.<domain>"; "dns:node-red.<domain>"; "dns:passbolt.<domain>"; "dns:portainer.<domain>"; "dns:prometheus.<domain>"; "dns:searxng.<domain>"; "dns:traefik.<domain>"; }
subgraph "cluster_networks" {
label="Docker backend networks"; style="rounded,dashed"; color="#d1d5db";
"net:gramps" [label="gramps", shape=ellipse, fillcolor="#f8fafc"];
"net:monitor" [label="monitor", shape=ellipse, fillcolor="#f8fafc"];
"net:nextcloud" [label="nextcloud", shape=ellipse, fillcolor="#f8fafc"];
"net:passbolt" [label="passbolt", shape=ellipse, fillcolor="#f8fafc"];
"net:traefik" [label="traefik", shape=ellipse, fillcolor="#f8fafc"];
}
"svc:authelia" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:gitea" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:gotify" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:grafana" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:grafana" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:grampsweb" -> "net:gramps" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:grampsweb" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:influxdb" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:influxdb" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:monitor-kuma" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:monitor-kuma" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:mtls-bridge" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:mtls-bridge" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:nextcloud-webapp" -> "net:nextcloud" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:nextcloud-webapp" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:node-red" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:node-red" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:passbolt-webapp" -> "net:passbolt" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:passbolt-webapp" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:portainer" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:prometheus" -> "net:monitor" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:prometheus" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:searxng-webapp" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
"svc:traefik" -> "net:traefik" [style=dashed, color="#94a3b8", arrowsize=0.7];
}
+611
View File
@@ -0,0 +1,611 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: DockerTraefikDynu Pages: 1 -->
<svg width="1113pt" height="1536pt"
viewBox="0.00 0.00 1113.39 1536.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1532)">
<title>DockerTraefikDynu</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1532 1109.39,-1532 1109.39,4 -4,4"/>
<g id="clust2" class="cluster">
<title>cluster_networks</title>
<path fill="none" stroke="#d1d5db" stroke-dasharray="5,2" d="M922,-650C922,-650 1093.39,-650 1093.39,-650 1099.39,-650 1105.39,-656 1105.39,-662 1105.39,-662 1105.39,-1117 1105.39,-1117 1105.39,-1123 1099.39,-1129 1093.39,-1129 1093.39,-1129 922,-1129 922,-1129 916,-1129 910,-1123 910,-1117 910,-1117 910,-662 910,-662 910,-656 916,-650 922,-650"/>
<text text-anchor="middle" x="1007.69" y="-1113.8" font-family="Helvetica,sans-Serif" font-size="14.00">Docker backend networks</text>
</g>
<!-- dynu -->
<g id="node1" class="node">
<title>dynu</title>
<path fill="#fde68a" stroke="black" d="M374,-789C374,-789 282,-789 282,-789 276,-789 270,-783 270,-777 270,-777 270,-765 270,-765 270,-759 276,-753 282,-753 282,-753 374,-753 374,-753 380,-753 386,-759 386,-765 386,-765 386,-777 386,-777 386,-783 380,-789 374,-789"/>
<text text-anchor="middle" x="328" y="-768.2" font-family="Helvetica,sans-Serif" font-size="11.00">Dynu / Public DNS</text>
</g>
<!-- svc:traefik -->
<g id="node2" class="node">
<title>svc:traefik</title>
<path fill="#dcfce7" stroke="black" d="M559,-789C559,-789 513,-789 513,-789 507,-789 501,-783 501,-777 501,-777 501,-765 501,-765 501,-759 507,-753 513,-753 513,-753 559,-753 559,-753 565,-753 571,-759 571,-765 571,-765 571,-777 571,-777 571,-783 565,-789 559,-789"/>
<text text-anchor="middle" x="536" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik</text>
<text text-anchor="middle" x="536" y="-762.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
</g>
<!-- dynu&#45;&gt;svc:traefik -->
<g id="edge1" class="edge">
<title>dynu&#45;&gt;svc:traefik</title>
<path fill="none" stroke="#334155" stroke-width="1.6" d="M386.11,-771C419.13,-771 460.06,-771 490.64,-771"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.6" points="491,-774.5 501,-771 491,-767.5 491,-774.5"/>
</g>
<!-- svc:traefik&#45;&gt;svc:traefik -->
<g id="edge30" class="edge">
<title>svc:traefik&#45;&gt;svc:traefik</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M511.86,-789.35C485.91,-817.82 493.95,-854 536,-854 574.1,-854 584.29,-824.29 566.55,-797.52"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="569.07,-795.06 560.14,-789.35 563.55,-799.38 569.07,-795.06"/>
</g>
<!-- svc:authelia -->
<g id="node3" class="node">
<title>svc:authelia</title>
<path fill="#dcfce7" stroke="black" d="M763,-789C763,-789 726,-789 726,-789 720,-789 714,-783 714,-777 714,-777 714,-765 714,-765 714,-759 720,-753 726,-753 726,-753 763,-753 763,-753 769,-753 775,-759 775,-765 775,-765 775,-777 775,-777 775,-783 769,-789 763,-789"/>
<text text-anchor="middle" x="744.5" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">authelia</text>
<text text-anchor="middle" x="744.5" y="-762.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:authelia -->
<g id="edge2" class="edge">
<title>svc:traefik&#45;&gt;svc:authelia</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-771C607.43,-771 664.91,-771 703.39,-771"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="703.71,-774.5 713.71,-771 703.71,-767.5 703.71,-774.5"/>
</g>
<!-- svc:gitea -->
<g id="node5" class="node">
<title>svc:gitea</title>
<path fill="#dcfce7" stroke="black" d="M759.5,-688C759.5,-688 729.5,-688 729.5,-688 723.5,-688 717.5,-682 717.5,-676 717.5,-676 717.5,-656 717.5,-656 717.5,-650 723.5,-644 729.5,-644 729.5,-644 759.5,-644 759.5,-644 765.5,-644 771.5,-650 771.5,-656 771.5,-656 771.5,-676 771.5,-676 771.5,-682 765.5,-688 759.5,-688"/>
<text text-anchor="middle" x="744.5" y="-675.2" font-family="Helvetica,sans-Serif" font-size="11.00">gitea</text>
<text text-anchor="middle" x="744.5" y="-663.2" font-family="Helvetica,sans-Serif" font-size="11.00">:3000</text>
<text text-anchor="middle" x="744.5" y="-651.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:gitea -->
<g id="edge4" class="edge">
<title>svc:traefik&#45;&gt;svc:gitea</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-753.66C608.91,-734.44 669.61,-703.57 707.98,-684.06"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="709.89,-687.01 717.22,-679.36 706.72,-680.77 709.89,-687.01"/>
</g>
<!-- svc:gotify -->
<g id="node7" class="node">
<title>svc:gotify</title>
<path fill="#dcfce7" stroke="black" d="M759.5,-579C759.5,-579 729.5,-579 729.5,-579 723.5,-579 717.5,-573 717.5,-567 717.5,-567 717.5,-555 717.5,-555 717.5,-549 723.5,-543 729.5,-543 729.5,-543 759.5,-543 759.5,-543 765.5,-543 771.5,-549 771.5,-555 771.5,-555 771.5,-567 771.5,-567 771.5,-573 765.5,-579 759.5,-579"/>
<text text-anchor="middle" x="744.5" y="-564.2" font-family="Helvetica,sans-Serif" font-size="11.00">gotify</text>
<text text-anchor="middle" x="744.5" y="-552.2" font-family="Helvetica,sans-Serif" font-size="11.00">:80</text>
</g>
<!-- svc:traefik&#45;&gt;svc:gotify -->
<g id="edge6" class="edge">
<title>svc:traefik&#45;&gt;svc:gotify</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M553.8,-752.96C592.77,-711.11 686,-611 686,-611 686,-611 700.43,-598.45 714.83,-585.93"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-588.25 722.74,-579.05 712.9,-582.97 717.49,-588.25"/>
</g>
<!-- svc:grafana -->
<g id="node9" class="node">
<title>svc:grafana</title>
<path fill="#dcfce7" stroke="black" d="M762,-999C762,-999 727,-999 727,-999 721,-999 715,-993 715,-987 715,-987 715,-975 715,-975 715,-969 721,-963 727,-963 727,-963 762,-963 762,-963 768,-963 774,-969 774,-975 774,-975 774,-987 774,-987 774,-993 768,-999 762,-999"/>
<text text-anchor="middle" x="744.5" y="-984.2" font-family="Helvetica,sans-Serif" font-size="11.00">grafana</text>
<text text-anchor="middle" x="744.5" y="-972.2" font-family="Helvetica,sans-Serif" font-size="11.00">:3000</text>
</g>
<!-- svc:traefik&#45;&gt;svc:grafana -->
<g id="edge8" class="edge">
<title>svc:traefik&#45;&gt;svc:grafana</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M553.8,-789.04C592.77,-830.89 686,-931 686,-931 686,-931 700.43,-943.55 714.83,-956.07"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-959.03 722.74,-962.95 717.49,-953.75 712.9,-959.03"/>
</g>
<!-- svc:grampsweb -->
<g id="node11" class="node">
<title>svc:grampsweb</title>
<path fill="#dcfce7" stroke="black" d="M772.5,-1528C772.5,-1528 716.5,-1528 716.5,-1528 710.5,-1528 704.5,-1522 704.5,-1516 704.5,-1516 704.5,-1504 704.5,-1504 704.5,-1498 710.5,-1492 716.5,-1492 716.5,-1492 772.5,-1492 772.5,-1492 778.5,-1492 784.5,-1498 784.5,-1504 784.5,-1504 784.5,-1516 784.5,-1516 784.5,-1522 778.5,-1528 772.5,-1528"/>
<text text-anchor="middle" x="744.5" y="-1507.2" font-family="Helvetica,sans-Serif" font-size="11.00">grampsweb</text>
</g>
<!-- svc:traefik&#45;&gt;svc:grampsweb -->
<g id="edge10" class="edge">
<title>svc:traefik&#45;&gt;svc:grampsweb</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M540.91,-789.07C564.42,-897.78 686,-1460 686,-1460 686,-1460 700.43,-1472.55 714.83,-1485.07"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-1488.03 722.74,-1491.95 717.49,-1482.75 712.9,-1488.03"/>
</g>
<!-- svc:influxdb -->
<g id="node13" class="node">
<title>svc:influxdb</title>
<path fill="#dcfce7" stroke="black" d="M767.5,-898C767.5,-898 721.5,-898 721.5,-898 715.5,-898 709.5,-892 709.5,-886 709.5,-886 709.5,-866 709.5,-866 709.5,-860 715.5,-854 721.5,-854 721.5,-854 767.5,-854 767.5,-854 773.5,-854 779.5,-860 779.5,-866 779.5,-866 779.5,-886 779.5,-886 779.5,-892 773.5,-898 767.5,-898"/>
<text text-anchor="middle" x="744.5" y="-885.2" font-family="Helvetica,sans-Serif" font-size="11.00">influxdb</text>
<text text-anchor="middle" x="744.5" y="-873.2" font-family="Helvetica,sans-Serif" font-size="11.00">:8086</text>
<text text-anchor="middle" x="744.5" y="-861.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:influxdb -->
<g id="edge12" class="edge">
<title>svc:traefik&#45;&gt;svc:influxdb</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-788.34C606.37,-806.27 661.57,-834.34 699.97,-853.87"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="698.85,-857.22 709.35,-858.64 702.02,-850.98 698.85,-857.22"/>
</g>
<!-- svc:monitor&#45;kuma -->
<g id="node15" class="node">
<title>svc:monitor&#45;kuma</title>
<path fill="#dcfce7" stroke="black" d="M778.5,-1427C778.5,-1427 710.5,-1427 710.5,-1427 704.5,-1427 698.5,-1421 698.5,-1415 698.5,-1415 698.5,-1403 698.5,-1403 698.5,-1397 704.5,-1391 710.5,-1391 710.5,-1391 778.5,-1391 778.5,-1391 784.5,-1391 790.5,-1397 790.5,-1403 790.5,-1403 790.5,-1415 790.5,-1415 790.5,-1421 784.5,-1427 778.5,-1427"/>
<text text-anchor="middle" x="744.5" y="-1412.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor&#45;kuma</text>
<text text-anchor="middle" x="744.5" y="-1400.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:monitor&#45;kuma -->
<g id="edge14" class="edge">
<title>svc:traefik&#45;&gt;svc:monitor&#45;kuma</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M541.62,-789.24C566.77,-888.49 686,-1359 686,-1359 686,-1359 700.43,-1371.55 714.83,-1384.07"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-1387.03 722.74,-1390.95 717.49,-1381.75 712.9,-1387.03"/>
</g>
<!-- svc:mtls&#45;bridge -->
<g id="node17" class="node">
<title>svc:mtls&#45;bridge</title>
<path fill="#dcfce7" stroke="black" d="M772,-1326C772,-1326 717,-1326 717,-1326 711,-1326 705,-1320 705,-1314 705,-1314 705,-1294 705,-1294 705,-1288 711,-1282 717,-1282 717,-1282 772,-1282 772,-1282 778,-1282 784,-1288 784,-1294 784,-1294 784,-1314 784,-1314 784,-1320 778,-1326 772,-1326"/>
<text text-anchor="middle" x="744.5" y="-1313.2" font-family="Helvetica,sans-Serif" font-size="11.00">mtls&#45;bridge</text>
<text text-anchor="middle" x="744.5" y="-1301.2" font-family="Helvetica,sans-Serif" font-size="11.00">:8080</text>
<text text-anchor="middle" x="744.5" y="-1289.2" font-family="Helvetica,sans-Serif" font-size="11.00">[mTLS]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:mtls&#45;bridge -->
<g id="edge16" class="edge">
<title>svc:traefik&#45;&gt;svc:mtls&#45;bridge</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M542.66,-789.19C569.88,-876.69 686,-1250 686,-1250 686,-1250 698.77,-1262 712.28,-1274.68"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1277.43 719.78,-1281.72 714.89,-1272.33 710.1,-1277.43"/>
</g>
<!-- svc:nextcloud&#45;webapp -->
<g id="node19" class="node">
<title>svc:nextcloud&#45;webapp</title>
<path fill="#dcfce7" stroke="black" d="M791,-137C791,-137 698,-137 698,-137 692,-137 686,-131 686,-125 686,-125 686,-113 686,-113 686,-107 692,-101 698,-101 698,-101 791,-101 791,-101 797,-101 803,-107 803,-113 803,-113 803,-125 803,-125 803,-131 797,-137 791,-137"/>
<text text-anchor="middle" x="744.5" y="-116.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud&#45;webapp</text>
</g>
<!-- svc:traefik&#45;&gt;svc:nextcloud&#45;webapp -->
<g id="edge18" class="edge">
<title>svc:traefik&#45;&gt;svc:nextcloud&#45;webapp</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M541.62,-752.85C566.77,-654.11 686,-186 686,-186 686,-186 704.96,-163.91 721.1,-145.1"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="723.93,-147.18 727.79,-137.31 718.62,-142.62 723.93,-147.18"/>
</g>
<!-- svc:node&#45;red -->
<g id="node21" class="node">
<title>svc:node&#45;red</title>
<path fill="#dcfce7" stroke="black" d="M767.5,-1217C767.5,-1217 721.5,-1217 721.5,-1217 715.5,-1217 709.5,-1211 709.5,-1205 709.5,-1205 709.5,-1185 709.5,-1185 709.5,-1179 715.5,-1173 721.5,-1173 721.5,-1173 767.5,-1173 767.5,-1173 773.5,-1173 779.5,-1179 779.5,-1185 779.5,-1185 779.5,-1205 779.5,-1205 779.5,-1211 773.5,-1217 767.5,-1217"/>
<text text-anchor="middle" x="744.5" y="-1204.2" font-family="Helvetica,sans-Serif" font-size="11.00">node&#45;red</text>
<text text-anchor="middle" x="744.5" y="-1192.2" font-family="Helvetica,sans-Serif" font-size="11.00">:1880</text>
<text text-anchor="middle" x="744.5" y="-1180.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:node&#45;red -->
<g id="edge20" class="edge">
<title>svc:traefik&#45;&gt;svc:node&#45;red</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M544.29,-789.1C574.2,-863.38 686,-1141 686,-1141 686,-1141 698.77,-1153 712.28,-1165.68"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1168.43 719.78,-1172.72 714.89,-1163.33 710.1,-1168.43"/>
</g>
<!-- svc:passbolt&#45;webapp -->
<g id="node23" class="node">
<title>svc:passbolt&#45;webapp</title>
<path fill="#dcfce7" stroke="black" d="M787.5,-36C787.5,-36 701.5,-36 701.5,-36 695.5,-36 689.5,-30 689.5,-24 689.5,-24 689.5,-12 689.5,-12 689.5,-6 695.5,0 701.5,0 701.5,0 787.5,0 787.5,0 793.5,0 799.5,-6 799.5,-12 799.5,-12 799.5,-24 799.5,-24 799.5,-30 793.5,-36 787.5,-36"/>
<text text-anchor="middle" x="744.5" y="-15.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt&#45;webapp</text>
</g>
<!-- svc:traefik&#45;&gt;svc:passbolt&#45;webapp -->
<g id="edge22" class="edge">
<title>svc:traefik&#45;&gt;svc:passbolt&#45;webapp</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M540.83,-752.92C564.15,-642.88 686,-68 686,-68 686,-68 700.43,-55.45 714.83,-42.93"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-45.25 722.74,-36.05 712.9,-39.97 717.49,-45.25"/>
</g>
<!-- svc:portainer -->
<g id="node25" class="node">
<title>svc:portainer</title>
<path fill="#dcfce7" stroke="black" d="M766,-478C766,-478 723,-478 723,-478 717,-478 711,-472 711,-466 711,-466 711,-446 711,-446 711,-440 717,-434 723,-434 723,-434 766,-434 766,-434 772,-434 778,-440 778,-446 778,-446 778,-466 778,-466 778,-472 772,-478 766,-478"/>
<text text-anchor="middle" x="744.5" y="-465.2" font-family="Helvetica,sans-Serif" font-size="11.00">portainer</text>
<text text-anchor="middle" x="744.5" y="-453.2" font-family="Helvetica,sans-Serif" font-size="11.00">:9000</text>
<text text-anchor="middle" x="744.5" y="-441.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:portainer -->
<g id="edge24" class="edge">
<title>svc:traefik&#45;&gt;svc:portainer</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M547.48,-752.65C581.39,-693.24 686,-510 686,-510 686,-510 698.77,-498 712.28,-485.32"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="714.89,-487.67 719.78,-478.28 710.1,-482.57 714.89,-487.67"/>
</g>
<!-- svc:prometheus -->
<g id="node27" class="node">
<title>svc:prometheus</title>
<path fill="#dcfce7" stroke="black" d="M774,-1108C774,-1108 715,-1108 715,-1108 709,-1108 703,-1102 703,-1096 703,-1096 703,-1076 703,-1076 703,-1070 709,-1064 715,-1064 715,-1064 774,-1064 774,-1064 780,-1064 786,-1070 786,-1076 786,-1076 786,-1096 786,-1096 786,-1102 780,-1108 774,-1108"/>
<text text-anchor="middle" x="744.5" y="-1095.2" font-family="Helvetica,sans-Serif" font-size="11.00">prometheus</text>
<text text-anchor="middle" x="744.5" y="-1083.2" font-family="Helvetica,sans-Serif" font-size="11.00">:9090</text>
<text text-anchor="middle" x="744.5" y="-1071.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:prometheus -->
<g id="edge26" class="edge">
<title>svc:traefik&#45;&gt;svc:prometheus</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M547.48,-789.35C581.39,-848.76 686,-1032 686,-1032 686,-1032 698.77,-1044 712.28,-1056.68"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1059.43 719.78,-1063.72 714.89,-1054.33 710.1,-1059.43"/>
</g>
<!-- svc:searxng&#45;webapp -->
<g id="node29" class="node">
<title>svc:searxng&#45;webapp</title>
<path fill="#dcfce7" stroke="black" d="M786,-369C786,-369 703,-369 703,-369 697,-369 691,-363 691,-357 691,-357 691,-345 691,-345 691,-339 697,-333 703,-333 703,-333 786,-333 786,-333 792,-333 798,-339 798,-345 798,-345 798,-357 798,-357 798,-363 792,-369 786,-369"/>
<text text-anchor="middle" x="744.5" y="-348.2" font-family="Helvetica,sans-Serif" font-size="11.00">searxng&#45;webapp</text>
</g>
<!-- svc:traefik&#45;&gt;svc:searxng&#45;webapp -->
<g id="edge28" class="edge">
<title>svc:traefik&#45;&gt;svc:searxng&#45;webapp</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M544.29,-752.9C574.2,-678.62 686,-401 686,-401 686,-401 700.43,-388.45 714.83,-375.93"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-378.25 722.74,-369.05 712.9,-372.97 717.49,-378.25"/>
</g>
<!-- net:traefik -->
<g id="node36" class="node">
<title>net:traefik</title>
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-878" rx="31.04" ry="18"/>
<text text-anchor="middle" x="1007.69" y="-875.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik</text>
</g>
<!-- svc:traefik&#45;&gt;net:traefik -->
<g id="edge55" class="edge">
<title>svc:traefik&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M542.75,-752.82C570.13,-666.27 686,-300 686,-300 686,-300 803,-300 803,-300 803,-300 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- svc:authelia&#45;&gt;net:traefik -->
<g id="edge32" class="edge">
<title>svc:authelia&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M775.21,-783.17C824.86,-803.51 924.25,-844.23 975.12,-865.06"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="974.26,-867.36 981.67,-867.75 976.12,-862.83 974.26,-867.36"/>
</g>
<!-- dns:auth.&lt;domain&gt; -->
<g id="node4" class="node">
<title>dns:auth.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="123.5,-1496 25.5,-1496 25.5,-1460 129.5,-1460 129.5,-1490 123.5,-1496"/>
<polyline fill="none" stroke="black" points="123.5,-1496 123.5,-1490 "/>
<polyline fill="none" stroke="black" points="129.5,-1490 123.5,-1490 "/>
<text text-anchor="middle" x="77.5" y="-1475.2" font-family="Helvetica,sans-Serif" font-size="11.00">auth.&lt;domain&gt;</text>
</g>
<!-- dns:auth.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge3" class="edge">
<title>dns:auth.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-1459.95C128.03,-1445.63 155,-1428 155,-1428 155,-1428 286.65,-925.11 319.6,-799.28"/>
<polygon fill="#334155" stroke="#334155" points="323.07,-799.82 322.22,-789.26 316.3,-798.05 323.07,-799.82"/>
</g>
<!-- svc:gitea&#45;&gt;net:traefik -->
<g id="edge33" class="edge">
<title>svc:gitea&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M768.56,-688.05C784.55,-703.36 803,-721 803,-721 803,-721 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:gitea.&lt;domain&gt; -->
<g id="node6" class="node">
<title>dns:gitea.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="125,-1395 24,-1395 24,-1359 131,-1359 131,-1389 125,-1395"/>
<polyline fill="none" stroke="black" points="125,-1395 125,-1389 "/>
<polyline fill="none" stroke="black" points="131,-1389 125,-1389 "/>
<text text-anchor="middle" x="77.5" y="-1374.2" font-family="Helvetica,sans-Serif" font-size="11.00">gitea.&lt;domain&gt;</text>
</g>
<!-- dns:gitea.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge5" class="edge">
<title>dns:gitea.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-1358.95C128.03,-1344.63 155,-1327 155,-1327 155,-1327 283.58,-911.36 318.4,-798.82"/>
<polygon fill="#334155" stroke="#334155" points="321.76,-799.77 321.38,-789.18 315.08,-797.7 321.76,-799.77"/>
</g>
<!-- svc:gotify&#45;&gt;net:traefik -->
<g id="edge34" class="edge">
<title>svc:gotify&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-579.05C782.73,-593.37 803,-611 803,-611 803,-611 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:gotify.&lt;domain&gt; -->
<g id="node8" class="node">
<title>dns:gotify.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="126,-1294 23,-1294 23,-1258 132,-1258 132,-1288 126,-1294"/>
<polyline fill="none" stroke="black" points="126,-1294 126,-1288 "/>
<polyline fill="none" stroke="black" points="132,-1288 126,-1288 "/>
<text text-anchor="middle" x="77.5" y="-1273.2" font-family="Helvetica,sans-Serif" font-size="11.00">gotify.&lt;domain&gt;</text>
</g>
<!-- dns:gotify.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge7" class="edge">
<title>dns:gotify.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-1257.95C128.03,-1243.63 155,-1226 155,-1226 155,-1226 279.21,-897.41 316.53,-798.71"/>
<polygon fill="#334155" stroke="#334155" points="319.89,-799.71 320.15,-789.12 313.34,-797.23 319.89,-799.71"/>
</g>
<!-- net:monitor -->
<g id="node33" class="node">
<title>net:monitor</title>
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-979" rx="35.46" ry="18"/>
<text text-anchor="middle" x="1007.69" y="-976.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor</text>
</g>
<!-- svc:grafana&#45;&gt;net:monitor -->
<g id="edge35" class="edge">
<title>svc:grafana&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M774.2,-980.78C820.4,-980.43 911.47,-979.73 964.89,-979.32"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="965.19,-981.77 972.17,-979.26 965.15,-976.87 965.19,-981.77"/>
</g>
<!-- svc:grafana&#45;&gt;net:traefik -->
<g id="edge36" class="edge">
<title>svc:grafana&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M774.2,-969.68C823.41,-950.28 923.53,-910.8 974.83,-890.57"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="975.82,-892.81 981.44,-887.96 974.03,-888.25 975.82,-892.81"/>
</g>
<!-- dns:grafana.&lt;domain&gt; -->
<g id="node10" class="node">
<title>dns:grafana.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="132,-1193 17,-1193 17,-1157 138,-1157 138,-1187 132,-1193"/>
<polyline fill="none" stroke="black" points="132,-1193 132,-1187 "/>
<polyline fill="none" stroke="black" points="138,-1187 132,-1187 "/>
<text text-anchor="middle" x="77.5" y="-1172.2" font-family="Helvetica,sans-Serif" font-size="11.00">grafana.&lt;domain&gt;</text>
</g>
<!-- dns:grafana.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge9" class="edge">
<title>dns:grafana.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-1156.95C128.03,-1142.63 155,-1125 155,-1125 155,-1125 273.61,-880.89 313.84,-798.09"/>
<polygon fill="#334155" stroke="#334155" points="317.01,-799.57 318.23,-789.04 310.72,-796.51 317.01,-799.57"/>
</g>
<!-- net:gramps -->
<g id="node32" class="node">
<title>net:gramps</title>
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-1080" rx="34.76" ry="18"/>
<text text-anchor="middle" x="1007.69" y="-1077.2" font-family="Helvetica,sans-Serif" font-size="11.00">gramps</text>
</g>
<!-- svc:grampsweb&#45;&gt;net:gramps -->
<g id="edge37" class="edge">
<title>svc:grampsweb&#45;&gt;net:gramps</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1491.95C782.73,-1477.63 803,-1460 803,-1460 803,-1460 949.23,-1187.21 993.89,-1103.88"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="996.27,-1104.64 997.41,-1097.32 991.95,-1102.33 996.27,-1104.64"/>
</g>
<!-- svc:grampsweb&#45;&gt;net:traefik -->
<g id="edge38" class="edge">
<title>svc:grampsweb&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1491.95C782.73,-1477.63 803,-1460 803,-1460 803,-1460 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
</g>
<!-- dns:familytree.&lt;domain&gt; -->
<g id="node12" class="node">
<title>dns:familytree.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="139,-1092 10,-1092 10,-1056 145,-1056 145,-1086 139,-1092"/>
<polyline fill="none" stroke="black" points="139,-1092 139,-1086 "/>
<polyline fill="none" stroke="black" points="145,-1086 139,-1086 "/>
<text text-anchor="middle" x="77.5" y="-1071.2" font-family="Helvetica,sans-Serif" font-size="11.00">familytree.&lt;domain&gt;</text>
</g>
<!-- dns:familytree.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge11" class="edge">
<title>dns:familytree.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-1055.95C128.03,-1041.63 155,-1024 155,-1024 155,-1024 264.64,-862.73 308.84,-797.71"/>
<polygon fill="#334155" stroke="#334155" points="311.9,-799.43 314.63,-789.2 306.11,-795.5 311.9,-799.43"/>
</g>
<!-- svc:influxdb&#45;&gt;net:monitor -->
<g id="edge39" class="edge">
<title>svc:influxdb&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M779.65,-889.47C829.7,-909.2 922.46,-945.78 972.53,-965.53"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="971.89,-967.91 979.3,-968.2 973.69,-963.35 971.89,-967.91"/>
</g>
<!-- svc:influxdb&#45;&gt;net:traefik -->
<g id="edge40" class="edge">
<title>svc:influxdb&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M779.65,-876.26C828.54,-876.64 918.2,-877.32 968.99,-877.71"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="969.17,-880.16 976.19,-877.77 969.21,-875.26 969.17,-880.16"/>
</g>
<!-- dns:influxdb.&lt;domain&gt; -->
<g id="node14" class="node">
<title>dns:influxdb.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="132.5,-991 16.5,-991 16.5,-955 138.5,-955 138.5,-985 132.5,-991"/>
<polyline fill="none" stroke="black" points="132.5,-991 132.5,-985 "/>
<polyline fill="none" stroke="black" points="138.5,-985 132.5,-985 "/>
<text text-anchor="middle" x="77.5" y="-970.2" font-family="Helvetica,sans-Serif" font-size="11.00">influxdb.&lt;domain&gt;</text>
</g>
<!-- dns:influxdb.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge13" class="edge">
<title>dns:influxdb.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-954.95C128.03,-940.63 155,-923 155,-923 155,-923 250.14,-838.92 298.91,-795.83"/>
<polygon fill="#334155" stroke="#334155" points="301.42,-798.28 306.59,-789.03 296.78,-793.03 301.42,-798.28"/>
</g>
<!-- svc:monitor&#45;kuma&#45;&gt;net:monitor -->
<g id="edge41" class="edge">
<title>svc:monitor&#45;kuma&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1390.95C782.73,-1376.63 803,-1359 803,-1359 803,-1359 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
</g>
<!-- svc:monitor&#45;kuma&#45;&gt;net:traefik -->
<g id="edge42" class="edge">
<title>svc:monitor&#45;kuma&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1390.95C782.73,-1376.63 803,-1359 803,-1359 803,-1359 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
</g>
<!-- dns:monitor&#45;kuma.&lt;domain&gt; -->
<g id="node16" class="node">
<title>dns:monitor&#45;kuma.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="149,-890 0,-890 0,-854 155,-854 155,-884 149,-890"/>
<polyline fill="none" stroke="black" points="149,-890 149,-884 "/>
<polyline fill="none" stroke="black" points="155,-884 149,-884 "/>
<text text-anchor="middle" x="77.5" y="-869.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor&#45;kuma.&lt;domain&gt;</text>
</g>
<!-- dns:monitor&#45;kuma.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge15" class="edge">
<title>dns:monitor&#45;kuma.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M122.93,-853.94C165.02,-836.83 228.32,-811.11 273.25,-792.85"/>
<polygon fill="#334155" stroke="#334155" points="274.59,-796.08 282.54,-789.07 271.96,-789.59 274.59,-796.08"/>
</g>
<!-- svc:mtls&#45;bridge&#45;&gt;net:monitor -->
<g id="edge43" class="edge">
<title>svc:mtls&#45;bridge&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1281.72C785.06,-1266.85 803,-1250 803,-1250 803,-1250 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
</g>
<!-- svc:mtls&#45;bridge&#45;&gt;net:traefik -->
<g id="edge44" class="edge">
<title>svc:mtls&#45;bridge&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1281.72C785.06,-1266.85 803,-1250 803,-1250 803,-1250 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
</g>
<!-- dns:mtls&#45;bridge.&lt;domain&gt; -->
<g id="node18" class="node">
<title>dns:mtls&#45;bridge.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="142,-789 7,-789 7,-753 148,-753 148,-783 142,-789"/>
<polyline fill="none" stroke="black" points="142,-789 142,-783 "/>
<polyline fill="none" stroke="black" points="148,-783 142,-783 "/>
<text text-anchor="middle" x="77.5" y="-768.2" font-family="Helvetica,sans-Serif" font-size="11.00">mtls&#45;bridge.&lt;domain&gt;</text>
</g>
<!-- dns:mtls&#45;bridge.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge17" class="edge">
<title>dns:mtls&#45;bridge.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M148.05,-771C182.94,-771 225.03,-771 259.61,-771"/>
<polygon fill="#334155" stroke="#334155" points="259.62,-774.5 269.62,-771 259.62,-767.5 259.62,-774.5"/>
</g>
<!-- net:nextcloud -->
<g id="node34" class="node">
<title>net:nextcloud</title>
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-777" rx="42.89" ry="18"/>
<text text-anchor="middle" x="1007.69" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud</text>
</g>
<!-- svc:nextcloud&#45;webapp&#45;&gt;net:nextcloud -->
<g id="edge45" class="edge">
<title>svc:nextcloud&#45;webapp&#45;&gt;net:nextcloud</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M761.21,-137.31C778.24,-157.15 803,-186 803,-186 803,-186 910,-727 910,-727 910,-727 945.48,-745.35 973.46,-759.81"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="972.67,-762.17 980.02,-763.2 974.92,-757.81 972.67,-762.17"/>
</g>
<!-- svc:nextcloud&#45;webapp&#45;&gt;net:traefik -->
<g id="edge46" class="edge">
<title>svc:nextcloud&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M761.21,-137.31C778.24,-157.15 803,-186 803,-186 803,-186 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:nextcloud.&lt;domain&gt; -->
<g id="node20" class="node">
<title>dns:nextcloud.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="138,-688 11,-688 11,-652 144,-652 144,-682 138,-688"/>
<polyline fill="none" stroke="black" points="138,-688 138,-682 "/>
<polyline fill="none" stroke="black" points="144,-682 138,-682 "/>
<text text-anchor="middle" x="77.5" y="-667.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud.&lt;domain&gt;</text>
</g>
<!-- dns:nextcloud.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge19" class="edge">
<title>dns:nextcloud.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M122.93,-688.06C165.02,-705.17 228.32,-730.89 273.25,-749.15"/>
<polygon fill="#334155" stroke="#334155" points="271.96,-752.41 282.54,-752.93 274.59,-745.92 271.96,-752.41"/>
</g>
<!-- svc:node&#45;red&#45;&gt;net:monitor -->
<g id="edge47" class="edge">
<title>svc:node&#45;red&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1172.72C785.06,-1157.85 803,-1141 803,-1141 803,-1141 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
</g>
<!-- svc:node&#45;red&#45;&gt;net:traefik -->
<g id="edge48" class="edge">
<title>svc:node&#45;red&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1172.72C785.06,-1157.85 803,-1141 803,-1141 803,-1141 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
</g>
<!-- dns:node&#45;red.&lt;domain&gt; -->
<g id="node22" class="node">
<title>dns:node&#45;red.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="135.5,-587 13.5,-587 13.5,-551 141.5,-551 141.5,-581 135.5,-587"/>
<polyline fill="none" stroke="black" points="135.5,-587 135.5,-581 "/>
<polyline fill="none" stroke="black" points="141.5,-581 135.5,-581 "/>
<text text-anchor="middle" x="77.5" y="-566.2" font-family="Helvetica,sans-Serif" font-size="11.00">node&#45;red.&lt;domain&gt;</text>
</g>
<!-- dns:node&#45;red.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge21" class="edge">
<title>dns:node&#45;red.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-587.05C128.03,-601.37 155,-619 155,-619 155,-619 250.14,-703.08 298.91,-746.17"/>
<polygon fill="#334155" stroke="#334155" points="296.78,-748.97 306.59,-752.97 301.42,-743.72 296.78,-748.97"/>
</g>
<!-- net:passbolt -->
<g id="node35" class="node">
<title>net:passbolt</title>
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-676" rx="37.77" ry="18"/>
<text text-anchor="middle" x="1007.69" y="-673.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt</text>
</g>
<!-- svc:passbolt&#45;webapp&#45;&gt;net:passbolt -->
<g id="edge49" class="edge">
<title>svc:passbolt&#45;webapp&#45;&gt;net:passbolt</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-36.05C782.73,-50.37 803,-68 803,-68 803,-68 960.4,-537.83 998.48,-651.47"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="996.16,-652.27 1000.71,-658.13 1000.81,-650.72 996.16,-652.27"/>
</g>
<!-- svc:passbolt&#45;webapp&#45;&gt;net:traefik -->
<g id="edge50" class="edge">
<title>svc:passbolt&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-36.05C782.73,-50.37 803,-68 803,-68 803,-68 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:passbolt.&lt;domain&gt; -->
<g id="node24" class="node">
<title>dns:passbolt.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="134,-486 15,-486 15,-450 140,-450 140,-480 134,-486"/>
<polyline fill="none" stroke="black" points="134,-486 134,-480 "/>
<polyline fill="none" stroke="black" points="140,-480 134,-480 "/>
<text text-anchor="middle" x="77.5" y="-465.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt.&lt;domain&gt;</text>
</g>
<!-- dns:passbolt.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge23" class="edge">
<title>dns:passbolt.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-486.05C128.03,-500.37 155,-518 155,-518 155,-518 264.64,-679.27 308.84,-744.29"/>
<polygon fill="#334155" stroke="#334155" points="306.11,-746.5 314.63,-752.8 311.9,-742.57 306.11,-746.5"/>
</g>
<!-- svc:portainer&#45;&gt;net:traefik -->
<g id="edge51" class="edge">
<title>svc:portainer&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-478.28C785.06,-493.15 803,-510 803,-510 803,-510 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:portainer.&lt;domain&gt; -->
<g id="node26" class="node">
<title>dns:portainer.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="135.5,-385 13.5,-385 13.5,-349 141.5,-349 141.5,-379 135.5,-385"/>
<polyline fill="none" stroke="black" points="135.5,-385 135.5,-379 "/>
<polyline fill="none" stroke="black" points="141.5,-379 135.5,-379 "/>
<text text-anchor="middle" x="77.5" y="-364.2" font-family="Helvetica,sans-Serif" font-size="11.00">portainer.&lt;domain&gt;</text>
</g>
<!-- dns:portainer.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge25" class="edge">
<title>dns:portainer.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-385.05C128.03,-399.37 155,-417 155,-417 155,-417 273.61,-661.11 313.84,-743.91"/>
<polygon fill="#334155" stroke="#334155" points="310.72,-745.49 318.23,-752.96 317.01,-742.43 310.72,-745.49"/>
</g>
<!-- svc:prometheus&#45;&gt;net:monitor -->
<g id="edge52" class="edge">
<title>svc:prometheus&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M786.03,-1069.4C837.48,-1048.32 925.31,-1012.34 973.18,-992.73"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="974.11,-995 979.66,-990.07 972.26,-990.46 974.11,-995"/>
</g>
<!-- svc:prometheus&#45;&gt;net:traefik -->
<g id="edge53" class="edge">
<title>svc:prometheus&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1063.72C785.06,-1048.85 803,-1032 803,-1032 803,-1032 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
</g>
<!-- dns:prometheus.&lt;domain&gt; -->
<g id="node28" class="node">
<title>dns:prometheus.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="144,-284 5,-284 5,-248 150,-248 150,-278 144,-284"/>
<polyline fill="none" stroke="black" points="144,-284 144,-278 "/>
<polyline fill="none" stroke="black" points="150,-278 144,-278 "/>
<text text-anchor="middle" x="77.5" y="-263.2" font-family="Helvetica,sans-Serif" font-size="11.00">prometheus.&lt;domain&gt;</text>
</g>
<!-- dns:prometheus.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge27" class="edge">
<title>dns:prometheus.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-284.05C128.03,-298.37 155,-316 155,-316 155,-316 279.21,-644.59 316.53,-743.29"/>
<polygon fill="#334155" stroke="#334155" points="313.34,-744.77 320.15,-752.88 319.89,-742.29 313.34,-744.77"/>
</g>
<!-- svc:searxng&#45;webapp&#45;&gt;net:traefik -->
<g id="edge54" class="edge">
<title>svc:searxng&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-369.05C782.73,-383.37 803,-401 803,-401 803,-401 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:searxng.&lt;domain&gt; -->
<g id="node30" class="node">
<title>dns:searxng.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="133,-183 16,-183 16,-147 139,-147 139,-177 133,-183"/>
<polyline fill="none" stroke="black" points="133,-183 133,-177 "/>
<polyline fill="none" stroke="black" points="139,-177 133,-177 "/>
<text text-anchor="middle" x="77.5" y="-162.2" font-family="Helvetica,sans-Serif" font-size="11.00">searxng.&lt;domain&gt;</text>
</g>
<!-- dns:searxng.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge29" class="edge">
<title>dns:searxng.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-183.05C128.03,-197.37 155,-215 155,-215 155,-215 283.58,-630.64 318.4,-743.18"/>
<polygon fill="#334155" stroke="#334155" points="315.08,-744.3 321.38,-752.82 321.76,-742.23 315.08,-744.3"/>
</g>
<!-- dns:traefik.&lt;domain&gt; -->
<g id="node31" class="node">
<title>dns:traefik.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="128.5,-82 20.5,-82 20.5,-46 134.5,-46 134.5,-76 128.5,-82"/>
<polyline fill="none" stroke="black" points="128.5,-82 128.5,-76 "/>
<polyline fill="none" stroke="black" points="134.5,-76 128.5,-76 "/>
<text text-anchor="middle" x="77.5" y="-61.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik.&lt;domain&gt;</text>
</g>
<!-- dns:traefik.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge31" class="edge">
<title>dns:traefik.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-82.05C128.03,-96.37 155,-114 155,-114 155,-114 286.65,-616.89 319.6,-742.72"/>
<polygon fill="#334155" stroke="#334155" points="316.3,-743.95 322.22,-752.74 323.07,-742.18 316.3,-743.95"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 41 KiB

+4
View File
@@ -0,0 +1,4 @@
digraph PhysicalTopology {
graph [rankdir=LR, fontname="Helvetica", nodesep=1.0, ranksep=1.5];
"placeholder:inventory" [shape=note, style="filled", fillcolor="#fef3c7", label="Host inventory JSON not found.\nGenerate terraform inventory and rerun scripts/docs/generate-all.sh\n(--host-inventory <path>)."];
}
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: PhysicalTopology Pages: 1 -->
<svg width="514pt" height="61pt"
viewBox="0.00 0.00 514.00 61.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 57)">
<title>PhysicalTopology</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-57 510,-57 510,4 -4,4"/>
<!-- placeholder:inventory -->
<g id="node1" class="node">
<title>placeholder:inventory</title>
<polygon fill="#fef3c7" stroke="black" points="500,-53 0,-53 0,0 506,0 506,-47 500,-53"/>
<polyline fill="none" stroke="black" points="500,-53 500,-47 "/>
<polyline fill="none" stroke="black" points="506,-47 500,-47 "/>
<text text-anchor="middle" x="253" y="-37.8" font-family="Times,serif" font-size="14.00">Host inventory JSON not found.</text>
<text text-anchor="middle" x="253" y="-22.8" font-family="Times,serif" font-size="14.00">Generate terraform inventory and rerun scripts/docs/generate&#45;all.sh</text>
<text text-anchor="middle" x="253" y="-7.8" font-family="Times,serif" font-size="14.00">(&#45;&#45;host&#45;inventory &lt;path&gt;).</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+61
View File
@@ -0,0 +1,61 @@
# Docker Environment Composition
This repo uses multi-file Docker Compose with a wrapper script as the composition entrypoint.
## Composition source of truth
`services-up.sh` is the composition authority for this repository.
It:
1. discovers compose files under `apps/`, `monitoring/`, and `core/`,
2. prepends shared baseline files,
3. applies `default-environment.env` and `secrets/stack-secrets.env`,
4. invokes `docker compose` with a stable project name.
Because of this, when validating or understanding runtime composition, prefer running:
```bash
./services-up.sh --profile all config
```
## Inputs used by `services-up.sh`
- `default-network.yml`
- discovered `docker-compose.yml` / `docker-compose.yaml` files under `core/`, `apps/`, `monitoring/`
- `default-environment.env`
- `secrets/stack-secrets.env` (local, not committed)
## Typical workflows
### Validate final composed model
```bash
./services-up.sh --profile all config
```
Use this to review merged services, networks, volumes, profiles, and environment substitution.
### Validate one compose file directly
```bash
docker compose -f apps/nextcloud/docker-compose.yml config
```
Use this when focused on one service family.
### Deployment prerequisites
Before runtime operations, follow [deployment-prerequisites.md](deployment-prerequisites.md) to create required local secret files.
## What not to do
- Do not treat archived compose files in `archive/` as active runtime definitions.
- Do not hardcode secrets in committed compose files.
- Do not bypass `services-up.sh` when trying to understand full active composition.
## Related docs
- [docs/source-of-truth.md](source-of-truth.md)
- [docs/repo-structure.md](repo-structure.md)
- [docs/architecture.md](architecture.md)
+13
View File
@@ -0,0 +1,13 @@
# Docker Environment
This environment is orchestrated from central Docker Compose definitions committed in this repository.
Compose source files are rendered into a resolved configuration during docs generation, then summarized into generated markdown and diagrams.
Generated outputs:
- [Compose Inventory](generated/compose-inventory.md)
- [Resolved Compose Config](generated/docker-compose.resolved.yml)
- [Docker Compose Diagram](diagrams/docker-compose.svg)
Generated documentation is produced by CI from repository files only. Documentation generation does not start containers.
+36
View File
@@ -0,0 +1,36 @@
# Documentation Strategy
This repository's documentation should help both humans and Codex agents make safe, accurate changes.
## Principles
1. **Authority first**: identify authoritative files clearly (Compose + `services-up.sh` for runtime; Terraform for structured inventory/reconciliation).
2. **Task-oriented docs**: include practical workflow steps, not only conceptual text.
3. **No speculation**: document implemented behavior, not aspirational designs without code evidence.
4. **Cross-linking**: root README and topic docs should point to each other for discoverability.
5. **Safety clarity**: explicitly note what should not be committed or applied casually.
## Documentation quality checklist
Before merging doc changes, check:
- Is this statement verifiable from current repo files?
- Does this conflict with existing docs?
- Does this clarify source-of-truth boundaries?
- Does this improve a real workflow for contributors/Codex?
- Are sensitive details excluded?
## Recommended update cadence
Update docs when any of these change:
- `services-up.sh` composition behavior,
- major Compose directory or profile structure,
- Terraform workflow conventions,
- inventory output shapes,
- secret handling conventions.
## Audience-specific outcomes
- Humans should quickly understand how to operate the repo safely.
- Future Codex runs should quickly identify authoritative files, guardrails, and reconciliation workflows.
+139
View File
@@ -0,0 +1,139 @@
# Dynu DNS Read-Only Inventory
This repository includes a **read-only** Dynu DNS inventory workflow for `lan.ddnsgeek.com`.
> This integration is intentionally read-only. No Dynu mutations are permitted in this repo at this stage.
## Scope
- Fetch live DNS/domain data from Dynu using **GET requests only**.
- Correlate Dynu hostnames with Traefik `Host(...)` rules found in compose files.
- Generate local inventory artifacts for documentation.
## Safety Guard Rails
- Scripts fail unless `DYNU_READ_ONLY=true`.
- No Dynu write methods (`POST`, `PUT`, `PATCH`, `DELETE`) are implemented.
- No Terraform Dynu provider/resources/modules are introduced.
- No Ansible Dynu mutation tasks are introduced.
- API secrets are read from environment variables and are never logged.
## Correlation logic
`scripts/dynu/correlate_dynu_with_traefik.py` uses compose files as the source of truth and parses them as YAML.
It supports both common label formats:
- list style:
```yaml
labels:
- "traefik.http.routers.app.rule=Host(`app.lan.ddnsgeek.com`)"
```
- map style:
```yaml
labels:
traefik.http.routers.app.rule: "Host(`app.lan.ddnsgeek.com`)"
```
The parser extracts hostnames from router rules such as:
- `Host(`a`)`
- `Host("a")`
- `Host('a')`
- multi-host rules (comma-delimited)
- combined expressions such as `Host(...) && PathPrefix(...)`
## Route metadata in inventory
Each discovered hostname mapping includes:
- fqdn
- compose service name
- compose file path
- stack area (`apps`, `monitoring`, `core`)
- router label key(s)
- raw router rule
- `uses_tls`
- `tls_options`
- `middlewares`
- `uses_mtls`
- `uses_authelia`
mTLS is metadata only and **never blocks mapping**.
## Validation model
The generated JSON/Markdown include a top-level `validation` section with:
- `allowed_unmapped_hostnames`
- `unexpected_unmapped_hostnames`
- `duplicate_hostnames`
- `ambiguous_hostnames`
- `validation_ok`
Current policy:
- `edge.lan.ddnsgeek.com` is the only allowed unmapped DNS hostname.
- every other `*.lan.ddnsgeek.com` DNS hostname should map to a compose/Traefik-discovered service.
Optional strict mode:
- Set `DYNU_ENFORCE_VALIDATION=true` to make the correlate script exit non-zero when unexpected unmapped hostnames exist.
## Required Environment Variables
- `DYNU_API_KEY` (required for fetch)
- `DYNU_BASE_URL` (optional, defaults to `https://api.dynu.com`)
- `DYNU_READ_ONLY` (**must** be `true`)
Recommended local secrets file (not committed): `secrets/dynu.env`
```bash
DYNU_API_KEY=replace-with-real-api-key
DYNU_READ_ONLY=true
DYNU_BASE_URL=https://api.dynu.com
```
Notes:
- Keep values unquoted unless required by your shell.
- `scripts/dynu/build_dns_inventory.sh` will auto-load `secrets/dynu.env` when present.
## Commands
Run directly:
```bash
DYNU_READ_ONLY=true DYNU_API_KEY=... python3 scripts/dynu/fetch_dynu_dns.py
DYNU_READ_ONLY=true python3 scripts/dynu/correlate_dynu_with_traefik.py
```
Or run the wrapper:
```bash
scripts/dynu/build_dns_inventory.sh
```
## Artifacts
- `data/dns/dynu_live.json` (generated, untracked by default due to repo `data/` ignore)
- `data/dns/dynu_traefik_inventory.json` (generated, untracked by default)
- `docs/generated/dns-inventory.md` (generated documentation artifact)
Because `data/` is gitignored in this repository, JSON outputs are intentionally local-only unless ignore behavior changes in the future.
## Ansible Wrapper (Read-Only)
A syntax-safe wrapper playbook is provided at:
- `infrastructure/ansible/playbooks/dns-inventory.yml`
It only executes the local read-only scripts and does not call write-capable Dynu APIs.
## Not Managed Yet
Dynu DNS records are **not** managed by Terraform or Ansible in this repository at this stage.
No configuration in this repository sends Dynu mutation requests.
View File
+26
View File
@@ -0,0 +1,26 @@
default-network.yml
apps/gitea/docker-compose.yml
apps/gramps/docker-compose.yml
apps/nextcloud/docker-compose.yml
apps/passbolt/docker-compose.yml
apps/searxng/docker-compose.yml
apps/shift-recorder/docker-compose.yml
apps/stockfill/docker-compose.yml
core/authelia/docker-compose.yml
core/crowdsec/docker-compose.yml
core/error-pages/docker-compose.yml
core/test/docker-compose.yml
core/traefik/docker-compose.yml
monitoring/docker-exporter/docker-compose.yml
monitoring/docker-socket-proxy/docker-compose.yml
monitoring/gotify/docker-compose.yml
monitoring/grafana/docker-compose.yml
monitoring/influxdb/docker-compose.yml
monitoring/mtls-bridge/docker-compose.yml
monitoring/node-exporter/docker-compose.yml
monitoring/node-red/docker-compose.yml
monitoring/pihole-exporter/docker-compose.yml
monitoring/portainer/docker-compose.yml
monitoring/prometheus/docker-compose.yml
monitoring/telegraf/docker-compose.yml
monitoring/uptime-kuma/docker-compose.yml
+61
View File
@@ -0,0 +1,61 @@
# Docker Compose Inventory
Source fingerprint: `0fad36c3fed6`
## Summary
| Item | Count |
|---|---:|
| Services | 30 |
| Networks | 5 |
| Volumes | 0 |
## Services
| Service | Container | Image | Build | Profiles | Networks | Ports | Restart |
|---|---|---|---|---|---|---|---|
| authelia | authelia | authelia/authelia | /home/nixos/docker/core/authelia | core, all, authelia, traefik | traefik | | always |
| crowdsec | crowdsec | | /home/nixos/docker/core/crowdsec | core, all, crowdsec, traefik | traefik | | always |
| docker-socket-proxy | docker-socket-proxy | tecnativa/docker-socket-proxy:latest | | monitoring, all, docker-socket-proxy, core, traefik, prometheus | monitor, traefik | | unless-stopped |
| docker-update-exporter | docker-update-exporter | | /home/nixos/docker/monitoring/docker-exporter | monitoring, all, docker-exporter, prometheus | monitor | | unless-stopped |
| error-pages | error-pages | tarampampam/error-pages:3 | | core, all, error-pages, traefik | traefik | | always |
| gitea | gitea | gitea/gitea:latest | | apps, all, gitea | traefik | | always |
| gitea-runner | gitea-runner | gitea/act_runner:latest | | apps, all, gitea, ci | traefik | | always |
| gotify | gotify | gotify/server:latest | | monitoring, all, gotify | traefik | | always |
| grafana | grafana | grafana/grafana:latest | | monitoring, all, grafana | monitor, traefik | | unless-stopped |
| gramps-redis | gramps-redis | valkey/valkey:8-alpine | | apps, all, gramps | gramps | | always |
| grampsweb | gramps-web | ghcr.io/gramps-project/grampsweb:latest | | apps, all, gramps | gramps, traefik | | always |
| grampsweb_celery | gramps-web-celery | ghcr.io/gramps-project/grampsweb:latest | | apps, all, gramps | gramps | | always |
| influxdb | influxdb | influxdb:2.7 | | monitoring, all, influxdb, prometheus | monitor, traefik | | unless-stopped |
| monitor-kuma | monitor-kuma | louislam/uptime-kuma:2.1.1 | | monitoring, all, uptime-kuma | monitor, traefik | | always |
| mtls-bridge | mtls-bridge | | /home/nixos/docker/monitoring/mtls-bridge | monitoring, all, mtls-bridge | monitor, traefik | | unless-stopped |
| nextcloud-db | nextcloud-db | mariadb:11.4 | | apps, all, nextcloud | nextcloud | | always |
| nextcloud-redis | nextcloud-redis | redis | | apps, all, nextcloud | nextcloud | | always |
| nextcloud-webapp | nextcloud-webapp | | /home/nixos/docker/apps/nextcloud | apps, all, nextcloud | nextcloud, traefik | | always |
| node-exporter | node-exporter | prom/node-exporter:latest | | monitoring, all, node-exporter, prometheus | monitor | | unless-stopped |
| node-red | node-red | | /home/nixos/docker/monitoring/node-red | monitoring, all, node-red | monitor, traefik | | unless-stopped |
| passbolt-db | passbolt-db | mariadb:12 | | apps, all, passbolt | passbolt | | always |
| passbolt-webapp | passbolt-webapp | passbolt/passbolt:latest-ce | | apps, all, passbolt | passbolt, traefik | | always |
| pihole-exporter | pihole-exporter | ekofr/pihole-exporter:latest | | monitoring, all, pihole-exporter, prometheus | monitor | {'mode': 'ingress', 'target': 9617, 'published': '9617', 'protocol': 'tcp'} | unless-stopped |
| portainer | portainer | portainer/portainer-ce:latest | | monitoring, all, portainer | traefik | | unless-stopped |
| prometheus | prometheus | prom/prometheus:latest | | monitoring, all, prometheus | monitor, traefik | | unless-stopped |
| searxng-webapp | searxng-webapp | searxng/searxng | | apps, all, searxng | traefik | | always |
| shift-recorder-web | shift-recorder | | /home/nixos/docker/apps/shift-recorder | apps, all, shift-recorder | traefik | | unless-stopped |
| stockfill | stockfill | | /home/nixos/docker/apps/stockfill | apps, all, stockfill | traefik | | unless-stopped |
| telegraf | telegraf | telegraf:latest | | monitoring, all, telegraf, prometheus | monitor | | unless-stopped |
| traefik | traefik | traefik:3 | /home/nixos/docker/core | core, all, traefik | traefik | {'mode': 'ingress', 'target': 80, 'published': '80', 'protocol': 'tcp'}, {'mode': 'ingress', 'target': 443, 'published': '443', 'protocol': 'tcp'} | always |
## Networks
| Network | Driver | External |
|---|---|---|
| gramps | | False |
| monitor | | False |
| nextcloud | | False |
| passbolt | | False |
| traefik | bridge | False |
## Volumes
| Volume | External |
|---|---|
+65
View File
@@ -0,0 +1,65 @@
# DNS Inventory (Dynu + Traefik)
> This integration is intentionally read-only. No Dynu mutations are permitted in this repo at this stage.
- Base domain: `lan.ddnsgeek.com`
- Dynu fetched at: `2026-04-21T04:18:38+00:00`
- Inventory generated at: `2026-04-21T04:18:39+00:00`
## Summary
- Traefik hostnames discovered: **17**
- Dynu hostnames discovered: **20**
- Mapped hostnames: **17**
- DNS-only hostnames: **3**
- Traefik-only hostnames: **0**
- Ambiguous hostnames: **0**
## Validation
- Validation ok: **false**
- Allowed unmapped hostnames: `edge.lan.ddnsgeek.com`
- Unexpected unmapped hostnames: **1**
- Duplicate hostnames: **1**
- Ambiguous hostnames: **0**
### Allowed unmapped hostnames
- `edge.lan.ddnsgeek.com`
### Unexpected unmapped hostnames
- `kuma.lan.ddnsgeek.com`
### Duplicate hostnames
- `mtls-bridge.lan.ddnsgeek.com`
### Ambiguous hostnames
_None._
## Correlation
| Hostname | Status | Reasons | Service(s) | Route metadata | DNS records |
|---|---|---|---|---|---|
| `auth.lan.ddnsgeek.com` | `mapped` | `mapped` | core/authelia | authelia [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
| `edge.lan.ddnsgeek.com` | `allowed_unmapped` | `allowed_unmapped, dns_only` | - | - | A: |
| `familytree.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/grampsweb | gramps [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
| `gitea.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/gitea | gitea [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
| `gotify.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/gotify | gotify [tls=true, mtls=true, authelia=false, tls_options=mtls-private-admin@file, middlewares=-] | A: |
| `grafana.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/grafana | grafana [tls=true, mtls=true, authelia=false, tls_options=mtls-private-admin@file, middlewares=-] | A: |
| `influxdb.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/influxdb | influxdb [tls=true, mtls=true, authelia=true, tls_options=mtls-private-admin@file, middlewares=authelia] | A: |
| `kuma.lan.ddnsgeek.com` | `unexpected_unmapped` | `unexpected_unmapped, dns_only` | - | - | A:120.155.63.223 |
| `lan.ddnsgeek.com` | `dns_only` | `dns_only` | - | - | SOA: |
| `monitor-kuma.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/monitor-kuma | monitor [tls=true, mtls=true, authelia=false, tls_options=mtls-private-admin@file, middlewares=-] | A: |
| `mtls-bridge.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/mtls-bridge | mtls-bridge [tls=true, mtls=true, authelia=false, tls_options=-, middlewares=mtls-bridge-auth,mtls-bridge-cors]<br>mtls-bridge-preflight [tls=true, mtls=true, authelia=false, tls_options=-, middlewares=mtls-bridge-cors] | A: |
| `nextcloud.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/nextcloud-webapp | nextcloud [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=nextcloud-dav,nextcloud-webfinger] | A: |
| `node-red.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/node-red | node-red [tls=true, mtls=true, authelia=true, tls_options=mtls-private-admin@file, middlewares=authelia] | A: |
| `passbolt.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/passbolt-webapp | passbolt [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
| `portainer.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/portainer | portainer [tls=true, mtls=true, authelia=false, tls_options=mtls-private-admin@file, middlewares=-] | A: |
| `prometheus.lan.ddnsgeek.com` | `mapped` | `mapped` | monitoring/prometheus | prometheus [tls=true, mtls=true, authelia=true, tls_options=mtls-private-admin@file, middlewares=authelia] | A: |
| `searxng.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/searxng-webapp | searxng [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
| `shifts.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/shift-recorder-web | shifts [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
| `stockfill.lan.ddnsgeek.com` | `mapped` | `mapped` | apps/stockfill | stockfill [tls=true, mtls=false, authelia=false, tls_options=-, middlewares=-] | A: |
| `traefik.lan.ddnsgeek.com` | `mapped` | `mapped` | core/traefik | traefik [tls=true, mtls=true, authelia=true, tls_options=mtls-private-admin@file, middlewares=authelia] | A: |
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
# Host Topology
> Generated by `scripts/docs/generate_host_topology.py` on 2026-05-12T18:32:09+00:00.
## Topology Diagram
```mermaid
flowchart TD
phys_pve["pve\nphysical"]
phys_raspberrypi["raspberrypi\nphysical"]
virt_docker["docker\nvirtual"]
phys_pve --> virt_docker
virt_nix_cache["nix-cache\nvirtual"]
phys_pve --> virt_nix_cache
virt_pbs["pbs\nvirtual"]
phys_pve --> virt_pbs
virt_pihole["pihole\nvirtual"]
phys_pve --> virt_pihole
virt_server_nixos["server-nixos\nvirtual"]
phys_pve --> virt_server_nixos
```
## Physical Hosts
| Name | Type | Role | Management | OS | Hypervisor | Location | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- |
| pve | physical | proxmox | pve.sweet.home | debian | proxmox | home | Primary Proxmox VE host |
| raspberrypi | physical | edge | raspberrypi.tail13f623.ts.net | debian | | riverglades | Raspberry Pi host |
## Virtual Hosts
| Name | Type | Role | Parent/Node | Management | OS | Notes |
| --- | --- | --- | --- | --- | --- | --- |
| docker | virtual | docker-host | pve | | linux | Primary Docker VM |
| nix-cache | virtual | cache | pve | | linux | Nix binary cache VM |
| pbs | virtual | backup | pve | | linux | Proxmox Backup Server VM |
| pihole | virtual | dns | pve | | linux | DNS filtering VM |
| server-nixos | virtual | nixos-server | pve | | nixos | General-purpose NixOS VM |
+11
View File
@@ -0,0 +1,11 @@
# Generated Documentation
This directory contains documentation generated automatically from repository configuration.
## Files
- [Compose file list](compose-files.txt)
- [Resolved Docker Compose config](docker-compose.resolved.yml)
- [Compose inventory](compose-inventory.md)
- [Traefik routes](traefik-routes.md)
- [Docker Compose diagram](../diagrams/docker-compose.svg)
+23
View File
@@ -0,0 +1,23 @@
# Traefik Routes
| Service | Router | Rule | Entrypoints | TLS | Middlewares | Target Port |
|---|---|---|---|---|---|---|
| authelia | authelia | Host(`auth.lan.ddnsgeek.com`) | websecure | true | | |
| error-pages | error-pages-router | HostRegexp(`{host:.+}`) | web | | error-pages-middleware | |
| gitea | gitea | Host(`gitea.lan.ddnsgeek.com`) | websecure | true | | 3000 |
| gotify | gotify | Host(`gotify.lan.ddnsgeek.com`) | websecure | | | 80 |
| grafana | grafana | Host(`grafana.lan.ddnsgeek.com`) | websecure | | | 3000 |
| grampsweb | gramps | Host(`familytree.lan.ddnsgeek.com`) | websecure | | | 5000 |
| influxdb | influxdb | Host(`influxdb.lan.ddnsgeek.com`) | websecure | | authelia | 8086 |
| monitor-kuma | monitor | Host(`monitor-kuma.lan.ddnsgeek.com`) | websecure | true | | 3001 |
| mtls-bridge | mtls-bridge | Host(`mtls-bridge.lan.ddnsgeek.com`) | websecure | | mtls-bridge-auth,mtls-bridge-cors | 8080 |
| mtls-bridge | mtls-bridge-preflight | Host(`mtls-bridge.lan.ddnsgeek.com`) && Method(`OPTIONS`) | websecure | | mtls-bridge-cors | |
| nextcloud-webapp | nextcloud | Host(`nextcloud.lan.ddnsgeek.com`) | websecure | | nextcloud-dav, nextcloud-webfinger | |
| node-red | node-red | Host(`node-red.lan.ddnsgeek.com`) | websecure | | authelia | 1880 |
| passbolt-webapp | passbolt | Host(`passbolt.lan.ddnsgeek.com`) | websecure | | | |
| portainer | portainer | Host(`portainer.lan.ddnsgeek.com`) | websecure | true | | 9000 |
| prometheus | prometheus | Host(`prometheus.lan.ddnsgeek.com`) | websecure | | authelia | 9090 |
| searxng-webapp | searxng | Host(`searxng.lan.ddnsgeek.com`) | websecure | | | 8080 |
| shift-recorder-web | shifts | Host(`shifts.lan.ddnsgeek.com`) | websecure | true | | 80 |
| stockfill | stockfill | Host(`stockfill.lan.ddnsgeek.com`) | websecure | true | | 80 |
| traefik | traefik | Host(`traefik.lan.ddnsgeek.com`) | websecure | | authelia | |
+29
View File
@@ -0,0 +1,29 @@
# Infrastructure Documentation
This documentation describes the Docker-based infrastructure, reverse proxy configuration, monitoring stack, automation services, and generated inventory for this environment.
Some sections are manually written. Other sections are generated automatically by GitHub Actions from the repository configuration.
## Sections
- [Docker Environment](docker.md)
- [Networking and Reverse Proxy](networking.md)
- [Monitoring and Alerting](monitoring.md)
- [Automation](automation.md)
- [Operations](operations.md)
- [Public Showcase](showcase.md)
## Generated Documentation
- [Compose Inventory](generated/compose-inventory.md)
- [Traefik Routes](generated/traefik-routes.md)
- [Resolved Compose Config](generated/docker-compose.resolved.yml)
- [Docker Compose Diagram](diagrams/docker-compose.svg)
## Public-safe Output
Sanitized documentation intended for public sharing is generated under:
```text
docs/public/
```
+76
View File
@@ -0,0 +1,76 @@
# Infrastructure Inventory Model
This repository treats infrastructure inventory as first-class documentation.
## Intent
The goal is not only deployment configuration, but also a maintainable map of:
- what hosts/VMs exist,
- how they are identified,
- what selected runtime objects are mirrored into Terraform,
- what outputs can be consumed by docs and future tooling.
## Current inventory sources
### 1) Terraform Proxmox layer
`infrastructure/terraform/proxmox/` contains imported/reconciled VM resources and local metadata for physical hosts.
This is currently the most structured host/VM inventory in the repo.
### 2) Terraform Docker layer
`infrastructure/terraform/docker/` contains selective Docker container resources used as documentation-oriented mirrors.
These resources should match existing running containers, not redefine runtime composition strategy.
### 3) Ansible bootstrap layer
`infrastructure/ansible/` provides an emerging inventory/configuration scaffold for hosts and devices.
Current scope is intentionally limited to structure, variables scaffolding, and safe validation workflows.
### 4) Compose runtime definitions
Compose files define intended service runtime composition, networking, labels, and integration.
### 5) Architecture docs
`docs/architecture.md` provides a human-readable topology view based on repository configuration and observed runtime signals.
### 6) Dynu DNS read-only inventory
`scripts/dynu/` and `docs/dynu-dns-inventory.md` provide a strictly read-only DNS inventory workflow:
- fetch Dynu DNS data with GET-only API usage,
- correlate Dynu hostnames with Traefik `Host(...)` labels in Compose sources,
- generate local JSON and markdown artifacts for documentation pipelines.
Dynu write operations are intentionally blocked in this repository stage.
### 7) Terraform Dynu DNS layer
`infrastructure/terraform/dynu/` is the brownfield Terraform DNS mirror/reconciliation root for Dynu domain/record inventory outputs.
At this stage it is primarily documentation-oriented catalog output, ready for one-object-at-a-time imports.
## Output shaping expectations
When adding Terraform outputs for documentation/tooling:
- prefer concise inventory maps/lists,
- include stable identifiers and roles,
- avoid raw giant provider objects where possible,
- include descriptions so future consumers understand intent.
## Limitations today
- Generated host topology document: `docs/generated/host-topology.md` (via `scripts/docs/build_host_topology.sh`).
- No full generated inventory document pipeline is present yet.
- Some Terraform files still include generated boilerplate comments requiring ongoing cleanup.
- Ansible is currently a bootstrap inventory/configuration layer and is not authoritative for full operations yet.
- NixOS operational management is not yet implemented as an Ansible authority in this repo.
These limitations are expected for the current adoption stage.
+10
View File
@@ -0,0 +1,10 @@
# Monitoring and Alerting
Monitoring documentation is generated from static rule files committed in this repository.
- Prometheus rule files are parsed statically.
- Live Prometheus APIs are not queried.
- Future improvements can include Grafana dashboard and alert contact-point summaries.
Generated asset:
+12
View File
@@ -0,0 +1,12 @@
# Networking and Reverse Proxy
Networking and reverse-proxy documentation is generated from Docker Compose metadata.
- Traefik labels are parsed statically from Compose definitions.
- Service-to-network relationships are represented in generated diagrams.
- Sensitive internal values are redacted in public-facing output.
Related generated assets:
- [Traefik Routes](generated/traefik-routes.md)
- [Docker Compose Diagram](diagrams/docker-compose.svg)
+30
View File
@@ -0,0 +1,30 @@
# Operations
## Local docs generation
```bash
chmod +x scripts/docs/*.sh
scripts/docs/generate-all.sh
```
## Inspect generated changes
```bash
git status -- docs/generated docs/diagrams docs/public
```
## GitHub Actions artifacts
Use the **Generate documentation** workflow run and download the `generated-documentation` artifact from the run summary.
## Commit behavior
- Pull requests generate docs and upload artifacts only.
- Pushes to `main` generate docs, upload artifacts, and commit generated docs when changes exist.
- Manual workflow runs can commit generated docs when enabled through workflow input.
## Troubleshooting
- Confirm required tooling (`python3`, `jq`, `graphviz`, `docker compose`) is available.
- Re-run generation after documentation script changes.
- Review `docs/public` output for redaction coverage before sharing.
View File
+59
View File
@@ -0,0 +1,59 @@
# Docker Compose Inventory
Source fingerprint: `232be78ef441`
## Summary
| Item | Count |
|---|---:|
| Services | 28 |
| Networks | 5 |
| Volumes | 0 |
## Services
| Service | Container | Image | Build | Profiles | Networks | Ports | Restart |
|---|---|---|---|---|---|---|---|
| authelia | authelia | authelia/authelia | /home/nixos/docker/core/authelia | core, all, authelia, traefik | traefik | | always |
| crowdsec | crowdsec | | /home/nixos/docker/core/crowdsec | core, all, crowdsec, traefik | traefik | | always |
| docker-socket-proxy | docker-socket-proxy | tecnativa/docker-socket-proxy:latest | | monitoring, all, docker-socket-proxy, core, traefik, prometheus | monitor, traefik | | unless-stopped |
| docker-update-exporter | docker-update-exporter | | /home/nixos/docker/monitoring/docker-exporter | monitoring, all, docker-exporter, prometheus | monitor | | unless-stopped |
| error-pages | error-pages | tarampampam/error-pages:3 | | core, all, error-pages, traefik | traefik | | always |
| gitea | gitea | gitea/gitea:latest | | apps, all, gitea | traefik | | always |
| gitea-runner | gitea-runner | gitea/act_runner:latest | | apps, all, gitea, ci | traefik | | always |
| gotify | gotify | gotify/server:latest | | monitoring, all, gotify | traefik | | always |
| grafana | grafana | grafana/grafana:latest | | monitoring, all, grafana | monitor, traefik | | unless-stopped |
| gramps-redis | gramps-redis | valkey/valkey:8-alpine | | apps, all, gramps | gramps | | always |
| grampsweb | gramps-web | ghcr.io/gramps-project/grampsweb:latest | | apps, all, gramps | gramps, traefik | | always |
| grampsweb_celery | gramps-web-celery | ghcr.io/gramps-project/grampsweb:latest | | apps, all, gramps | gramps | | always |
| influxdb | influxdb | influxdb:2.7 | | monitoring, all, influxdb, prometheus | monitor, traefik | | unless-stopped |
| monitor-kuma | monitor-kuma | louislam/uptime-kuma:2.1.1 | | monitoring, all, uptime-kuma | monitor, traefik | | always |
| mtls-bridge | mtls-bridge | | /home/nixos/docker/monitoring/mtls-bridge | monitoring, all, mtls-bridge | monitor, traefik | | unless-stopped |
| nextcloud-db | nextcloud-db | mariadb:11.4 | | apps, all, nextcloud | nextcloud | | always |
| nextcloud-redis | nextcloud-redis | redis | | apps, all, nextcloud | nextcloud | | always |
| nextcloud-webapp | nextcloud-webapp | | /home/nixos/docker/apps/nextcloud | apps, all, nextcloud | nextcloud, traefik | | always |
| node-exporter | node-exporter | prom/node-exporter:latest | | monitoring, all, node-exporter, prometheus | monitor | | unless-stopped |
| node-red | node-red | | /home/nixos/docker/monitoring/node-red | monitoring, all, node-red | monitor, traefik | | unless-stopped |
| passbolt-db | passbolt-db | mariadb:12 | | apps, all, passbolt | passbolt | | always |
| passbolt-webapp | passbolt-webapp | passbolt/passbolt:latest-ce | | apps, all, passbolt | passbolt, traefik | | always |
| pihole-exporter | pihole-exporter | ekofr/pihole-exporter:latest | | monitoring, all, pihole-exporter, prometheus | monitor | {'mode': 'ingress', 'target': 9617, 'published': '9617', 'protocol': 'tcp'} | unless-stopped |
| portainer | portainer | portainer/portainer-ce:latest | | monitoring, all, portainer | traefik | | unless-stopped |
| prometheus | prometheus | prom/prometheus:latest | | monitoring, all, prometheus | monitor, traefik | | unless-stopped |
| searxng-webapp | searxng-webapp | searxng/searxng | | apps, all, searxng | traefik | | always |
| telegraf | telegraf | telegraf:latest | | monitoring, all, telegraf, prometheus | monitor | | unless-stopped |
| traefik | traefik | traefik:3 | /home/nixos/docker/core | core, all, traefik | traefik | {'mode': 'ingress', 'target': 80, 'published': '80', 'protocol': 'tcp'}, {'mode': 'ingress', 'target': 443, 'published': '443', 'protocol': 'tcp'} | always |
## Networks
| Network | Driver | External |
|---|---|---|
| gramps | | False |
| monitor | | False |
| nextcloud | | False |
| passbolt | | False |
| traefik | bridge | False |
## Volumes
| Volume | External |
|---|---|
+19
View File
@@ -0,0 +1,19 @@
# Infrastructure diagrams
## Physical / virtual topology
This view groups containers by inferred host and service role (edge/proxy/auth, monitoring, automation, apps, and supporting storage/services).
<div class="diagram-wrap">
<img src="physical-topology.svg" alt="Physical topology">
</div>
## Docker, Traefik and Dynu routing
This view shows sanitised public DNS names flowing to Traefik, then to exposed Docker services, with backend Docker network membership shown as secondary context.
_Diagrams are generated from Compose data and Traefik labels._
<div class="diagram-wrap">
<img src="docker-traefik-dynu.svg" alt="Docker Traefik Dynu">
</div>
+439
View File
@@ -0,0 +1,439 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: Compose Pages: 1 -->
<svg width="334pt" height="1502pt"
viewBox="0.00 0.00 334.49 1502.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1498)">
<title>Compose</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1498 330.49,-1498 330.49,4 -4,4"/>
<!-- svc:authelia -->
<g id="node1" class="node">
<title>svc:authelia</title>
<polygon fill="#dfefff" stroke="black" points="126,-738 54,-738 54,-702 126,-702 126,-738"/>
<text text-anchor="middle" x="90" y="-716.3" font-family="Helvetica,sans-Serif" font-size="14.00">authelia</text>
</g>
<!-- net:traefik -->
<g id="node33" class="node">
<title>net:traefik</title>
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-774" rx="40.09" ry="18"/>
<text text-anchor="middle" x="271.25" y="-770.3" font-family="Helvetica,sans-Serif" font-size="14.00">traefik</text>
</g>
<!-- svc:authelia&#45;&gt;net:traefik -->
<g id="edge1" class="edge">
<title>svc:authelia&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M126.41,-730.67C155.5,-739.43 196.8,-751.87 227.69,-761.18"/>
<polygon fill="black" stroke="black" points="226.74,-764.55 237.32,-764.08 228.76,-757.85 226.74,-764.55"/>
</g>
<!-- svc:crowdsec -->
<g id="node2" class="node">
<title>svc:crowdsec</title>
<polygon fill="#dfefff" stroke="black" points="130.5,-684 49.5,-684 49.5,-648 130.5,-648 130.5,-684"/>
<text text-anchor="middle" x="90" y="-662.3" font-family="Helvetica,sans-Serif" font-size="14.00">crowdsec</text>
</g>
<!-- svc:crowdsec&#45;&gt;net:traefik -->
<g id="edge2" class="edge">
<title>svc:crowdsec&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M130.61,-674.06C146.59,-678.3 164.81,-684.44 180,-693 206.25,-707.78 231.35,-731.35 248.38,-749.26"/>
<polygon fill="black" stroke="black" points="246.24,-752.1 255.62,-757.03 251.37,-747.33 246.24,-752.1"/>
</g>
<!-- svc:docker&#45;socket&#45;proxy -->
<g id="node3" class="node">
<title>svc:docker&#45;socket&#45;proxy</title>
<polygon fill="#dfefff" stroke="black" points="167.5,-954 12.5,-954 12.5,-918 167.5,-918 167.5,-954"/>
<text text-anchor="middle" x="90" y="-932.3" font-family="Helvetica,sans-Serif" font-size="14.00">docker&#45;socket&#45;proxy</text>
</g>
<!-- net:monitor -->
<g id="node30" class="node">
<title>net:monitor</title>
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-1206" rx="46.29" ry="18"/>
<text text-anchor="middle" x="271.25" y="-1202.3" font-family="Helvetica,sans-Serif" font-size="14.00">monitor</text>
</g>
<!-- svc:docker&#45;socket&#45;proxy&#45;&gt;net:monitor -->
<g id="edge3" class="edge">
<title>svc:docker&#45;socket&#45;proxy&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M167.53,-953.71C172.02,-956.38 176.24,-959.46 180,-963 242.3,-1021.6 261.83,-1127.12 267.77,-1177.6"/>
<polygon fill="black" stroke="black" points="264.31,-1178.21 268.86,-1187.78 271.27,-1177.46 264.31,-1178.21"/>
</g>
<!-- svc:docker&#45;socket&#45;proxy&#45;&gt;net:traefik -->
<g id="edge4" class="edge">
<title>svc:docker&#45;socket&#45;proxy&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M165.31,-917.9C170.5,-915.32 175.46,-912.37 180,-909 218.01,-880.82 244.97,-831.56 259.03,-800.98"/>
<polygon fill="black" stroke="black" points="262.27,-802.31 263.14,-791.76 255.87,-799.46 262.27,-802.31"/>
</g>
<!-- svc:docker&#45;update&#45;exporter -->
<g id="node4" class="node">
<title>svc:docker&#45;update&#45;exporter</title>
<polygon fill="#dfefff" stroke="black" points="180,-1494 0,-1494 0,-1458 180,-1458 180,-1494"/>
<text text-anchor="middle" x="90" y="-1472.3" font-family="Helvetica,sans-Serif" font-size="14.00">docker&#45;update&#45;exporter</text>
</g>
<!-- svc:docker&#45;update&#45;exporter&#45;&gt;net:monitor -->
<g id="edge5" class="edge">
<title>svc:docker&#45;update&#45;exporter&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M168.37,-1457.79C172.55,-1455.23 176.47,-1452.32 180,-1449 242.3,-1390.4 261.83,-1284.88 267.77,-1234.4"/>
<polygon fill="black" stroke="black" points="271.27,-1234.54 268.86,-1224.22 264.31,-1233.79 271.27,-1234.54"/>
</g>
<!-- svc:error&#45;pages -->
<g id="node5" class="node">
<title>svc:error&#45;pages</title>
<polygon fill="#dfefff" stroke="black" points="137.5,-630 42.5,-630 42.5,-594 137.5,-594 137.5,-630"/>
<text text-anchor="middle" x="90" y="-608.3" font-family="Helvetica,sans-Serif" font-size="14.00">error&#45;pages</text>
</g>
<!-- svc:error&#45;pages&#45;&gt;net:traefik -->
<g id="edge6" class="edge">
<title>svc:error&#45;pages&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M137.71,-619.76C152.25,-623.79 167.68,-629.86 180,-639 218.01,-667.18 244.97,-716.44 259.03,-747.02"/>
<polygon fill="black" stroke="black" points="255.87,-748.54 263.14,-756.24 262.27,-745.69 255.87,-748.54"/>
</g>
<!-- svc:gitea -->
<g id="node6" class="node">
<title>svc:gitea</title>
<polygon fill="#dfefff" stroke="black" points="117,-576 63,-576 63,-540 117,-540 117,-576"/>
<text text-anchor="middle" x="90" y="-554.3" font-family="Helvetica,sans-Serif" font-size="14.00">gitea</text>
</g>
<!-- svc:gitea&#45;&gt;net:traefik -->
<g id="edge7" class="edge">
<title>svc:gitea&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M117.07,-560.28C136.41,-563.2 162.37,-569.85 180,-585 229.98,-627.95 254.42,-704.8 264.44,-746.04"/>
<polygon fill="black" stroke="black" points="261.07,-747 266.73,-755.95 267.88,-745.42 261.07,-747"/>
</g>
<!-- svc:gitea&#45;runner -->
<g id="node7" class="node">
<title>svc:gitea&#45;runner</title>
<polygon fill="#dfefff" stroke="black" points="142,-522 38,-522 38,-486 142,-486 142,-522"/>
<text text-anchor="middle" x="90" y="-500.3" font-family="Helvetica,sans-Serif" font-size="14.00">gitea&#45;runner</text>
</g>
<!-- svc:gitea&#45;runner&#45;&gt;net:traefik -->
<g id="edge8" class="edge">
<title>svc:gitea&#45;runner&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M142.31,-511.04C155.9,-515.03 169.65,-521.26 180,-531 242.3,-589.6 261.83,-695.12 267.77,-745.6"/>
<polygon fill="black" stroke="black" points="264.31,-746.21 268.86,-755.78 271.27,-745.46 264.31,-746.21"/>
</g>
<!-- svc:gotify -->
<g id="node8" class="node">
<title>svc:gotify</title>
<polygon fill="#dfefff" stroke="black" points="118,-468 62,-468 62,-432 118,-432 118,-468"/>
<text text-anchor="middle" x="90" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">gotify</text>
</g>
<!-- svc:gotify&#45;&gt;net:traefik -->
<g id="edge9" class="edge">
<title>svc:gotify&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M118.17,-451.6C137.81,-454.16 163.67,-460.68 180,-477 254.99,-551.96 268.02,-687.13 270.03,-745.69"/>
<polygon fill="black" stroke="black" points="266.53,-745.79 270.29,-755.69 273.53,-745.61 266.53,-745.79"/>
</g>
<!-- svc:grafana -->
<g id="node9" class="node">
<title>svc:grafana</title>
<polygon fill="#dfefff" stroke="black" points="125.5,-1278 54.5,-1278 54.5,-1242 125.5,-1242 125.5,-1278"/>
<text text-anchor="middle" x="90" y="-1256.3" font-family="Helvetica,sans-Serif" font-size="14.00">grafana</text>
</g>
<!-- svc:grafana&#45;&gt;net:monitor -->
<g id="edge10" class="edge">
<title>svc:grafana&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M125.56,-1249.59C153.74,-1241.1 193.78,-1229.04 224.6,-1219.75"/>
<polygon fill="black" stroke="black" points="225.69,-1223.08 234.26,-1216.84 223.67,-1216.38 225.69,-1223.08"/>
</g>
<!-- svc:grafana&#45;&gt;net:traefik -->
<g id="edge11" class="edge">
<title>svc:grafana&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M125.54,-1257.93C144.23,-1254.97 166.25,-1248.21 180,-1233 238.17,-1168.67 262.1,-892.17 268.43,-802.32"/>
<polygon fill="black" stroke="black" points="271.93,-802.3 269.13,-792.08 264.95,-801.82 271.93,-802.3"/>
</g>
<!-- svc:gramps&#45;redis -->
<g id="node10" class="node">
<title>svc:gramps&#45;redis</title>
<polygon fill="#dfefff" stroke="black" points="144.5,-360 35.5,-360 35.5,-324 144.5,-324 144.5,-360"/>
<text text-anchor="middle" x="90" y="-338.3" font-family="Helvetica,sans-Serif" font-size="14.00">gramps&#45;redis</text>
</g>
<!-- net:gramps -->
<g id="node29" class="node">
<title>net:gramps</title>
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-342" rx="45.49" ry="18"/>
<text text-anchor="middle" x="271.25" y="-338.3" font-family="Helvetica,sans-Serif" font-size="14.00">gramps</text>
</g>
<!-- svc:gramps&#45;redis&#45;&gt;net:gramps -->
<g id="edge12" class="edge">
<title>svc:gramps&#45;redis&#45;&gt;net:gramps</title>
<path fill="none" stroke="black" d="M144.78,-342C167.14,-342 193.05,-342 215.51,-342"/>
<polygon fill="black" stroke="black" points="215.56,-345.5 225.56,-342 215.56,-338.5 215.56,-345.5"/>
</g>
<!-- svc:grampsweb -->
<g id="node11" class="node">
<title>svc:grampsweb</title>
<polygon fill="#dfefff" stroke="black" points="139,-414 41,-414 41,-378 139,-378 139,-414"/>
<text text-anchor="middle" x="90" y="-392.3" font-family="Helvetica,sans-Serif" font-size="14.00">grampsweb</text>
</g>
<!-- svc:grampsweb&#45;&gt;net:gramps -->
<g id="edge13" class="edge">
<title>svc:grampsweb&#45;&gt;net:gramps</title>
<path fill="none" stroke="black" d="M139.03,-381.53C165.7,-373.5 198.68,-363.56 224.9,-355.66"/>
<polygon fill="black" stroke="black" points="226.03,-358.98 234.59,-352.74 224.01,-352.27 226.03,-358.98"/>
</g>
<!-- svc:grampsweb&#45;&gt;net:traefik -->
<g id="edge14" class="edge">
<title>svc:grampsweb&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M139.27,-401.34C154.08,-405.21 169.28,-411.81 180,-423 225.12,-470.09 256.34,-671.08 266.59,-745.84"/>
<polygon fill="black" stroke="black" points="263.14,-746.48 267.95,-755.92 270.08,-745.55 263.14,-746.48"/>
</g>
<!-- svc:grampsweb_celery -->
<g id="node12" class="node">
<title>svc:grampsweb_celery</title>
<polygon fill="#dfefff" stroke="black" points="163.5,-306 16.5,-306 16.5,-270 163.5,-270 163.5,-306"/>
<text text-anchor="middle" x="90" y="-284.3" font-family="Helvetica,sans-Serif" font-size="14.00">grampsweb_celery</text>
</g>
<!-- svc:grampsweb_celery&#45;&gt;net:gramps -->
<g id="edge15" class="edge">
<title>svc:grampsweb_celery&#45;&gt;net:gramps</title>
<path fill="none" stroke="black" d="M151.18,-306.13C175.28,-313.39 202.57,-321.61 224.94,-328.35"/>
<polygon fill="black" stroke="black" points="223.94,-331.71 234.52,-331.24 225.96,-325 223.94,-331.71"/>
</g>
<!-- svc:influxdb -->
<g id="node13" class="node">
<title>svc:influxdb</title>
<polygon fill="#dfefff" stroke="black" points="127,-1224 53,-1224 53,-1188 127,-1188 127,-1224"/>
<text text-anchor="middle" x="90" y="-1202.3" font-family="Helvetica,sans-Serif" font-size="14.00">influxdb</text>
</g>
<!-- svc:influxdb&#45;&gt;net:monitor -->
<g id="edge16" class="edge">
<title>svc:influxdb&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M127.27,-1206C152.33,-1206 186.13,-1206 214.57,-1206"/>
<polygon fill="black" stroke="black" points="214.79,-1209.5 224.79,-1206 214.79,-1202.5 214.79,-1209.5"/>
</g>
<!-- svc:influxdb&#45;&gt;net:traefik -->
<g id="edge17" class="edge">
<title>svc:influxdb&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M127.17,-1203.5C145.42,-1200.36 166.5,-1193.56 180,-1179 231.57,-1123.4 259.36,-885.3 267.6,-802.51"/>
<polygon fill="black" stroke="black" points="271.1,-802.62 268.59,-792.33 264.14,-801.94 271.1,-802.62"/>
</g>
<!-- svc:monitor&#45;kuma -->
<g id="node14" class="node">
<title>svc:monitor&#45;kuma</title>
<polygon fill="#dfefff" stroke="black" points="146.5,-1170 33.5,-1170 33.5,-1134 146.5,-1134 146.5,-1170"/>
<text text-anchor="middle" x="90" y="-1148.3" font-family="Helvetica,sans-Serif" font-size="14.00">monitor&#45;kuma</text>
</g>
<!-- svc:monitor&#45;kuma&#45;&gt;net:monitor -->
<g id="edge18" class="edge">
<title>svc:monitor&#45;kuma&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M146.73,-1168.79C171.73,-1176.32 200.85,-1185.1 224.54,-1192.23"/>
<polygon fill="black" stroke="black" points="223.75,-1195.65 234.33,-1195.18 225.77,-1188.94 223.75,-1195.65"/>
</g>
<!-- svc:monitor&#45;kuma&#45;&gt;net:traefik -->
<g id="edge19" class="edge">
<title>svc:monitor&#45;kuma&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M146.69,-1144.49C159.02,-1140.46 171.06,-1134.33 180,-1125 225.12,-1077.91 256.34,-876.92 266.59,-802.16"/>
<polygon fill="black" stroke="black" points="270.08,-802.45 267.95,-792.08 263.14,-801.52 270.08,-802.45"/>
</g>
<!-- svc:mtls&#45;bridge -->
<g id="node15" class="node">
<title>svc:mtls&#45;bridge</title>
<polygon fill="#dfefff" stroke="black" points="138.5,-1116 41.5,-1116 41.5,-1080 138.5,-1080 138.5,-1116"/>
<text text-anchor="middle" x="90" y="-1094.3" font-family="Helvetica,sans-Serif" font-size="14.00">mtls&#45;bridge</text>
</g>
<!-- svc:mtls&#45;bridge&#45;&gt;net:monitor -->
<g id="edge20" class="edge">
<title>svc:mtls&#45;bridge&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M138.76,-1108.34C152.58,-1112.41 167.34,-1117.87 180,-1125 206.25,-1139.78 231.35,-1163.35 248.38,-1181.26"/>
<polygon fill="black" stroke="black" points="246.24,-1184.1 255.62,-1189.03 251.37,-1179.33 246.24,-1184.1"/>
</g>
<!-- svc:mtls&#45;bridge&#45;&gt;net:traefik -->
<g id="edge21" class="edge">
<title>svc:mtls&#45;bridge&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M138.53,-1092.51C153.46,-1088.63 168.92,-1082.07 180,-1071 254.99,-996.04 268.02,-860.87 270.03,-802.31"/>
<polygon fill="black" stroke="black" points="273.53,-802.39 270.29,-792.31 266.53,-802.21 273.53,-802.39"/>
</g>
<!-- svc:nextcloud&#45;db -->
<g id="node16" class="node">
<title>svc:nextcloud&#45;db</title>
<polygon fill="#dfefff" stroke="black" points="144,-90 36,-90 36,-54 144,-54 144,-90"/>
<text text-anchor="middle" x="90" y="-68.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud&#45;db</text>
</g>
<!-- net:nextcloud -->
<g id="node31" class="node">
<title>net:nextcloud</title>
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-180" rx="55.49" ry="18"/>
<text text-anchor="middle" x="271.25" y="-176.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud</text>
</g>
<!-- svc:nextcloud&#45;db&#45;&gt;net:nextcloud -->
<g id="edge22" class="edge">
<title>svc:nextcloud&#45;db&#45;&gt;net:nextcloud</title>
<path fill="none" stroke="black" d="M144.33,-84.04C156.48,-87.9 169.03,-92.82 180,-99 206.09,-113.7 231.04,-137.06 248.07,-154.93"/>
<polygon fill="black" stroke="black" points="245.93,-157.77 255.31,-162.7 251.06,-153 245.93,-157.77"/>
</g>
<!-- svc:nextcloud&#45;redis -->
<g id="node17" class="node">
<title>svc:nextcloud&#45;redis</title>
<polygon fill="#dfefff" stroke="black" points="152,-198 28,-198 28,-162 152,-162 152,-198"/>
<text text-anchor="middle" x="90" y="-176.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud&#45;redis</text>
</g>
<!-- svc:nextcloud&#45;redis&#45;&gt;net:nextcloud -->
<g id="edge23" class="edge">
<title>svc:nextcloud&#45;redis&#45;&gt;net:nextcloud</title>
<path fill="none" stroke="black" d="M152.18,-180C169.48,-180 188.35,-180 205.83,-180"/>
<polygon fill="black" stroke="black" points="205.94,-183.5 215.94,-180 205.94,-176.5 205.94,-183.5"/>
</g>
<!-- svc:nextcloud&#45;webapp -->
<g id="node18" class="node">
<title>svc:nextcloud&#45;webapp</title>
<polygon fill="#dfefff" stroke="black" points="162.5,-252 17.5,-252 17.5,-216 162.5,-216 162.5,-252"/>
<text text-anchor="middle" x="90" y="-230.3" font-family="Helvetica,sans-Serif" font-size="14.00">nextcloud&#45;webapp</text>
</g>
<!-- svc:nextcloud&#45;webapp&#45;&gt;net:nextcloud -->
<g id="edge24" class="edge">
<title>svc:nextcloud&#45;webapp&#45;&gt;net:nextcloud</title>
<path fill="none" stroke="black" d="M151.18,-215.87C173.57,-209.12 198.72,-201.55 220.11,-195.1"/>
<polygon fill="black" stroke="black" points="221.4,-198.37 229.97,-192.13 219.38,-191.67 221.4,-198.37"/>
</g>
<!-- svc:nextcloud&#45;webapp&#45;&gt;net:traefik -->
<g id="edge25" class="edge">
<title>svc:nextcloud&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M162.89,-247.71C169.31,-251.2 175.18,-255.56 180,-261 212.66,-297.85 255.07,-643.36 267,-745.62"/>
<polygon fill="black" stroke="black" points="263.55,-746.27 268.18,-755.8 270.51,-745.47 263.55,-746.27"/>
</g>
<!-- svc:node&#45;exporter -->
<g id="node19" class="node">
<title>svc:node&#45;exporter</title>
<polygon fill="#dfefff" stroke="black" points="148,-1440 32,-1440 32,-1404 148,-1404 148,-1440"/>
<text text-anchor="middle" x="90" y="-1418.3" font-family="Helvetica,sans-Serif" font-size="14.00">node&#45;exporter</text>
</g>
<!-- svc:node&#45;exporter&#45;&gt;net:monitor -->
<g id="edge26" class="edge">
<title>svc:node&#45;exporter&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M148.25,-1412.27C159.68,-1408.33 170.94,-1402.79 180,-1395 229.98,-1352.05 254.42,-1275.2 264.44,-1233.96"/>
<polygon fill="black" stroke="black" points="267.88,-1234.58 266.73,-1224.05 261.07,-1233 267.88,-1234.58"/>
</g>
<!-- svc:node&#45;red -->
<g id="node20" class="node">
<title>svc:node&#45;red</title>
<polygon fill="#dfefff" stroke="black" points="129.5,-1062 50.5,-1062 50.5,-1026 129.5,-1026 129.5,-1062"/>
<text text-anchor="middle" x="90" y="-1040.3" font-family="Helvetica,sans-Serif" font-size="14.00">node&#45;red</text>
</g>
<!-- svc:node&#45;red&#45;&gt;net:monitor -->
<g id="edge27" class="edge">
<title>svc:node&#45;red&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M129.57,-1049.69C146.29,-1053.6 165.34,-1060.13 180,-1071 218.01,-1099.18 244.97,-1148.44 259.03,-1179.02"/>
<polygon fill="black" stroke="black" points="255.87,-1180.54 263.14,-1188.24 262.27,-1177.69 255.87,-1180.54"/>
</g>
<!-- svc:node&#45;red&#45;&gt;net:traefik -->
<g id="edge28" class="edge">
<title>svc:node&#45;red&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M129.95,-1040.03C147.14,-1036.46 166.48,-1029.72 180,-1017 242.3,-958.4 261.83,-852.88 267.77,-802.4"/>
<polygon fill="black" stroke="black" points="271.27,-802.54 268.86,-792.22 264.31,-801.79 271.27,-802.54"/>
</g>
<!-- svc:passbolt&#45;db -->
<g id="node21" class="node">
<title>svc:passbolt&#45;db</title>
<polygon fill="#dfefff" stroke="black" points="139,-36 41,-36 41,0 139,0 139,-36"/>
<text text-anchor="middle" x="90" y="-14.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt&#45;db</text>
</g>
<!-- net:passbolt -->
<g id="node32" class="node">
<title>net:passbolt</title>
<ellipse fill="#f4f4f4" stroke="black" cx="271.25" cy="-72" rx="48.99" ry="18"/>
<text text-anchor="middle" x="271.25" y="-68.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt</text>
</g>
<!-- svc:passbolt&#45;db&#45;&gt;net:passbolt -->
<g id="edge29" class="edge">
<title>svc:passbolt&#45;db&#45;&gt;net:passbolt</title>
<path fill="none" stroke="black" d="M139.03,-32.47C165.15,-40.34 197.32,-50.03 223.27,-57.85"/>
<polygon fill="black" stroke="black" points="222.3,-61.21 232.88,-60.74 224.32,-54.51 222.3,-61.21"/>
</g>
<!-- svc:passbolt&#45;webapp -->
<g id="node22" class="node">
<title>svc:passbolt&#45;webapp</title>
<polygon fill="#dfefff" stroke="black" points="157.5,-144 22.5,-144 22.5,-108 157.5,-108 157.5,-144"/>
<text text-anchor="middle" x="90" y="-122.3" font-family="Helvetica,sans-Serif" font-size="14.00">passbolt&#45;webapp</text>
</g>
<!-- svc:passbolt&#45;webapp&#45;&gt;net:passbolt -->
<g id="edge30" class="edge">
<title>svc:passbolt&#45;webapp&#45;&gt;net:passbolt</title>
<path fill="none" stroke="black" d="M151.18,-107.87C174.64,-100.8 201.13,-92.82 223.16,-86.19"/>
<polygon fill="black" stroke="black" points="224.37,-89.48 232.94,-83.24 222.35,-82.77 224.37,-89.48"/>
</g>
<!-- svc:passbolt&#45;webapp&#45;&gt;net:traefik -->
<g id="edge31" class="edge">
<title>svc:passbolt&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M157.85,-136.93C166.26,-140.81 174,-146.02 180,-153 189.88,-164.49 250.79,-625.33 266.53,-745.56"/>
<polygon fill="black" stroke="black" points="263.1,-746.33 267.87,-755.8 270.04,-745.43 263.1,-746.33"/>
</g>
<!-- svc:pihole&#45;exporter -->
<g id="node23" class="node">
<title>svc:pihole&#45;exporter</title>
<polygon fill="#dfefff" stroke="black" points="151.5,-1386 28.5,-1386 28.5,-1350 151.5,-1350 151.5,-1386"/>
<text text-anchor="middle" x="90" y="-1364.3" font-family="Helvetica,sans-Serif" font-size="14.00">pihole&#45;exporter</text>
</g>
<!-- svc:pihole&#45;exporter&#45;&gt;net:monitor -->
<g id="edge32" class="edge">
<title>svc:pihole&#45;exporter&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M151.62,-1355.78C161.68,-1352.07 171.57,-1347.25 180,-1341 218.01,-1312.82 244.97,-1263.56 259.03,-1232.98"/>
<polygon fill="black" stroke="black" points="262.27,-1234.31 263.14,-1223.76 255.87,-1231.46 262.27,-1234.31"/>
</g>
<!-- svc:portainer -->
<g id="node24" class="node">
<title>svc:portainer</title>
<polygon fill="#dfefff" stroke="black" points="130,-900 50,-900 50,-864 130,-864 130,-900"/>
<text text-anchor="middle" x="90" y="-878.3" font-family="Helvetica,sans-Serif" font-size="14.00">portainer</text>
</g>
<!-- svc:portainer&#45;&gt;net:traefik -->
<g id="edge33" class="edge">
<title>svc:portainer&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M130.17,-874.06C146.25,-869.82 164.67,-863.64 180,-855 206.25,-840.22 231.35,-816.65 248.38,-798.74"/>
<polygon fill="black" stroke="black" points="251.37,-800.67 255.62,-790.97 246.24,-795.9 251.37,-800.67"/>
</g>
<!-- svc:prometheus -->
<g id="node25" class="node">
<title>svc:prometheus</title>
<polygon fill="#dfefff" stroke="black" points="140,-1008 40,-1008 40,-972 140,-972 140,-1008"/>
<text text-anchor="middle" x="90" y="-986.3" font-family="Helvetica,sans-Serif" font-size="14.00">prometheus</text>
</g>
<!-- svc:prometheus&#45;&gt;net:monitor -->
<g id="edge34" class="edge">
<title>svc:prometheus&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M140.37,-997.26C154.39,-1001.24 168.86,-1007.42 180,-1017 229.98,-1059.95 254.42,-1136.8 264.44,-1178.04"/>
<polygon fill="black" stroke="black" points="261.07,-1179 266.73,-1187.95 267.88,-1177.42 261.07,-1179"/>
</g>
<!-- svc:prometheus&#45;&gt;net:traefik -->
<g id="edge35" class="edge">
<title>svc:prometheus&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M140.37,-982.74C154.39,-978.76 168.86,-972.58 180,-963 229.98,-920.05 254.42,-843.2 264.44,-801.96"/>
<polygon fill="black" stroke="black" points="267.88,-802.58 266.73,-792.05 261.07,-801 267.88,-802.58"/>
</g>
<!-- svc:searxng&#45;webapp -->
<g id="node26" class="node">
<title>svc:searxng&#45;webapp</title>
<polygon fill="#dfefff" stroke="black" points="156,-846 24,-846 24,-810 156,-810 156,-846"/>
<text text-anchor="middle" x="90" y="-824.3" font-family="Helvetica,sans-Serif" font-size="14.00">searxng&#45;webapp</text>
</g>
<!-- svc:searxng&#45;webapp&#45;&gt;net:traefik -->
<g id="edge36" class="edge">
<title>svc:searxng&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M151.18,-809.87C176.26,-802.32 204.79,-793.72 227.63,-786.84"/>
<polygon fill="black" stroke="black" points="228.81,-790.14 237.37,-783.9 226.79,-783.44 228.81,-790.14"/>
</g>
<!-- svc:telegraf -->
<g id="node27" class="node">
<title>svc:telegraf</title>
<polygon fill="#dfefff" stroke="black" points="125.5,-1332 54.5,-1332 54.5,-1296 125.5,-1296 125.5,-1332"/>
<text text-anchor="middle" x="90" y="-1310.3" font-family="Helvetica,sans-Serif" font-size="14.00">telegraf</text>
</g>
<!-- svc:telegraf&#45;&gt;net:monitor -->
<g id="edge37" class="edge">
<title>svc:telegraf&#45;&gt;net:monitor</title>
<path fill="none" stroke="black" d="M125.8,-1307.18C142.85,-1302.93 163.26,-1296.43 180,-1287 206.25,-1272.22 231.35,-1248.65 248.38,-1230.74"/>
<polygon fill="black" stroke="black" points="251.37,-1232.67 255.62,-1222.97 246.24,-1227.9 251.37,-1232.67"/>
</g>
<!-- svc:traefik -->
<g id="node28" class="node">
<title>svc:traefik</title>
<polygon fill="#dfefff" stroke="black" points="121,-792 59,-792 59,-756 121,-756 121,-792"/>
<text text-anchor="middle" x="90" y="-770.3" font-family="Helvetica,sans-Serif" font-size="14.00">traefik</text>
</g>
<!-- svc:traefik&#45;&gt;net:traefik -->
<g id="edge38" class="edge">
<title>svc:traefik&#45;&gt;net:traefik</title>
<path fill="none" stroke="black" d="M121,-774C148.16,-774 188.69,-774 220.66,-774"/>
<polygon fill="black" stroke="black" points="220.71,-777.5 230.71,-774 220.71,-770.5 220.71,-777.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

+611
View File
@@ -0,0 +1,611 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: DockerTraefikDynu Pages: 1 -->
<svg width="1113pt" height="1536pt"
viewBox="0.00 0.00 1113.39 1536.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1532)">
<title>DockerTraefikDynu</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1532 1109.39,-1532 1109.39,4 -4,4"/>
<g id="clust2" class="cluster">
<title>cluster_networks</title>
<path fill="none" stroke="#d1d5db" stroke-dasharray="5,2" d="M922,-650C922,-650 1093.39,-650 1093.39,-650 1099.39,-650 1105.39,-656 1105.39,-662 1105.39,-662 1105.39,-1117 1105.39,-1117 1105.39,-1123 1099.39,-1129 1093.39,-1129 1093.39,-1129 922,-1129 922,-1129 916,-1129 910,-1123 910,-1117 910,-1117 910,-662 910,-662 910,-656 916,-650 922,-650"/>
<text text-anchor="middle" x="1007.69" y="-1113.8" font-family="Helvetica,sans-Serif" font-size="14.00">Docker backend networks</text>
</g>
<!-- dynu -->
<g id="node1" class="node">
<title>dynu</title>
<path fill="#fde68a" stroke="black" d="M374,-789C374,-789 282,-789 282,-789 276,-789 270,-783 270,-777 270,-777 270,-765 270,-765 270,-759 276,-753 282,-753 282,-753 374,-753 374,-753 380,-753 386,-759 386,-765 386,-765 386,-777 386,-777 386,-783 380,-789 374,-789"/>
<text text-anchor="middle" x="328" y="-768.2" font-family="Helvetica,sans-Serif" font-size="11.00">Dynu / Public DNS</text>
</g>
<!-- svc:traefik -->
<g id="node2" class="node">
<title>svc:traefik</title>
<path fill="#dcfce7" stroke="black" d="M559,-789C559,-789 513,-789 513,-789 507,-789 501,-783 501,-777 501,-777 501,-765 501,-765 501,-759 507,-753 513,-753 513,-753 559,-753 559,-753 565,-753 571,-759 571,-765 571,-765 571,-777 571,-777 571,-783 565,-789 559,-789"/>
<text text-anchor="middle" x="536" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik</text>
<text text-anchor="middle" x="536" y="-762.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
</g>
<!-- dynu&#45;&gt;svc:traefik -->
<g id="edge1" class="edge">
<title>dynu&#45;&gt;svc:traefik</title>
<path fill="none" stroke="#334155" stroke-width="1.6" d="M386.11,-771C419.13,-771 460.06,-771 490.64,-771"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.6" points="491,-774.5 501,-771 491,-767.5 491,-774.5"/>
</g>
<!-- svc:traefik&#45;&gt;svc:traefik -->
<g id="edge30" class="edge">
<title>svc:traefik&#45;&gt;svc:traefik</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M511.86,-789.35C485.91,-817.82 493.95,-854 536,-854 574.1,-854 584.29,-824.29 566.55,-797.52"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="569.07,-795.06 560.14,-789.35 563.55,-799.38 569.07,-795.06"/>
</g>
<!-- svc:authelia -->
<g id="node3" class="node">
<title>svc:authelia</title>
<path fill="#dcfce7" stroke="black" d="M763,-789C763,-789 726,-789 726,-789 720,-789 714,-783 714,-777 714,-777 714,-765 714,-765 714,-759 720,-753 726,-753 726,-753 763,-753 763,-753 769,-753 775,-759 775,-765 775,-765 775,-777 775,-777 775,-783 769,-789 763,-789"/>
<text text-anchor="middle" x="744.5" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">authelia</text>
<text text-anchor="middle" x="744.5" y="-762.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:authelia -->
<g id="edge2" class="edge">
<title>svc:traefik&#45;&gt;svc:authelia</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-771C607.43,-771 664.91,-771 703.39,-771"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="703.71,-774.5 713.71,-771 703.71,-767.5 703.71,-774.5"/>
</g>
<!-- svc:gitea -->
<g id="node5" class="node">
<title>svc:gitea</title>
<path fill="#dcfce7" stroke="black" d="M759.5,-688C759.5,-688 729.5,-688 729.5,-688 723.5,-688 717.5,-682 717.5,-676 717.5,-676 717.5,-656 717.5,-656 717.5,-650 723.5,-644 729.5,-644 729.5,-644 759.5,-644 759.5,-644 765.5,-644 771.5,-650 771.5,-656 771.5,-656 771.5,-676 771.5,-676 771.5,-682 765.5,-688 759.5,-688"/>
<text text-anchor="middle" x="744.5" y="-675.2" font-family="Helvetica,sans-Serif" font-size="11.00">gitea</text>
<text text-anchor="middle" x="744.5" y="-663.2" font-family="Helvetica,sans-Serif" font-size="11.00">:3000</text>
<text text-anchor="middle" x="744.5" y="-651.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:gitea -->
<g id="edge4" class="edge">
<title>svc:traefik&#45;&gt;svc:gitea</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-753.66C608.91,-734.44 669.61,-703.57 707.98,-684.06"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="709.89,-687.01 717.22,-679.36 706.72,-680.77 709.89,-687.01"/>
</g>
<!-- svc:gotify -->
<g id="node7" class="node">
<title>svc:gotify</title>
<path fill="#dcfce7" stroke="black" d="M759.5,-579C759.5,-579 729.5,-579 729.5,-579 723.5,-579 717.5,-573 717.5,-567 717.5,-567 717.5,-555 717.5,-555 717.5,-549 723.5,-543 729.5,-543 729.5,-543 759.5,-543 759.5,-543 765.5,-543 771.5,-549 771.5,-555 771.5,-555 771.5,-567 771.5,-567 771.5,-573 765.5,-579 759.5,-579"/>
<text text-anchor="middle" x="744.5" y="-564.2" font-family="Helvetica,sans-Serif" font-size="11.00">gotify</text>
<text text-anchor="middle" x="744.5" y="-552.2" font-family="Helvetica,sans-Serif" font-size="11.00">:80</text>
</g>
<!-- svc:traefik&#45;&gt;svc:gotify -->
<g id="edge6" class="edge">
<title>svc:traefik&#45;&gt;svc:gotify</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M553.8,-752.96C592.77,-711.11 686,-611 686,-611 686,-611 700.43,-598.45 714.83,-585.93"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-588.25 722.74,-579.05 712.9,-582.97 717.49,-588.25"/>
</g>
<!-- svc:grafana -->
<g id="node9" class="node">
<title>svc:grafana</title>
<path fill="#dcfce7" stroke="black" d="M762,-999C762,-999 727,-999 727,-999 721,-999 715,-993 715,-987 715,-987 715,-975 715,-975 715,-969 721,-963 727,-963 727,-963 762,-963 762,-963 768,-963 774,-969 774,-975 774,-975 774,-987 774,-987 774,-993 768,-999 762,-999"/>
<text text-anchor="middle" x="744.5" y="-984.2" font-family="Helvetica,sans-Serif" font-size="11.00">grafana</text>
<text text-anchor="middle" x="744.5" y="-972.2" font-family="Helvetica,sans-Serif" font-size="11.00">:3000</text>
</g>
<!-- svc:traefik&#45;&gt;svc:grafana -->
<g id="edge8" class="edge">
<title>svc:traefik&#45;&gt;svc:grafana</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M553.8,-789.04C592.77,-830.89 686,-931 686,-931 686,-931 700.43,-943.55 714.83,-956.07"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-959.03 722.74,-962.95 717.49,-953.75 712.9,-959.03"/>
</g>
<!-- svc:grampsweb -->
<g id="node11" class="node">
<title>svc:grampsweb</title>
<path fill="#dcfce7" stroke="black" d="M772.5,-1528C772.5,-1528 716.5,-1528 716.5,-1528 710.5,-1528 704.5,-1522 704.5,-1516 704.5,-1516 704.5,-1504 704.5,-1504 704.5,-1498 710.5,-1492 716.5,-1492 716.5,-1492 772.5,-1492 772.5,-1492 778.5,-1492 784.5,-1498 784.5,-1504 784.5,-1504 784.5,-1516 784.5,-1516 784.5,-1522 778.5,-1528 772.5,-1528"/>
<text text-anchor="middle" x="744.5" y="-1507.2" font-family="Helvetica,sans-Serif" font-size="11.00">grampsweb</text>
</g>
<!-- svc:traefik&#45;&gt;svc:grampsweb -->
<g id="edge10" class="edge">
<title>svc:traefik&#45;&gt;svc:grampsweb</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M540.91,-789.07C564.42,-897.78 686,-1460 686,-1460 686,-1460 700.43,-1472.55 714.83,-1485.07"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-1488.03 722.74,-1491.95 717.49,-1482.75 712.9,-1488.03"/>
</g>
<!-- svc:influxdb -->
<g id="node13" class="node">
<title>svc:influxdb</title>
<path fill="#dcfce7" stroke="black" d="M767.5,-898C767.5,-898 721.5,-898 721.5,-898 715.5,-898 709.5,-892 709.5,-886 709.5,-886 709.5,-866 709.5,-866 709.5,-860 715.5,-854 721.5,-854 721.5,-854 767.5,-854 767.5,-854 773.5,-854 779.5,-860 779.5,-866 779.5,-866 779.5,-886 779.5,-886 779.5,-892 773.5,-898 767.5,-898"/>
<text text-anchor="middle" x="744.5" y="-885.2" font-family="Helvetica,sans-Serif" font-size="11.00">influxdb</text>
<text text-anchor="middle" x="744.5" y="-873.2" font-family="Helvetica,sans-Serif" font-size="11.00">:8086</text>
<text text-anchor="middle" x="744.5" y="-861.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:influxdb -->
<g id="edge12" class="edge">
<title>svc:traefik&#45;&gt;svc:influxdb</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M571.1,-788.34C606.37,-806.27 661.57,-834.34 699.97,-853.87"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="698.85,-857.22 709.35,-858.64 702.02,-850.98 698.85,-857.22"/>
</g>
<!-- svc:monitor&#45;kuma -->
<g id="node15" class="node">
<title>svc:monitor&#45;kuma</title>
<path fill="#dcfce7" stroke="black" d="M778.5,-1427C778.5,-1427 710.5,-1427 710.5,-1427 704.5,-1427 698.5,-1421 698.5,-1415 698.5,-1415 698.5,-1403 698.5,-1403 698.5,-1397 704.5,-1391 710.5,-1391 710.5,-1391 778.5,-1391 778.5,-1391 784.5,-1391 790.5,-1397 790.5,-1403 790.5,-1403 790.5,-1415 790.5,-1415 790.5,-1421 784.5,-1427 778.5,-1427"/>
<text text-anchor="middle" x="744.5" y="-1412.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor&#45;kuma</text>
<text text-anchor="middle" x="744.5" y="-1400.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:monitor&#45;kuma -->
<g id="edge14" class="edge">
<title>svc:traefik&#45;&gt;svc:monitor&#45;kuma</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M541.62,-789.24C566.77,-888.49 686,-1359 686,-1359 686,-1359 700.43,-1371.55 714.83,-1384.07"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="712.9,-1387.03 722.74,-1390.95 717.49,-1381.75 712.9,-1387.03"/>
</g>
<!-- svc:mtls&#45;bridge -->
<g id="node17" class="node">
<title>svc:mtls&#45;bridge</title>
<path fill="#dcfce7" stroke="black" d="M772,-1326C772,-1326 717,-1326 717,-1326 711,-1326 705,-1320 705,-1314 705,-1314 705,-1294 705,-1294 705,-1288 711,-1282 717,-1282 717,-1282 772,-1282 772,-1282 778,-1282 784,-1288 784,-1294 784,-1294 784,-1314 784,-1314 784,-1320 778,-1326 772,-1326"/>
<text text-anchor="middle" x="744.5" y="-1313.2" font-family="Helvetica,sans-Serif" font-size="11.00">mtls&#45;bridge</text>
<text text-anchor="middle" x="744.5" y="-1301.2" font-family="Helvetica,sans-Serif" font-size="11.00">:8080</text>
<text text-anchor="middle" x="744.5" y="-1289.2" font-family="Helvetica,sans-Serif" font-size="11.00">[mTLS]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:mtls&#45;bridge -->
<g id="edge16" class="edge">
<title>svc:traefik&#45;&gt;svc:mtls&#45;bridge</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M542.66,-789.19C569.88,-876.69 686,-1250 686,-1250 686,-1250 698.77,-1262 712.28,-1274.68"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1277.43 719.78,-1281.72 714.89,-1272.33 710.1,-1277.43"/>
</g>
<!-- svc:nextcloud&#45;webapp -->
<g id="node19" class="node">
<title>svc:nextcloud&#45;webapp</title>
<path fill="#dcfce7" stroke="black" d="M791,-137C791,-137 698,-137 698,-137 692,-137 686,-131 686,-125 686,-125 686,-113 686,-113 686,-107 692,-101 698,-101 698,-101 791,-101 791,-101 797,-101 803,-107 803,-113 803,-113 803,-125 803,-125 803,-131 797,-137 791,-137"/>
<text text-anchor="middle" x="744.5" y="-116.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud&#45;webapp</text>
</g>
<!-- svc:traefik&#45;&gt;svc:nextcloud&#45;webapp -->
<g id="edge18" class="edge">
<title>svc:traefik&#45;&gt;svc:nextcloud&#45;webapp</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M541.62,-752.85C566.77,-654.11 686,-186 686,-186 686,-186 704.96,-163.91 721.1,-145.1"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="723.93,-147.18 727.79,-137.31 718.62,-142.62 723.93,-147.18"/>
</g>
<!-- svc:node&#45;red -->
<g id="node21" class="node">
<title>svc:node&#45;red</title>
<path fill="#dcfce7" stroke="black" d="M767.5,-1217C767.5,-1217 721.5,-1217 721.5,-1217 715.5,-1217 709.5,-1211 709.5,-1205 709.5,-1205 709.5,-1185 709.5,-1185 709.5,-1179 715.5,-1173 721.5,-1173 721.5,-1173 767.5,-1173 767.5,-1173 773.5,-1173 779.5,-1179 779.5,-1185 779.5,-1185 779.5,-1205 779.5,-1205 779.5,-1211 773.5,-1217 767.5,-1217"/>
<text text-anchor="middle" x="744.5" y="-1204.2" font-family="Helvetica,sans-Serif" font-size="11.00">node&#45;red</text>
<text text-anchor="middle" x="744.5" y="-1192.2" font-family="Helvetica,sans-Serif" font-size="11.00">:1880</text>
<text text-anchor="middle" x="744.5" y="-1180.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:node&#45;red -->
<g id="edge20" class="edge">
<title>svc:traefik&#45;&gt;svc:node&#45;red</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M544.29,-789.1C574.2,-863.38 686,-1141 686,-1141 686,-1141 698.77,-1153 712.28,-1165.68"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1168.43 719.78,-1172.72 714.89,-1163.33 710.1,-1168.43"/>
</g>
<!-- svc:passbolt&#45;webapp -->
<g id="node23" class="node">
<title>svc:passbolt&#45;webapp</title>
<path fill="#dcfce7" stroke="black" d="M787.5,-36C787.5,-36 701.5,-36 701.5,-36 695.5,-36 689.5,-30 689.5,-24 689.5,-24 689.5,-12 689.5,-12 689.5,-6 695.5,0 701.5,0 701.5,0 787.5,0 787.5,0 793.5,0 799.5,-6 799.5,-12 799.5,-12 799.5,-24 799.5,-24 799.5,-30 793.5,-36 787.5,-36"/>
<text text-anchor="middle" x="744.5" y="-15.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt&#45;webapp</text>
</g>
<!-- svc:traefik&#45;&gt;svc:passbolt&#45;webapp -->
<g id="edge22" class="edge">
<title>svc:traefik&#45;&gt;svc:passbolt&#45;webapp</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M540.83,-752.92C564.15,-642.88 686,-68 686,-68 686,-68 700.43,-55.45 714.83,-42.93"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-45.25 722.74,-36.05 712.9,-39.97 717.49,-45.25"/>
</g>
<!-- svc:portainer -->
<g id="node25" class="node">
<title>svc:portainer</title>
<path fill="#dcfce7" stroke="black" d="M766,-478C766,-478 723,-478 723,-478 717,-478 711,-472 711,-466 711,-466 711,-446 711,-446 711,-440 717,-434 723,-434 723,-434 766,-434 766,-434 772,-434 778,-440 778,-446 778,-446 778,-466 778,-466 778,-472 772,-478 766,-478"/>
<text text-anchor="middle" x="744.5" y="-465.2" font-family="Helvetica,sans-Serif" font-size="11.00">portainer</text>
<text text-anchor="middle" x="744.5" y="-453.2" font-family="Helvetica,sans-Serif" font-size="11.00">:9000</text>
<text text-anchor="middle" x="744.5" y="-441.2" font-family="Helvetica,sans-Serif" font-size="11.00">[TLS]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:portainer -->
<g id="edge24" class="edge">
<title>svc:traefik&#45;&gt;svc:portainer</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M547.48,-752.65C581.39,-693.24 686,-510 686,-510 686,-510 698.77,-498 712.28,-485.32"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="714.89,-487.67 719.78,-478.28 710.1,-482.57 714.89,-487.67"/>
</g>
<!-- svc:prometheus -->
<g id="node27" class="node">
<title>svc:prometheus</title>
<path fill="#dcfce7" stroke="black" d="M774,-1108C774,-1108 715,-1108 715,-1108 709,-1108 703,-1102 703,-1096 703,-1096 703,-1076 703,-1076 703,-1070 709,-1064 715,-1064 715,-1064 774,-1064 774,-1064 780,-1064 786,-1070 786,-1076 786,-1076 786,-1096 786,-1096 786,-1102 780,-1108 774,-1108"/>
<text text-anchor="middle" x="744.5" y="-1095.2" font-family="Helvetica,sans-Serif" font-size="11.00">prometheus</text>
<text text-anchor="middle" x="744.5" y="-1083.2" font-family="Helvetica,sans-Serif" font-size="11.00">:9090</text>
<text text-anchor="middle" x="744.5" y="-1071.2" font-family="Helvetica,sans-Serif" font-size="11.00">[authelia]</text>
</g>
<!-- svc:traefik&#45;&gt;svc:prometheus -->
<g id="edge26" class="edge">
<title>svc:traefik&#45;&gt;svc:prometheus</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M547.48,-789.35C581.39,-848.76 686,-1032 686,-1032 686,-1032 698.77,-1044 712.28,-1056.68"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="710.1,-1059.43 719.78,-1063.72 714.89,-1054.33 710.1,-1059.43"/>
</g>
<!-- svc:searxng&#45;webapp -->
<g id="node29" class="node">
<title>svc:searxng&#45;webapp</title>
<path fill="#dcfce7" stroke="black" d="M786,-369C786,-369 703,-369 703,-369 697,-369 691,-363 691,-357 691,-357 691,-345 691,-345 691,-339 697,-333 703,-333 703,-333 786,-333 786,-333 792,-333 798,-339 798,-345 798,-345 798,-357 798,-357 798,-363 792,-369 786,-369"/>
<text text-anchor="middle" x="744.5" y="-348.2" font-family="Helvetica,sans-Serif" font-size="11.00">searxng&#45;webapp</text>
</g>
<!-- svc:traefik&#45;&gt;svc:searxng&#45;webapp -->
<g id="edge28" class="edge">
<title>svc:traefik&#45;&gt;svc:searxng&#45;webapp</title>
<path fill="none" stroke="#334155" stroke-width="1.4" d="M544.29,-752.9C574.2,-678.62 686,-401 686,-401 686,-401 700.43,-388.45 714.83,-375.93"/>
<polygon fill="#334155" stroke="#334155" stroke-width="1.4" points="717.49,-378.25 722.74,-369.05 712.9,-372.97 717.49,-378.25"/>
</g>
<!-- net:traefik -->
<g id="node36" class="node">
<title>net:traefik</title>
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-878" rx="31.04" ry="18"/>
<text text-anchor="middle" x="1007.69" y="-875.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik</text>
</g>
<!-- svc:traefik&#45;&gt;net:traefik -->
<g id="edge55" class="edge">
<title>svc:traefik&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M542.75,-752.82C570.13,-666.27 686,-300 686,-300 686,-300 803,-300 803,-300 803,-300 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- svc:authelia&#45;&gt;net:traefik -->
<g id="edge32" class="edge">
<title>svc:authelia&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M775.21,-783.17C824.86,-803.51 924.25,-844.23 975.12,-865.06"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="974.26,-867.36 981.67,-867.75 976.12,-862.83 974.26,-867.36"/>
</g>
<!-- dns:auth.&lt;domain&gt; -->
<g id="node4" class="node">
<title>dns:auth.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="123.5,-1496 25.5,-1496 25.5,-1460 129.5,-1460 129.5,-1490 123.5,-1496"/>
<polyline fill="none" stroke="black" points="123.5,-1496 123.5,-1490 "/>
<polyline fill="none" stroke="black" points="129.5,-1490 123.5,-1490 "/>
<text text-anchor="middle" x="77.5" y="-1475.2" font-family="Helvetica,sans-Serif" font-size="11.00">auth.&lt;domain&gt;</text>
</g>
<!-- dns:auth.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge3" class="edge">
<title>dns:auth.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-1459.95C128.03,-1445.63 155,-1428 155,-1428 155,-1428 286.65,-925.11 319.6,-799.28"/>
<polygon fill="#334155" stroke="#334155" points="323.07,-799.82 322.22,-789.26 316.3,-798.05 323.07,-799.82"/>
</g>
<!-- svc:gitea&#45;&gt;net:traefik -->
<g id="edge33" class="edge">
<title>svc:gitea&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M768.56,-688.05C784.55,-703.36 803,-721 803,-721 803,-721 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:gitea.&lt;domain&gt; -->
<g id="node6" class="node">
<title>dns:gitea.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="125,-1395 24,-1395 24,-1359 131,-1359 131,-1389 125,-1395"/>
<polyline fill="none" stroke="black" points="125,-1395 125,-1389 "/>
<polyline fill="none" stroke="black" points="131,-1389 125,-1389 "/>
<text text-anchor="middle" x="77.5" y="-1374.2" font-family="Helvetica,sans-Serif" font-size="11.00">gitea.&lt;domain&gt;</text>
</g>
<!-- dns:gitea.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge5" class="edge">
<title>dns:gitea.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-1358.95C128.03,-1344.63 155,-1327 155,-1327 155,-1327 283.58,-911.36 318.4,-798.82"/>
<polygon fill="#334155" stroke="#334155" points="321.76,-799.77 321.38,-789.18 315.08,-797.7 321.76,-799.77"/>
</g>
<!-- svc:gotify&#45;&gt;net:traefik -->
<g id="edge34" class="edge">
<title>svc:gotify&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-579.05C782.73,-593.37 803,-611 803,-611 803,-611 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:gotify.&lt;domain&gt; -->
<g id="node8" class="node">
<title>dns:gotify.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="126,-1294 23,-1294 23,-1258 132,-1258 132,-1288 126,-1294"/>
<polyline fill="none" stroke="black" points="126,-1294 126,-1288 "/>
<polyline fill="none" stroke="black" points="132,-1288 126,-1288 "/>
<text text-anchor="middle" x="77.5" y="-1273.2" font-family="Helvetica,sans-Serif" font-size="11.00">gotify.&lt;domain&gt;</text>
</g>
<!-- dns:gotify.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge7" class="edge">
<title>dns:gotify.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-1257.95C128.03,-1243.63 155,-1226 155,-1226 155,-1226 279.21,-897.41 316.53,-798.71"/>
<polygon fill="#334155" stroke="#334155" points="319.89,-799.71 320.15,-789.12 313.34,-797.23 319.89,-799.71"/>
</g>
<!-- net:monitor -->
<g id="node33" class="node">
<title>net:monitor</title>
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-979" rx="35.46" ry="18"/>
<text text-anchor="middle" x="1007.69" y="-976.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor</text>
</g>
<!-- svc:grafana&#45;&gt;net:monitor -->
<g id="edge35" class="edge">
<title>svc:grafana&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M774.2,-980.78C820.4,-980.43 911.47,-979.73 964.89,-979.32"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="965.19,-981.77 972.17,-979.26 965.15,-976.87 965.19,-981.77"/>
</g>
<!-- svc:grafana&#45;&gt;net:traefik -->
<g id="edge36" class="edge">
<title>svc:grafana&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M774.2,-969.68C823.41,-950.28 923.53,-910.8 974.83,-890.57"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="975.82,-892.81 981.44,-887.96 974.03,-888.25 975.82,-892.81"/>
</g>
<!-- dns:grafana.&lt;domain&gt; -->
<g id="node10" class="node">
<title>dns:grafana.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="132,-1193 17,-1193 17,-1157 138,-1157 138,-1187 132,-1193"/>
<polyline fill="none" stroke="black" points="132,-1193 132,-1187 "/>
<polyline fill="none" stroke="black" points="138,-1187 132,-1187 "/>
<text text-anchor="middle" x="77.5" y="-1172.2" font-family="Helvetica,sans-Serif" font-size="11.00">grafana.&lt;domain&gt;</text>
</g>
<!-- dns:grafana.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge9" class="edge">
<title>dns:grafana.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-1156.95C128.03,-1142.63 155,-1125 155,-1125 155,-1125 273.61,-880.89 313.84,-798.09"/>
<polygon fill="#334155" stroke="#334155" points="317.01,-799.57 318.23,-789.04 310.72,-796.51 317.01,-799.57"/>
</g>
<!-- net:gramps -->
<g id="node32" class="node">
<title>net:gramps</title>
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-1080" rx="34.76" ry="18"/>
<text text-anchor="middle" x="1007.69" y="-1077.2" font-family="Helvetica,sans-Serif" font-size="11.00">gramps</text>
</g>
<!-- svc:grampsweb&#45;&gt;net:gramps -->
<g id="edge37" class="edge">
<title>svc:grampsweb&#45;&gt;net:gramps</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1491.95C782.73,-1477.63 803,-1460 803,-1460 803,-1460 949.23,-1187.21 993.89,-1103.88"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="996.27,-1104.64 997.41,-1097.32 991.95,-1102.33 996.27,-1104.64"/>
</g>
<!-- svc:grampsweb&#45;&gt;net:traefik -->
<g id="edge38" class="edge">
<title>svc:grampsweb&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1491.95C782.73,-1477.63 803,-1460 803,-1460 803,-1460 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
</g>
<!-- dns:familytree.&lt;domain&gt; -->
<g id="node12" class="node">
<title>dns:familytree.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="139,-1092 10,-1092 10,-1056 145,-1056 145,-1086 139,-1092"/>
<polyline fill="none" stroke="black" points="139,-1092 139,-1086 "/>
<polyline fill="none" stroke="black" points="145,-1086 139,-1086 "/>
<text text-anchor="middle" x="77.5" y="-1071.2" font-family="Helvetica,sans-Serif" font-size="11.00">familytree.&lt;domain&gt;</text>
</g>
<!-- dns:familytree.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge11" class="edge">
<title>dns:familytree.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-1055.95C128.03,-1041.63 155,-1024 155,-1024 155,-1024 264.64,-862.73 308.84,-797.71"/>
<polygon fill="#334155" stroke="#334155" points="311.9,-799.43 314.63,-789.2 306.11,-795.5 311.9,-799.43"/>
</g>
<!-- svc:influxdb&#45;&gt;net:monitor -->
<g id="edge39" class="edge">
<title>svc:influxdb&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M779.65,-889.47C829.7,-909.2 922.46,-945.78 972.53,-965.53"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="971.89,-967.91 979.3,-968.2 973.69,-963.35 971.89,-967.91"/>
</g>
<!-- svc:influxdb&#45;&gt;net:traefik -->
<g id="edge40" class="edge">
<title>svc:influxdb&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M779.65,-876.26C828.54,-876.64 918.2,-877.32 968.99,-877.71"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="969.17,-880.16 976.19,-877.77 969.21,-875.26 969.17,-880.16"/>
</g>
<!-- dns:influxdb.&lt;domain&gt; -->
<g id="node14" class="node">
<title>dns:influxdb.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="132.5,-991 16.5,-991 16.5,-955 138.5,-955 138.5,-985 132.5,-991"/>
<polyline fill="none" stroke="black" points="132.5,-991 132.5,-985 "/>
<polyline fill="none" stroke="black" points="138.5,-985 132.5,-985 "/>
<text text-anchor="middle" x="77.5" y="-970.2" font-family="Helvetica,sans-Serif" font-size="11.00">influxdb.&lt;domain&gt;</text>
</g>
<!-- dns:influxdb.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge13" class="edge">
<title>dns:influxdb.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-954.95C128.03,-940.63 155,-923 155,-923 155,-923 250.14,-838.92 298.91,-795.83"/>
<polygon fill="#334155" stroke="#334155" points="301.42,-798.28 306.59,-789.03 296.78,-793.03 301.42,-798.28"/>
</g>
<!-- svc:monitor&#45;kuma&#45;&gt;net:monitor -->
<g id="edge41" class="edge">
<title>svc:monitor&#45;kuma&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1390.95C782.73,-1376.63 803,-1359 803,-1359 803,-1359 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
</g>
<!-- svc:monitor&#45;kuma&#45;&gt;net:traefik -->
<g id="edge42" class="edge">
<title>svc:monitor&#45;kuma&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-1390.95C782.73,-1376.63 803,-1359 803,-1359 803,-1359 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
</g>
<!-- dns:monitor&#45;kuma.&lt;domain&gt; -->
<g id="node16" class="node">
<title>dns:monitor&#45;kuma.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="149,-890 0,-890 0,-854 155,-854 155,-884 149,-890"/>
<polyline fill="none" stroke="black" points="149,-890 149,-884 "/>
<polyline fill="none" stroke="black" points="155,-884 149,-884 "/>
<text text-anchor="middle" x="77.5" y="-869.2" font-family="Helvetica,sans-Serif" font-size="11.00">monitor&#45;kuma.&lt;domain&gt;</text>
</g>
<!-- dns:monitor&#45;kuma.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge15" class="edge">
<title>dns:monitor&#45;kuma.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M122.93,-853.94C165.02,-836.83 228.32,-811.11 273.25,-792.85"/>
<polygon fill="#334155" stroke="#334155" points="274.59,-796.08 282.54,-789.07 271.96,-789.59 274.59,-796.08"/>
</g>
<!-- svc:mtls&#45;bridge&#45;&gt;net:monitor -->
<g id="edge43" class="edge">
<title>svc:mtls&#45;bridge&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1281.72C785.06,-1266.85 803,-1250 803,-1250 803,-1250 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
</g>
<!-- svc:mtls&#45;bridge&#45;&gt;net:traefik -->
<g id="edge44" class="edge">
<title>svc:mtls&#45;bridge&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1281.72C785.06,-1266.85 803,-1250 803,-1250 803,-1250 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
</g>
<!-- dns:mtls&#45;bridge.&lt;domain&gt; -->
<g id="node18" class="node">
<title>dns:mtls&#45;bridge.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="142,-789 7,-789 7,-753 148,-753 148,-783 142,-789"/>
<polyline fill="none" stroke="black" points="142,-789 142,-783 "/>
<polyline fill="none" stroke="black" points="148,-783 142,-783 "/>
<text text-anchor="middle" x="77.5" y="-768.2" font-family="Helvetica,sans-Serif" font-size="11.00">mtls&#45;bridge.&lt;domain&gt;</text>
</g>
<!-- dns:mtls&#45;bridge.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge17" class="edge">
<title>dns:mtls&#45;bridge.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M148.05,-771C182.94,-771 225.03,-771 259.61,-771"/>
<polygon fill="#334155" stroke="#334155" points="259.62,-774.5 269.62,-771 259.62,-767.5 259.62,-774.5"/>
</g>
<!-- net:nextcloud -->
<g id="node34" class="node">
<title>net:nextcloud</title>
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-777" rx="42.89" ry="18"/>
<text text-anchor="middle" x="1007.69" y="-774.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud</text>
</g>
<!-- svc:nextcloud&#45;webapp&#45;&gt;net:nextcloud -->
<g id="edge45" class="edge">
<title>svc:nextcloud&#45;webapp&#45;&gt;net:nextcloud</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M761.21,-137.31C778.24,-157.15 803,-186 803,-186 803,-186 910,-727 910,-727 910,-727 945.48,-745.35 973.46,-759.81"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="972.67,-762.17 980.02,-763.2 974.92,-757.81 972.67,-762.17"/>
</g>
<!-- svc:nextcloud&#45;webapp&#45;&gt;net:traefik -->
<g id="edge46" class="edge">
<title>svc:nextcloud&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M761.21,-137.31C778.24,-157.15 803,-186 803,-186 803,-186 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:nextcloud.&lt;domain&gt; -->
<g id="node20" class="node">
<title>dns:nextcloud.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="138,-688 11,-688 11,-652 144,-652 144,-682 138,-688"/>
<polyline fill="none" stroke="black" points="138,-688 138,-682 "/>
<polyline fill="none" stroke="black" points="144,-682 138,-682 "/>
<text text-anchor="middle" x="77.5" y="-667.2" font-family="Helvetica,sans-Serif" font-size="11.00">nextcloud.&lt;domain&gt;</text>
</g>
<!-- dns:nextcloud.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge19" class="edge">
<title>dns:nextcloud.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M122.93,-688.06C165.02,-705.17 228.32,-730.89 273.25,-749.15"/>
<polygon fill="#334155" stroke="#334155" points="271.96,-752.41 282.54,-752.93 274.59,-745.92 271.96,-752.41"/>
</g>
<!-- svc:node&#45;red&#45;&gt;net:monitor -->
<g id="edge47" class="edge">
<title>svc:node&#45;red&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1172.72C785.06,-1157.85 803,-1141 803,-1141 803,-1141 910,-1030 910,-1030 910,-1030 947.73,-1010.1 976.05,-995.16"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="977.25,-997.3 982.29,-991.87 974.96,-992.97 977.25,-997.3"/>
</g>
<!-- svc:node&#45;red&#45;&gt;net:traefik -->
<g id="edge48" class="edge">
<title>svc:node&#45;red&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1172.72C785.06,-1157.85 803,-1141 803,-1141 803,-1141 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
</g>
<!-- dns:node&#45;red.&lt;domain&gt; -->
<g id="node22" class="node">
<title>dns:node&#45;red.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="135.5,-587 13.5,-587 13.5,-551 141.5,-551 141.5,-581 135.5,-587"/>
<polyline fill="none" stroke="black" points="135.5,-587 135.5,-581 "/>
<polyline fill="none" stroke="black" points="141.5,-581 135.5,-581 "/>
<text text-anchor="middle" x="77.5" y="-566.2" font-family="Helvetica,sans-Serif" font-size="11.00">node&#45;red.&lt;domain&gt;</text>
</g>
<!-- dns:node&#45;red.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge21" class="edge">
<title>dns:node&#45;red.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-587.05C128.03,-601.37 155,-619 155,-619 155,-619 250.14,-703.08 298.91,-746.17"/>
<polygon fill="#334155" stroke="#334155" points="296.78,-748.97 306.59,-752.97 301.42,-743.72 296.78,-748.97"/>
</g>
<!-- net:passbolt -->
<g id="node35" class="node">
<title>net:passbolt</title>
<ellipse fill="#f8fafc" stroke="black" cx="1007.69" cy="-676" rx="37.77" ry="18"/>
<text text-anchor="middle" x="1007.69" y="-673.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt</text>
</g>
<!-- svc:passbolt&#45;webapp&#45;&gt;net:passbolt -->
<g id="edge49" class="edge">
<title>svc:passbolt&#45;webapp&#45;&gt;net:passbolt</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-36.05C782.73,-50.37 803,-68 803,-68 803,-68 960.4,-537.83 998.48,-651.47"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="996.16,-652.27 1000.71,-658.13 1000.81,-650.72 996.16,-652.27"/>
</g>
<!-- svc:passbolt&#45;webapp&#45;&gt;net:traefik -->
<g id="edge50" class="edge">
<title>svc:passbolt&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-36.05C782.73,-50.37 803,-68 803,-68 803,-68 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:passbolt.&lt;domain&gt; -->
<g id="node24" class="node">
<title>dns:passbolt.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="134,-486 15,-486 15,-450 140,-450 140,-480 134,-486"/>
<polyline fill="none" stroke="black" points="134,-486 134,-480 "/>
<polyline fill="none" stroke="black" points="140,-480 134,-480 "/>
<text text-anchor="middle" x="77.5" y="-465.2" font-family="Helvetica,sans-Serif" font-size="11.00">passbolt.&lt;domain&gt;</text>
</g>
<!-- dns:passbolt.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge23" class="edge">
<title>dns:passbolt.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-486.05C128.03,-500.37 155,-518 155,-518 155,-518 264.64,-679.27 308.84,-744.29"/>
<polygon fill="#334155" stroke="#334155" points="306.11,-746.5 314.63,-752.8 311.9,-742.57 306.11,-746.5"/>
</g>
<!-- svc:portainer&#45;&gt;net:traefik -->
<g id="edge51" class="edge">
<title>svc:portainer&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-478.28C785.06,-493.15 803,-510 803,-510 803,-510 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:portainer.&lt;domain&gt; -->
<g id="node26" class="node">
<title>dns:portainer.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="135.5,-385 13.5,-385 13.5,-349 141.5,-349 141.5,-379 135.5,-385"/>
<polyline fill="none" stroke="black" points="135.5,-385 135.5,-379 "/>
<polyline fill="none" stroke="black" points="141.5,-379 135.5,-379 "/>
<text text-anchor="middle" x="77.5" y="-364.2" font-family="Helvetica,sans-Serif" font-size="11.00">portainer.&lt;domain&gt;</text>
</g>
<!-- dns:portainer.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge25" class="edge">
<title>dns:portainer.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-385.05C128.03,-399.37 155,-417 155,-417 155,-417 273.61,-661.11 313.84,-743.91"/>
<polygon fill="#334155" stroke="#334155" points="310.72,-745.49 318.23,-752.96 317.01,-742.43 310.72,-745.49"/>
</g>
<!-- svc:prometheus&#45;&gt;net:monitor -->
<g id="edge52" class="edge">
<title>svc:prometheus&#45;&gt;net:monitor</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M786.03,-1069.4C837.48,-1048.32 925.31,-1012.34 973.18,-992.73"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="974.11,-995 979.66,-990.07 972.26,-990.46 974.11,-995"/>
</g>
<!-- svc:prometheus&#45;&gt;net:traefik -->
<g id="edge53" class="edge">
<title>svc:prometheus&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M769.22,-1063.72C785.06,-1048.85 803,-1032 803,-1032 803,-1032 910,-929 910,-929 910,-929 949.05,-908.4 977.52,-893.39"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="978.73,-895.52 983.78,-890.09 976.45,-891.18 978.73,-895.52"/>
</g>
<!-- dns:prometheus.&lt;domain&gt; -->
<g id="node28" class="node">
<title>dns:prometheus.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="144,-284 5,-284 5,-248 150,-248 150,-278 144,-284"/>
<polyline fill="none" stroke="black" points="144,-284 144,-278 "/>
<polyline fill="none" stroke="black" points="150,-278 144,-278 "/>
<text text-anchor="middle" x="77.5" y="-263.2" font-family="Helvetica,sans-Serif" font-size="11.00">prometheus.&lt;domain&gt;</text>
</g>
<!-- dns:prometheus.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge27" class="edge">
<title>dns:prometheus.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-284.05C128.03,-298.37 155,-316 155,-316 155,-316 279.21,-644.59 316.53,-743.29"/>
<polygon fill="#334155" stroke="#334155" points="313.34,-744.77 320.15,-752.88 319.89,-742.29 313.34,-744.77"/>
</g>
<!-- svc:searxng&#45;webapp&#45;&gt;net:traefik -->
<g id="edge54" class="edge">
<title>svc:searxng&#45;webapp&#45;&gt;net:traefik</title>
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M766.26,-369.05C782.73,-383.37 803,-401 803,-401 803,-401 910,-828 910,-828 910,-828 949.05,-848.19 977.52,-862.92"/>
<polygon fill="#94a3b8" stroke="#94a3b8" points="976.44,-865.11 983.78,-866.15 978.69,-860.76 976.44,-865.11"/>
</g>
<!-- dns:searxng.&lt;domain&gt; -->
<g id="node30" class="node">
<title>dns:searxng.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="133,-183 16,-183 16,-147 139,-147 139,-177 133,-183"/>
<polyline fill="none" stroke="black" points="133,-183 133,-177 "/>
<polyline fill="none" stroke="black" points="139,-177 133,-177 "/>
<text text-anchor="middle" x="77.5" y="-162.2" font-family="Helvetica,sans-Serif" font-size="11.00">searxng.&lt;domain&gt;</text>
</g>
<!-- dns:searxng.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge29" class="edge">
<title>dns:searxng.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-183.05C128.03,-197.37 155,-215 155,-215 155,-215 283.58,-630.64 318.4,-743.18"/>
<polygon fill="#334155" stroke="#334155" points="315.08,-744.3 321.38,-752.82 321.76,-742.23 315.08,-744.3"/>
</g>
<!-- dns:traefik.&lt;domain&gt; -->
<g id="node31" class="node">
<title>dns:traefik.&lt;domain&gt;</title>
<polygon fill="#fef3c7" stroke="black" points="128.5,-82 20.5,-82 20.5,-46 134.5,-46 134.5,-76 128.5,-82"/>
<polyline fill="none" stroke="black" points="128.5,-82 128.5,-76 "/>
<polyline fill="none" stroke="black" points="134.5,-76 128.5,-76 "/>
<text text-anchor="middle" x="77.5" y="-61.2" font-family="Helvetica,sans-Serif" font-size="11.00">traefik.&lt;domain&gt;</text>
</g>
<!-- dns:traefik.&lt;domain&gt;&#45;&gt;dynu -->
<g id="edge31" class="edge">
<title>dns:traefik.&lt;domain&gt;&#45;&gt;dynu</title>
<path fill="none" stroke="#334155" d="M106.12,-82.05C128.03,-96.37 155,-114 155,-114 155,-114 286.65,-616.89 319.6,-742.72"/>
<polygon fill="#334155" stroke="#334155" points="316.3,-743.95 322.22,-752.74 323.07,-742.18 316.3,-743.95"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 41 KiB

+21
View File
@@ -0,0 +1,21 @@
# Public Infrastructure Summary
This documentation is generated from the infrastructure repository. Sensitive values are redacted.
> Generated docs are sanitised/redacted before publishing to GitHub Pages.
## Infrastructure diagrams
### Physical / virtual topology
![Physical topology](physical-topology.svg)
### Docker, Traefik and Dynu routing
![Docker Traefik Dynu](docker-traefik-dynu.svg)
## Documents
- [Diagrams](diagrams.md)
- [Compose Inventory](compose-inventory.md)
- [Traefik Routes](traefik-routes.md)
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: PhysicalTopology Pages: 1 -->
<svg width="514pt" height="61pt"
viewBox="0.00 0.00 514.00 61.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 57)">
<title>PhysicalTopology</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-57 510,-57 510,4 -4,4"/>
<!-- placeholder:inventory -->
<g id="node1" class="node">
<title>placeholder:inventory</title>
<polygon fill="#fef3c7" stroke="black" points="500,-53 0,-53 0,0 506,0 506,-47 500,-53"/>
<polyline fill="none" stroke="black" points="500,-53 500,-47 "/>
<polyline fill="none" stroke="black" points="506,-47 500,-47 "/>
<text text-anchor="middle" x="253" y="-37.8" font-family="Times,serif" font-size="14.00">Host inventory JSON not found.</text>
<text text-anchor="middle" x="253" y="-22.8" font-family="Times,serif" font-size="14.00">Generate terraform inventory and rerun scripts/docs/generate&#45;all.sh</text>
<text text-anchor="middle" x="253" y="-7.8" font-family="Times,serif" font-size="14.00">(&#45;&#45;host&#45;inventory &lt;path&gt;).</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+23
View File
@@ -0,0 +1,23 @@
.md-content img, article img {
max-width: 100%;
width: auto;
height: auto;
}
.diagram-wrap {
width: 100%;
overflow-x: auto;
margin: 1rem 0 2rem;
}
.diagram-wrap img {
max-width: none;
width: 1400px;
height: auto;
}
@media (max-width: 900px) {
.diagram-wrap img {
width: 1200px;
}
}
+21
View File
@@ -0,0 +1,21 @@
# Traefik Routes
| Service | Router | Rule | Entrypoints | TLS | Middlewares | Target Port |
|---|---|---|---|---|---|---|
| authelia | authelia | Host(`auth.<domain>`) | websecure | true | | |
| error-pages | error-pages-router | HostRegexp(`{host:.+}`) | web | | error-pages-middleware | |
| gitea | gitea | Host(`gitea.<domain>`) | websecure | true | | 3000 |
| gotify | gotify | Host(`gotify.<domain>`) | websecure | | | 80 |
| grafana | grafana | Host(`grafana.<domain>`) | websecure | | | 3000 |
| grampsweb | gramps | Host(`familytree.<domain>`) | websecure | | | 5000 |
| influxdb | influxdb | Host(`influxdb.<domain>`) | websecure | | authelia | 8086 |
| monitor-kuma | monitor | Host(`monitor-kuma.<domain>`) | websecure | true | | 3001 |
| mtls-bridge | mtls-bridge | Host(`mtls-bridge.<domain>`) | websecure | | mtls-bridge-auth,mtls-bridge-cors | 8080 |
| mtls-bridge | mtls-bridge-preflight | Host(`mtls-bridge.<domain>`) && Method(`OPTIONS`) | websecure | | mtls-bridge-cors | |
| nextcloud-webapp | nextcloud | Host(`nextcloud.<domain>`) | websecure | | nextcloud-dav, nextcloud-webfinger | |
| node-red | node-red | Host(`node-red.<domain>`) | websecure | | authelia | 1880 |
| passbolt-webapp | passbolt | Host(`passbolt.<domain>`) | websecure | | | |
| portainer | portainer | Host(`portainer.<domain>`) | websecure | true | | 9000 |
| prometheus | prometheus | Host(`prometheus.<domain>`) | websecure | | authelia | 9090 |
| searxng-webapp | searxng | Host(`searxng.<domain>`) | websecure | | | 8080 |
| traefik | traefik | Host(`traefik.<domain>`) | websecure | | authelia | |
+42
View File
@@ -0,0 +1,42 @@
# Repository Structure
This page explains where to find authoritative files quickly.
## Top-level directories
- `core/` — core platform/security services (Traefik, Authelia, CrowdSec, error pages).
- `apps/` — user/business applications (Nextcloud, Passbolt, Gitea, Gramps, SearXNG).
- `monitoring/` — observability and operational tooling (Prometheus, Grafana, InfluxDB, Node-RED, etc.).
- `infrastructure/terraform/` — brownfield Terraform inventory/reconciliation layers.
- `infrastructure/ansible/` — phase-1 Ansible inventory/configuration scaffold and validation playbooks.
- `docs/` — repository-level architecture and workflow documentation.
- `archive/` — historical compose/config artifacts not part of active runtime composition.
- `secrets/` — local secret material and templates; never commit real values.
## Key top-level files
- `services-up.sh` — runtime composition entrypoint for multi-compose environment.
- `default-network.yml` — shared docker network definitions used across compose files.
- `default-environment.env` — non-secret default env values for compose rendering.
- `scripts/codex-setup.sh` — Codex/bootstrap helper to install validation tooling and prepare dummy secret material.
- `scripts/codex-maintenance.sh` — Codex maintenance helper to refresh tooling, reconcile dummy secret material, and run safe Ansible validation checks.
- `docs/deployment-prerequisites.md` — prerequisite setup before runtime operations.
- `docs/security-secrets.md` — secrets documentation and inventory model.
## Terraform layout
- `infrastructure/terraform/README.md` — Terraform purpose and boundaries in this repo.
- `infrastructure/terraform/proxmox/` — imported/reconciled Proxmox VM resources and host metadata.
- `infrastructure/terraform/docker/` — selective Docker container documentation mirrors.
- `infrastructure/terraform/bootstrap/` — backend/provider bootstrap scaffolding.
- `infrastructure/terraform/scripts/reconcile_from_plan.sh` — helper for `terraform plan -generate-config-out` reconciliation workflow.
## Fast path for future Codex runs
1. Read [README.md](https://github.com/beatz174-bit/docker/blob/main/README.md).
2. Read [docs/source-of-truth.md](source-of-truth.md).
3. Read [docs/docker-environment.md](docker-environment.md).
4. Read [docs/terraform-workflows.md](terraform-workflows.md).
5. Only then edit Compose/Terraform files.
6. For Ansible bootstrap changes, validate inventory and playbook syntax checks only.
+56
View File
@@ -0,0 +1,56 @@
# Security Secrets
## Overview
This page explains how secret material is organized in this repository and where to find both human-readable and machine-readable references.
For machine-readable inventory metadata, use [`secrets/inventory.json`](https://github.com/beatz174-bit/docker/blob/main/secrets/inventory.json).
## Scope and authority
- Canonical example template: [`secrets/.env.secrets.example`](https://github.com/beatz174-bit/docker/blob/main/secrets/.env.secrets.example)
- Runtime-loaded secret env file (local, non-committed): `../secrets/stack-secrets.env`
- Dynu DNS inventory env file (local, non-committed): `../secrets/dynu.env`
- Docker secret files (local, non-committed): `../secrets/*.txt`
Treat the example template as the canonical shape for expected environment variables.
## Secret material types
1. **Template variables in `.env.secrets.example`**
- Document expected variable names and usage expectations.
2. **Local runtime env file (`stack-secrets.env`)**
- Holds local runtime secret values loaded during compose rendering.
3. **Local Dynu env file (`dynu.env`)**
- Holds `DYNU_*` values used by read-only Dynu DNS inventory scripts.
4. **Local Docker secret files (`*.txt`)**
- Hold password/token material consumed via `*_FILE` style configuration.
5. **Externally managed secret inputs**
- Some values are managed outside shared templates and provided through file mounts or environment substitution.
## Machine-readable inventory
- Primary automation source: [`secrets/inventory.json`](https://github.com/beatz174-bit/docker/blob/main/secrets/inventory.json)
- Human guidance source: this page
Automation should parse `secrets/inventory.json` directly rather than scraping Markdown tables.
## Setup and deployment prerequisites
Before running compose operations, follow [`./deployment-prerequisites.md`](./deployment-prerequisites.md).
## Commit safety rules
Never commit:
- `secrets/stack-secrets.env`
- `secrets/dynu.env`
- real `secrets/*.txt` secret files
- real Terraform `.tfvars` files containing credentials
- Terraform state files with sensitive runtime metadata
## Related docs
- [`./deployment-prerequisites.md`](./deployment-prerequisites.md)
- [`./docker-environment.md`](./docker-environment.md)
- [`./source-of-truth.md`](./source-of-truth.md)
+14
View File
@@ -0,0 +1,14 @@
# Public Showcase
This environment showcases practical infrastructure operations with a documentation-first approach.
Highlights:
- Container orchestration with Docker Compose.
- Reverse proxy routing and TLS-focused service exposure patterns.
- Monitoring and alerting configuration via version-controlled rules.
- Documentation automation through CI.
- Generated architecture diagrams to aid operational understanding.
- Sanitized public documentation for safe sharing.
This page intentionally avoids private hostnames, internal-only URLs, credentials, and secret values.
+63
View File
@@ -0,0 +1,63 @@
# Source-of-Truth Boundaries
This repository has multiple layers. Knowing the authority for each layer prevents accidental drift.
## Boundary summary
| Layer | Primary authority | Purpose |
|---|---|---|
| Application/runtime container composition | `services-up.sh` + Compose files under `core/`, `apps/`, `monitoring/` | What runs in the Docker environment and how services are wired. |
| Docker shared baseline inputs | `default-network.yml`, `default-environment.env`, `secrets/stack-secrets.env` | Shared network/env material applied during compose rendering. |
| Infrastructure inventory and reconciliation | Terraform under `infrastructure/terraform/` | Codified inventory of existing infrastructure and relationships, especially Proxmox VMs and selected Docker mirrors. |
| Secret policy and inventory | `docs/security-secrets.md` + `secrets/inventory.json` + local secret files in `secrets/` | What secrets exist, where they are expected, and what automation should parse. |
| Host/device configuration bootstrap (emerging) | Ansible under `infrastructure/ansible/` | Gradual inventory/configuration layer for hosts/devices; validation-first at current stage. |
## Practical meaning
### Docker runtime decisions
Change Compose files and `services-up.sh` when changing runtime behavior.
Do **not** assume Terraform Docker resources are the deployment source for day-to-day service runtime.
### Infrastructure inventory decisions
Use Terraform when documenting/reconciling existing:
- Proxmox VM config and identifiers.
- Physical host metadata.
- Select Docker container details that are intentionally mirrored.
Do **not** treat Terraform as a full replacement for Compose operations in this repo.
- Dynu public DNS records remain authoritative at Dynu.
- Terraform Dynu configuration mirrors/reconciles Dynu DNS state for documentation and controlled drift management.
- Imported Dynu Terraform state reflects actual provider-side DNS state at import time.
### Ansible bootstrap decisions
Use Ansible under `infrastructure/ansible/` to build inventory and configuration structure incrementally.
At the current stage:
- Do **not** treat Ansible as replacement authority for Docker runtime operations.
- Do **not** treat Ansible as replacement authority for Terraform inventory/reconciliation.
- NixOS remains outside Ansible authority unless explicitly adopted in a later phase.
## Declared config vs observed/runtime state
- **Declared config**: files in this repository (Compose, Terraform, docs).
- **Observed/runtime state**: live Docker/Proxmox reality and Terraform state snapshots.
Brownfield workflows reconcile these two safely and incrementally.
## Guardrails for contributors and Codex
- Do not mass-import or mass-reconcile everything at once.
- Keep imports/reconciliation scoped to one object (or small set) at a time.
- Keep `ignore_changes` surgical and justified.
- Prefer shaped outputs (inventory-ready) over raw provider object dumps.
- Do not commit `.tfstate`, real `.tfvars`, or real secret files.
See [docs/terraform-workflows.md](terraform-workflows.md) for step-by-step procedures.
+93
View File
@@ -0,0 +1,93 @@
# Terraform Workflows (Brownfield / Reconciliation)
Terraform in this repository is primarily used for **importing and reconciling existing infrastructure**.
This is a brownfield workflow: real infrastructure exists first, then code/state are brought into alignment.
## Core workflow pattern
1. Define/import one existing object.
2. Inspect current provider state.
3. Reconcile hand-maintained `.tf` configuration.
4. Use targeted `ignore_changes` only when necessary.
5. Iterate until plan is sane/no-op for intended scope.
6. Avoid casual apply operations.
## Docker mirror workflow (documentation-oriented)
Directory: `infrastructure/terraform/docker/`
Use when intentionally mirroring selected running containers as structured documentation.
### Steps
1. Add minimal `docker_container` resource block (or uncomment/import-ready block).
2. Add `import {}` block or run `terraform import` for the container.
3. Run plan and inspect generated/state values.
4. Keep only meaningful, maintainable arguments in hand-edited files.
5. Use generated files as draft input, not final truth.
6. Re-run plan until intended scope is clean.
## Proxmox VM workflow
Directory: `infrastructure/terraform/proxmox/`
Use for existing Proxmox VMs and metadata reconciliation.
### Steps
1. Add/import one VM resource at a time.
2. Confirm provider import ID format and vm/node mapping.
3. Inspect with `terraform state show` / plan output.
4. Move useful arguments into stable hand-maintained files.
5. Keep lifecycle ignore rules narrow and explicit.
6. Iterate per VM until plan stabilizes.
## Dynu DNS workflow
Directory: `infrastructure/terraform/dynu/`
Use for existing Dynu domains and DNS records.
1. Add or confirm the documentation catalog entry for one hostname.
2. Confirm the provider resource type and import ID format.
3. Import one existing domain or DNS record at a time.
4. Inspect state with `terraform state show`.
5. Reconcile only stable, meaningful attributes into hand-maintained `.tf`.
6. Keep record IDs, dynamic DNS targets, and provider-computed values out unless intentionally required.
7. Re-run plan until intended scope is clean.
## Physical host metadata workflow
Physical host metadata currently lives in Proxmox Terraform locals/outputs and is used as documentation inventory context.
When updating:
1. update locals with factual host metadata,
2. ensure outputs remain documentation-friendly,
3. avoid leaking sensitive internal data not needed for repository goals.
## Generated config guidance
`infrastructure/terraform/scripts/reconcile_from_plan.sh` can generate Terraform draft configuration via `-generate-config-out`.
Treat generated files as:
- a starting point,
- reviewed manually,
- reduced to meaningful attributes,
- reformatted and split into maintainable files.
## Safety reminders
- Do not commit `.tfstate*` or real `.tfvars`.
- Do not commit credentials.
- Do not run `terraform apply`/`destroy` casually.
- Keep changes incremental and reviewable.
## Related docs
- [docs/source-of-truth.md](source-of-truth.md)
- [docs/infrastructure-inventory.md](infrastructure-inventory.md)
- [infrastructure/terraform/README.md](https://github.com/beatz174-bit/docker/blob/main/infrastructure/terraform/README.md)
+32
View File
@@ -0,0 +1,32 @@
# Terraform working dir
**/.terraform/*
# Terraform state
*.tfstate
*.tfstate.*
**/terraform.tfstate
**/terraform.tfstate.*
# Crash logs
crash.log
crash.*.log
# Variable files with real values
*.tfvars
*.tfvars.json
# CLI config / overrides
override.tf
override.tf.json
*_override.tf
*_override.tf.json
.terraformrc
terraform.rc
# Plan files
*.tfplan
plan.out
# Local backend/state folders if you make them
state/
artifacts/
+48
View File
@@ -0,0 +1,48 @@
# Ansible Foundation (Phase 1)
This directory provides a minimal Ansible bootstrap for this repository.
## Purpose
- Establish a maintainable inventory/configuration foundation for hosts and devices.
- Support gradual host onboarding and validation workflows.
- Keep boundaries clear with existing Compose and Terraform authorities.
This is intentionally a **foundation stage**, not full production automation.
## Boundaries
- Docker runtime authority remains in Compose files and `services-up.sh`.
- Terraform remains the primary structured infrastructure inventory/reconciliation layer.
- Ansible here is a complementary configuration/inventory layer.
- NixOS and network gear management are not authoritative through Ansible yet.
## Structure
- `ansible.cfg` - local defaults for inventory, collections, and output behavior.
- `inventory/hosts.yml` - YAML inventory scaffold with starter groups.
- `inventory/group_vars/` - shared/group variables.
- `inventory/host_vars/` - per-host variables.
- `playbooks/ping.yml` - minimal syntax/connection test playbook.
- `playbooks/dns-inventory.yml` - local-only Dynu DNS read-only inventory wrapper.
- `collections/requirements.yml` - lightweight baseline collections.
- `roles/` - reserved for future incremental role adoption.
## Basic commands
Run from repository root:
```bash
ansible --version
ansible-lint --version
ansible-galaxy collection install -r infrastructure/ansible/collections/requirements.yml -p infrastructure/ansible/collections
ansible-inventory -i infrastructure/ansible/inventory/hosts.yml --list
ansible-playbook -i infrastructure/ansible/inventory/hosts.yml infrastructure/ansible/playbooks/ping.yml --syntax-check
ansible-playbook -i infrastructure/ansible/inventory/hosts.yml infrastructure/ansible/playbooks/dns-inventory.yml --syntax-check
```
## Secrets and safety
- Do not commit real credentials or private keys.
- Put sensitive per-host variables in local, untracked files or a future vault approach.
- Keep host and device entries factual; avoid speculative production entries.
+9
View File
@@ -0,0 +1,9 @@
[defaults]
inventory = ./inventory/hosts.yml
collections_path = ./collections
retry_files_enabled = False
stdout_callback = yaml
host_key_checking = True
[inventory]
enable_plugins = yaml
@@ -0,0 +1,4 @@
---
collections:
- name: ansible.posix
- name: community.general
@@ -0,0 +1,14 @@
---
# Bootstrap defaults for the Ansible foundation in this repository.
# Keep secrets and environment-specific auth details out of version control.
# Common interpreter hint for modern Linux hosts. Override per-host if needed.
ansible_python_interpreter: /usr/bin/python3
# Placeholders for future connection/auth settings:
# ansible_user: ""
# ansible_port: 22
# ansible_ssh_private_key_file: ""
# Add group-specific settings under inventory/group_vars/<group>.yml
# and host-specific settings under inventory/host_vars/<host>.yml.
@@ -0,0 +1,17 @@
---
all:
children:
linux:
hosts: {}
network:
hosts: {}
virtualization:
hosts: {}
nixos:
hosts: {}
examples:
hosts:
example-managed-host:
ansible_host: example-host.local
ansible_connection: ssh
# Example only: replace/remove before real operations.
@@ -0,0 +1,26 @@
---
# This integration is intentionally read-only.
# No Dynu mutations are permitted in this repo at this stage.
- name: Build Dynu DNS read-only inventory artifacts
hosts: localhost
connection: local
gather_facts: false
vars:
repo_root: "{{ playbook_dir }}/../../.."
tasks:
- name: Assert read-only guard variable is set
ansible.builtin.assert:
that:
- lookup('ansible.builtin.env', 'DYNU_READ_ONLY') == 'true'
fail_msg: "Refusing to run: DYNU_READ_ONLY must be exactly 'true'."
- name: Fetch Dynu DNS (GET-only script)
ansible.builtin.command: python3 scripts/dynu/fetch_dynu_dns.py
args:
chdir: "{{ repo_root }}"
- name: Correlate Dynu with Traefik and generate docs
ansible.builtin.command: python3 scripts/dynu/correlate_dynu_with_traefik.py
args:
chdir: "{{ repo_root }}"
@@ -0,0 +1,7 @@
---
- name: Basic inventory and connectivity check
hosts: all
gather_facts: false
tasks:
- name: Ping managed hosts
ansible.builtin.ping:
+14
View File
@@ -0,0 +1,14 @@
.terraform/
*.tfstate
*.tfstate.*
*.tfvars
*.tfvars.json
crash.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
*.tfplan
plan.out
state/
artifacts/
+64
View File
@@ -0,0 +1,64 @@
# Terraform in This Repository
Terraform here is used as a **structured inventory + reconciliation layer** for existing infrastructure.
It does **not** replace Docker Compose as runtime deployment authority.
## What Terraform is currently used for
- Proxmox VM import/reconciliation for existing VMs.
- Physical host metadata represented in Terraform locals/outputs.
- Select Docker container mirror resources for documentation-oriented tracking.
- Outputs that can support documentation and later downstream tooling.
- Dynu DNS domain/record import and documentation inventory.
## What Terraform is not used for (today)
- Replacing `services-up.sh` / Compose for day-to-day app runtime orchestration.
- Broad, immediate greenfield provisioning of the whole stack.
- Casual `apply` operations across all infrastructure.
- Replacing Dynu as DNS authority.
- Blindly recreating production DNS records without import/reconciliation.
## Directory map
- `proxmox/` — imported/reconciled VM resources and host metadata outputs.
- `docker/` — selective Docker container import/mirror resources.
- `dynu/` — Dynu DNS brownfield import/reconciliation and DNS documentation outputs.
- `bootstrap/` — backend/provider bootstrap scaffolding.
- `modules/` — placeholder module directories for future stable abstractions.
- `scripts/reconcile_from_plan.sh` — helper to convert generated plan config into reviewable draft files.
## Brownfield workflow standard
1. Import one existing object.
2. Inspect state/plan output.
3. Reconcile hand-maintained Terraform code.
4. Keep `ignore_changes` narrowly scoped.
5. Iterate to no-op/sane plan for intended scope.
6. Avoid casual apply.
See detailed steps in [../../docs/terraform-workflows.md](../../docs/terraform-workflows.md).
## Safe validation commands
From Terraform directories, preferred checks are:
```bash
terraform fmt -check -recursive
terraform init -backend=false -input=false
terraform validate
```
## Secrets and state safety
- Do not commit `.tfstate*`.
- Do not commit real `.tfvars` values.
- Keep credentials in local, untracked inputs only.
## Related docs
- [../../docs/source-of-truth.md](../../docs/source-of-truth.md)
- [../../docs/infrastructure-inventory.md](../../docs/infrastructure-inventory.md)
- [docker/README.md](docker/README.md)
- [proxmox/README.md](proxmox/README.md)
+24
View File
@@ -0,0 +1,24 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/kreuzwerker/docker" {
version = "3.0.2"
constraints = "3.0.2"
hashes = [
"h1:cT2ccWOtlfKYBUE60/v2/4Q6Stk1KYTNnhxSck+VPlU=",
"zh:15b0a2b2b563d8d40f62f83057d91acb02cd0096f207488d8b4298a59203d64f",
"zh:23d919de139f7cd5ebfd2ff1b94e6d9913f0977fcfc2ca02e1573be53e269f95",
"zh:38081b3fe317c7e9555b2aaad325ad3fa516a886d2dfa8605ae6a809c1072138",
"zh:4a9c5065b178082f79ad8160243369c185214d874ff5048556d48d3edd03c4da",
"zh:5438ef6afe057945f28bce43d76c4401254073de01a774760169ac1058830ac2",
"zh:60b7fadc287166e5c9873dfe53a7976d98244979e0ab66428ea0dea1ebf33e06",
"zh:61c5ec1cb94e4c4a4fb1e4a24576d5f39a955f09afb17dab982de62b70a9bdd1",
"zh:a38fe9016ace5f911ab00c88e64b156ebbbbfb72a51a44da3c13d442cd214710",
"zh:c2c4d2b1fd9ebb291c57f524b3bf9d0994ff3e815c0cd9c9bcb87166dc687005",
"zh:d567bb8ce483ab2cf0602e07eae57027a1a53994aba470fa76095912a505533d",
"zh:e83bf05ab6a19dd8c43547ce9a8a511f8c331a124d11ac64687c764ab9d5a792",
"zh:e90c934b5cd65516fbcc454c89a150bfa726e7cf1fe749790c7480bbeb19d387",
"zh:f05f167d2eaf913045d8e7b88c13757e3cf595dd5cd333057fdafc7c4b7fed62",
"zh:fcc9c1cea5ce85e8bcb593862e699a881bd36dffd29e2e367f82d15368659c3d",
]
}
+43
View File
@@ -0,0 +1,43 @@
# Terraform Docker Mirror Layer
This directory tracks selected existing Docker containers in Terraform for inventory/documentation purposes.
## Purpose
- Mirror specific running containers as Terraform resources.
- Reconcile imported state into maintainable code.
- Produce structured outputs/reminders that support documentation workflows.
## Boundary with Docker Compose
Docker Compose + `services-up.sh` remain runtime composition authority.
Terraform resources here are **not** the primary day-to-day deployment mechanism for app services.
## Current contents
- `main.tf` — import-first workflow notes and minimal scaffolding.
- `searxng-webapp.tf` — generated/reconciled example container resource.
- `outputs.tf` — documentation-oriented reminders/outputs.
- `terraform.tfvars.example` — safe template for local values.
## Import/reconciliation workflow
1. Start with one existing container.
2. Import with `import {}` block or `terraform import`.
3. Inspect state / generated config.
4. Reduce generated attributes to meaningful, stable arguments.
5. Keep lifecycle `ignore_changes` narrow and justified.
6. Iterate until plan is clean for the intended resource.
## Guardrails
- Do not attempt to mirror all containers in one pass.
- Do not commit local state or real credentials.
- Treat generated config as draft input that needs review.
## Related docs
- [../README.md](../README.md)
- [../../../docs/source-of-truth.md](../../../docs/source-of-truth.md)
- [../../../docs/terraform-workflows.md](../../../docs/terraform-workflows.md)
@@ -0,0 +1,14 @@
resource "docker_container" "authelia" {
name = local.docker_containers["authelia"].container_name
image = local.docker_containers["authelia"].image
restart = local.docker_containers["authelia"].restart_policy
lifecycle {
ignore_changes = [
env,
labels,
]
}
}
@@ -0,0 +1,613 @@
locals {
docker_containers = {
"authelia" = {
terraform_resource = "docker_container.authelia"
compose_project = "core"
compose_service = "authelia"
compose_file = "core/authelia/docker-compose.yml"
container_name = "authelia"
image = "authelia/authelia"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["traefik"]
mounts = ["bind:/home/nixos/docker/core/authelia->/config"]
published_ports = []
build_context = "/home/nixos/docker/core/authelia"
build_dockerfile = "Dockerfile"
useful_labels = {
"traefik.enable" = "true"
"traefik.http.middlewares.authelia.forwardauth.address" = "http://authelia:9091/api/verify?rd=https://auth.lan.ddnsgeek.com/"
"traefik.http.middlewares.authelia.forwardauth.authResponseHeaders" = "Remote-User,Remote-Groups"
"traefik.http.middlewares.authelia.forwardauth.maxResponseBodySize" = "2097152"
"traefik.http.middlewares.authelia.forwardauth.trustForwardHeader" = "true"
"traefik.http.routers.authelia.entrypoints" = "websecure"
"traefik.http.routers.authelia.rule" = "Host(`auth.lan.ddnsgeek.com`)"
"traefik.http.routers.authelia.tls" = "true"
"traefik.http.routers.authelia.tls.certresolver" = "myresolver"
}
}
"crowdsec" = {
terraform_resource = "docker_container.crowdsec"
compose_project = "core"
compose_service = "crowdsec"
compose_file = "core/crowdsec/docker-compose.yml"
container_name = "crowdsec"
image = "core-crowdsec"
image_source = "compose_build_inferred"
restart_policy = "always"
network_mode = null
networks = ["traefik"]
mounts = ["bind:/home/nixos/docker/core/crowdsec/logs->/logs:ro", "bind:/home/nixos/docker/core/crowdsec/data->/var/lib/crowdsec/data", "bind:/home/nixos/docker/core/crowdsec/config->/etc/crowdsec"]
published_ports = []
build_context = "/home/nixos/docker/core/crowdsec"
build_dockerfile = "Dockerfile"
useful_labels = {}
}
"docker-socket-proxy" = {
terraform_resource = "docker_container.docker_socket_proxy"
compose_project = "core"
compose_service = "docker-socket-proxy"
compose_file = "monitoring/docker-socket-proxy/docker-compose.yml"
container_name = "docker-socket-proxy"
image = "tecnativa/docker-socket-proxy:latest"
image_source = "declared_image"
restart_policy = "unless-stopped"
network_mode = null
networks = ["monitor", "traefik"]
mounts = ["bind:/var/run/docker.sock->/var/run/docker.sock:ro"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {}
}
"docker-update-exporter" = {
terraform_resource = "docker_container.docker_update_exporter"
compose_project = "core"
compose_service = "docker-update-exporter"
compose_file = "monitoring/docker-exporter/docker-compose.yml"
container_name = "docker-update-exporter"
image = "core-docker-update-exporter"
image_source = "compose_build_inferred"
restart_policy = "unless-stopped"
network_mode = null
networks = ["monitor"]
mounts = ["bind:/root/.docker/config.json->/root/.docker/config.json:ro", "bind:/home/nixos/docker/monitoring/docker-exporter/data->/data", "bind:/home/nixos/docker->/compose:ro"]
published_ports = []
build_context = "/home/nixos/docker/monitoring/docker-exporter"
build_dockerfile = "Dockerfile"
useful_labels = {}
}
"error-pages" = {
terraform_resource = "docker_container.error_pages"
compose_project = "core"
compose_service = "error-pages"
compose_file = "core/error-pages/docker-compose.yml"
container_name = "error-pages"
image = "tarampampam/error-pages:3"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["traefik"]
mounts = []
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.enable" = "true"
"traefik.http.middlewares.error-pages-middleware.errors.query" = "/{status}.html"
"traefik.http.middlewares.error-pages-middleware.errors.service" = "error-pages-service"
"traefik.http.middlewares.error-pages-middleware.errors.status" = "400-599"
"traefik.http.routers.error-pages-router.entrypoints" = "web"
"traefik.http.routers.error-pages-router.middlewares" = "error-pages-middleware"
"traefik.http.routers.error-pages-router.rule" = "HostRegexp(`{host:.+}`)"
"traefik.http.services.error-pages-service.loadbalancer.server.port" = "8080"
}
}
"gitea" = {
terraform_resource = "docker_container.gitea"
compose_project = "core"
compose_service = "gitea"
compose_file = "apps/gitea/docker-compose.yml"
container_name = "gitea"
image = "gitea/gitea:latest"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["traefik"]
mounts = ["bind:/home/nixos/docker/apps/gitea/data->/data"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.routers.gitea.entrypoints" = "websecure"
"traefik.http.routers.gitea.rule" = "Host(`gitea.lan.ddnsgeek.com`)"
"traefik.http.routers.gitea.tls" = "true"
"traefik.http.routers.gitea.tls.certresolver" = "myresolver"
"traefik.http.services.gitea.loadbalancer.server.port" = "3000"
}
}
"gotify" = {
terraform_resource = "docker_container.gotify"
compose_project = "core"
compose_service = "gotify"
compose_file = "monitoring/gotify/docker-compose.yml"
container_name = "gotify"
image = "gotify/server:latest"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["traefik"]
mounts = ["bind:/home/nixos/docker/monitoring/gotify/data->/app/data"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.routers.gotify.entrypoints" = "websecure"
"traefik.http.routers.gotify.rule" = "Host(`gotify.lan.ddnsgeek.com`)"
"traefik.http.routers.gotify.tls.certresolver" = "myresolver"
"traefik.http.routers.gotify.tls.options" = "mtls-private-admin@file"
"traefik.http.services.gotify.loadbalancer.server.port" = "80"
}
}
"grafana" = {
terraform_resource = "docker_container.grafana"
compose_project = "core"
compose_service = "grafana"
compose_file = "monitoring/grafana/docker-compose.yml"
container_name = "grafana"
image = "grafana/grafana:latest"
image_source = "declared_image"
restart_policy = "unless-stopped"
network_mode = null
networks = ["monitor", "traefik"]
mounts = ["bind:/home/nixos/docker/monitoring/grafana/data->/var/lib/grafana"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.routers.grafana.entrypoints" = "websecure"
"traefik.http.routers.grafana.rule" = "Host(`grafana.lan.ddnsgeek.com`)"
"traefik.http.routers.grafana.tls.certresolver" = "myresolver"
"traefik.http.routers.grafana.tls.options" = "mtls-private-admin@file"
"traefik.http.services.grafana.loadbalancer.server.port" = "3000"
}
}
"gramps-redis" = {
terraform_resource = "docker_container.gramps_redis"
compose_project = "core"
compose_service = "gramps-redis"
compose_file = "apps/gramps/docker-compose.yml"
container_name = "gramps-redis"
image = "valkey/valkey:8-alpine"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["gramps"]
mounts = []
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {}
}
"gramps-web" = {
terraform_resource = "docker_container.gramps_web"
compose_project = "core"
compose_service = "grampsweb"
compose_file = "apps/gramps/docker-compose.yml"
container_name = "gramps-web"
image = "ghcr.io/gramps-project/grampsweb:latest"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["gramps", "traefik"]
mounts = ["bind:/home/nixos/docker/apps/gramps/data/users->/app/users", "bind:/home/nixos/docker/apps/gramps/data/index->/app/indexdir", "bind:/home/nixos/docker/apps/gramps/data/thumbnail_cache->/app/thumbnail_cache", "bind:/home/nixos/docker/apps/gramps/data/cache->/app/cache", "bind:/home/nixos/docker/apps/gramps/data/secret->/app/secret", "bind:/home/nixos/docker/apps/gramps/data/db->/root/.gramps/grampsdb", "bind:/home/nixos/docker/apps/gramps/data/media->/app/media", "bind:/home/nixos/docker/apps/gramps/data/tmp->/tmp"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.routers.gramps.entrypoints" = "websecure"
"traefik.http.routers.gramps.rule" = "Host(`familytree.lan.ddnsgeek.com`)"
"traefik.http.routers.gramps.tls.certresolver" = "myresolver"
"traefik.http.services.gramps.loadbalancer.server.port" = "5000"
}
}
"gramps-web-celery" = {
terraform_resource = "docker_container.gramps_web_celery"
compose_project = "core"
compose_service = "grampsweb_celery"
compose_file = "apps/gramps/docker-compose.yml"
container_name = "gramps-web-celery"
image = "ghcr.io/gramps-project/grampsweb:latest"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["gramps"]
mounts = ["bind:/home/nixos/docker/apps/gramps/data/users->/app/users", "bind:/home/nixos/docker/apps/gramps/data/index->/app/indexdir", "bind:/home/nixos/docker/apps/gramps/data/thumbnail_cache->/app/thumbnail_cache", "bind:/home/nixos/docker/apps/gramps/data/cache->/app/cache", "bind:/home/nixos/docker/apps/gramps/data/secret->/app/secret", "bind:/home/nixos/docker/apps/gramps/data/db->/root/.gramps/grampsdb", "bind:/home/nixos/docker/apps/gramps/data/media->/app/media", "bind:/home/nixos/docker/apps/gramps/data/tmp->/tmp"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {}
}
"influxdb" = {
terraform_resource = "docker_container.influxdb"
compose_project = "core"
compose_service = "influxdb"
compose_file = "monitoring/influxdb/docker-compose.yml"
container_name = "influxdb"
image = "influxdb:2.7"
image_source = "declared_image"
restart_policy = "unless-stopped"
network_mode = null
networks = ["monitor", "traefik"]
mounts = ["bind:/home/nixos/docker/monitoring/influxdb->/var/lib/influxdb2"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.routers.influxdb.entrypoints" = "websecure"
"traefik.http.routers.influxdb.middlewares" = "authelia"
"traefik.http.routers.influxdb.rule" = "Host(`influxdb.lan.ddnsgeek.com`)"
"traefik.http.routers.influxdb.tls.certresolver" = "myresolver"
"traefik.http.routers.influxdb.tls.options" = "mtls-private-admin@file"
"traefik.http.services.influxdb.loadbalancer.server.port" = "8086"
}
}
"monitor-kuma" = {
terraform_resource = "docker_container.monitor_kuma"
compose_project = "core"
compose_service = "monitor-kuma"
compose_file = "monitoring/uptime-kuma/docker-compose.yml"
container_name = "monitor-kuma"
image = "louislam/uptime-kuma:2.1.1"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["monitor", "traefik"]
mounts = ["bind:/home/nixos/docker/monitoring/uptime-kuma/data->/app/data"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.routers.monitor.entrypoints" = "websecure"
"traefik.http.routers.monitor.rule" = "Host(`monitor-kuma.lan.ddnsgeek.com`)"
"traefik.http.routers.monitor.tls" = "true"
"traefik.http.routers.monitor.tls.certresolver" = "myresolver"
"traefik.http.routers.monitor.tls.options" = "mtls-private-admin@file"
"traefik.http.services.monitor.loadbalancer.server.port" = "3001"
}
}
"mtls-bridge" = {
terraform_resource = "docker_container.mtls_bridge"
compose_project = "core"
compose_service = "mtls-bridge"
compose_file = "monitoring/mtls-bridge/docker-compose.yml"
container_name = "mtls-bridge"
image = "core-mtls-bridge"
image_source = "compose_build_inferred"
restart_policy = "unless-stopped"
network_mode = null
networks = ["monitor", "traefik"]
mounts = ["bind:/home/nixos/docker/core/traefik/certs->/certs:ro"]
published_ports = []
build_context = "/home/nixos/docker/monitoring/mtls-bridge"
build_dockerfile = "Dockerfile"
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.middlewares.mtls-bridge-auth.basicauth.users" = ""
"traefik.http.middlewares.mtls-bridge-cors.headers.accesscontrolallowcredentials" = "true"
"traefik.http.middlewares.mtls-bridge-cors.headers.accesscontrolallowheaders" = "authorization,content-type,x-grafana-action,x-grafana-device-id"
"traefik.http.middlewares.mtls-bridge-cors.headers.accesscontrolallowmethods" = "GET,POST,PUT,PATCH,DELETE,OPTIONS"
"traefik.http.middlewares.mtls-bridge-cors.headers.accesscontrolalloworiginlist" = "https://grafana.lan.ddnsgeek.com"
"traefik.http.middlewares.mtls-bridge-cors.headers.addvaryheader" = "true"
"traefik.http.routers.mtls-bridge-preflight.entrypoints" = "websecure"
"traefik.http.routers.mtls-bridge-preflight.middlewares" = "mtls-bridge-cors"
"traefik.http.routers.mtls-bridge-preflight.priority" = "100"
"traefik.http.routers.mtls-bridge-preflight.rule" = "Host(`mtls-bridge.lan.ddnsgeek.com`) && Method(`OPTIONS`)"
"traefik.http.routers.mtls-bridge-preflight.service" = "mtls-bridge"
"traefik.http.routers.mtls-bridge-preflight.tls.certresolver" = "myresolver"
"traefik.http.routers.mtls-bridge.entrypoints" = "websecure"
"traefik.http.routers.mtls-bridge.middlewares" = "mtls-bridge-auth,mtls-bridge-cors"
"traefik.http.routers.mtls-bridge.rule" = "Host(`mtls-bridge.lan.ddnsgeek.com`)"
"traefik.http.routers.mtls-bridge.tls.certresolver" = "myresolver"
"traefik.http.services.mtls-bridge.loadbalancer.server.port" = "8080"
}
}
"nextcloud-db" = {
terraform_resource = "docker_container.nextcloud_db"
compose_project = "core"
compose_service = "nextcloud-db"
compose_file = "apps/nextcloud/docker-compose.yml"
container_name = "nextcloud-db"
image = "mariadb:11.4"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["nextcloud"]
mounts = ["bind:/home/nixos/docker/apps/nextcloud/database->/var/lib/mysql"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {}
}
"nextcloud-redis" = {
terraform_resource = "docker_container.nextcloud_redis"
compose_project = "core"
compose_service = "nextcloud-redis"
compose_file = "apps/nextcloud/docker-compose.yml"
container_name = "nextcloud-redis"
image = "redis"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["nextcloud"]
mounts = ["bind:/home/nixos/docker/apps/nextcloud/data/redis->/data"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {}
}
"nextcloud-webapp" = {
terraform_resource = "docker_container.nextcloud_webapp"
compose_project = "core"
compose_service = "nextcloud-webapp"
compose_file = "apps/nextcloud/docker-compose.yml"
container_name = "nextcloud-webapp"
image = "core-nextcloud-webapp"
image_source = "compose_build_inferred"
restart_policy = "always"
network_mode = null
networks = ["nextcloud", "traefik"]
mounts = ["bind:/home/nixos/docker/apps/nextcloud/data->/var/www/html/data", "bind:/home/nixos/docker/apps/nextcloud/config->/var/www/html/config", "tmpfs:->/tmp:exec"]
published_ports = []
build_context = "/home/nixos/docker/apps/nextcloud"
build_dockerfile = "Dockerfile"
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.middlewares.nextcloud-dav.replacepathregex.regex" = "^/.well-known/ca(l|rd)dav"
"traefik.http.middlewares.nextcloud-dav.replacepathregex.replacement" = "/remote.php/dav/"
"traefik.http.middlewares.nextcloud-nodeinfo.replacepathregex.regex" = "^/.well-known/nodeinfo"
"traefik.http.middlewares.nextcloud-nodeinfo.replacepathregex.replacement" = "/nextcloud/index.php/.well-known/nodeinfo/"
"traefik.http.middlewares.nextcloud-webfinger.redirectregex.permanent" = "true"
"traefik.http.middlewares.nextcloud-webfinger.redirectregex.regex" = "https://(.*)/.well-known/webfinger"
"traefik.http.middlewares.nextcloud-webfinger.redirectregex.replacement" = "https://$${1}/nextcloud/index.php/.well-known/webfinger"
"traefik.http.routers.nextcloud.entrypoints" = "websecure"
"traefik.http.routers.nextcloud.middlewares" = "nextcloud-dav, nextcloud-webfinger"
"traefik.http.routers.nextcloud.rule" = "Host(`nextcloud.lan.ddnsgeek.com`)"
"traefik.http.routers.nextcloud.tls.certresolver" = "myresolver"
}
}
"node-exporter" = {
terraform_resource = "docker_container.node_exporter"
compose_project = "core"
compose_service = "node-exporter"
compose_file = "monitoring/node-exporter/docker-compose.yml"
container_name = "node-exporter"
image = "prom/node-exporter:latest"
image_source = "declared_image"
restart_policy = "unless-stopped"
network_mode = null
networks = ["monitor"]
mounts = ["bind:/proc->/host/proc:ro", "bind:/sys->/host/sys:ro", "bind:/->/rootfs:ro"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {}
}
"node-red" = {
terraform_resource = "docker_container.node_red"
compose_project = "core"
compose_service = "node-red"
compose_file = "monitoring/node-red/docker-compose.yml"
container_name = "node-red"
image = "core-node-red"
image_source = "compose_build_inferred"
restart_policy = "unless-stopped"
network_mode = null
networks = ["monitor", "traefik"]
mounts = ["bind:/home/nixos/docker/monitoring/node-red/data->/data", "bind:/home/nixos/docker->/compose/docker:ro", "bind:/home/nixos/raspi->/compose/raspi:ro"]
published_ports = []
build_context = "/home/nixos/docker/monitoring/node-red"
build_dockerfile = "Dockerfile"
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.routers.node-red.entrypoints" = "websecure"
"traefik.http.routers.node-red.middlewares" = "authelia"
"traefik.http.routers.node-red.rule" = "Host(`node-red.lan.ddnsgeek.com`)"
"traefik.http.routers.node-red.tls.certresolver" = "myresolver"
"traefik.http.routers.node-red.tls.options" = "mtls-private-admin@file"
"traefik.http.services.node-red.loadbalancer.server.port" = "1880"
}
}
"passbolt-db" = {
terraform_resource = "docker_container.passbolt_db"
compose_project = "core"
compose_service = "passbolt-db"
compose_file = "apps/passbolt/docker-compose.yml"
container_name = "passbolt-db"
image = "mariadb:12"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["passbolt"]
mounts = ["bind:/home/nixos/docker/apps/passbolt/data/database->/var/lib/mysql"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {}
}
"passbolt-webapp" = {
terraform_resource = "docker_container.passbolt_webapp"
compose_project = "core"
compose_service = "passbolt-webapp"
compose_file = "apps/passbolt/docker-compose.yml"
container_name = "passbolt-webapp"
image = "passbolt/passbolt:latest-ce"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["passbolt", "traefik"]
mounts = ["bind:/home/nixos/docker/apps/passbolt/data/gpg->/etc/passbolt/gpg", "bind:/home/nixos/docker/apps/passbolt/data/jwt->/etc/passbolt/jwt"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.routers.passbolt.entrypoints" = "websecure"
"traefik.http.routers.passbolt.rule" = "Host(`passbolt.lan.ddnsgeek.com`)"
"traefik.http.routers.passbolt.tls.certresolver" = "myresolver"
}
}
"pihole-exporter" = {
terraform_resource = "docker_container.pihole_exporter"
compose_project = "core"
compose_service = "pihole-exporter"
compose_file = "monitoring/pihole-exporter/docker-compose.yml"
container_name = "pihole-exporter"
image = "ekofr/pihole-exporter:latest"
image_source = "declared_image"
restart_policy = "unless-stopped"
network_mode = null
networks = ["monitor"]
mounts = []
published_ports = ["9617:9617/tcp"]
build_context = null
build_dockerfile = null
useful_labels = {}
}
"portainer" = {
terraform_resource = "docker_container.portainer"
compose_project = "core"
compose_service = "portainer"
compose_file = "monitoring/portainer/docker-compose.yml"
container_name = "portainer"
image = "portainer/portainer-ce:latest"
image_source = "declared_image"
restart_policy = "unless-stopped"
network_mode = null
networks = ["traefik"]
mounts = ["bind:/home/nixos/docker/monitoring/portainer/data->/data"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.enable" = "true"
"traefik.http.routers.portainer.entrypoints" = "websecure"
"traefik.http.routers.portainer.rule" = "Host(`portainer.lan.ddnsgeek.com`)"
"traefik.http.routers.portainer.tls" = "true"
"traefik.http.routers.portainer.tls.certresolver" = "myresolver"
"traefik.http.routers.portainer.tls.options" = "mtls-private-admin@file"
"traefik.http.services.portainer.loadbalancer.server.port" = "9000"
}
}
"prometheus" = {
terraform_resource = "docker_container.prometheus"
compose_project = "core"
compose_service = "prometheus"
compose_file = "monitoring/prometheus/docker-compose.yml"
container_name = "prometheus"
image = "prom/prometheus:latest"
image_source = "declared_image"
restart_policy = "unless-stopped"
network_mode = null
networks = ["monitor", "traefik"]
mounts = ["bind:/home/nixos/docker/monitoring/prometheus/prometheus.yml->/etc/prometheus/prometheus.yml:ro", "bind:/home/nixos/docker/monitoring/prometheus/data->/prometheus", "bind:/home/nixos/docker/monitoring/prometheus/rules->/etc/prometheus/rules:ro", "bind:/home/nixos/docker/secrets/prometheus_kuma_basic_auth_password.txt->/run/secrets/prometheus_kuma_basic_auth_password:ro"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.routers.prometheus.entrypoints" = "websecure"
"traefik.http.routers.prometheus.middlewares" = "authelia"
"traefik.http.routers.prometheus.rule" = "Host(`prometheus.lan.ddnsgeek.com`)"
"traefik.http.routers.prometheus.tls.certresolver" = "myresolver"
"traefik.http.routers.prometheus.tls.options" = "mtls-private-admin@file"
"traefik.http.services.prometheus.loadbalancer.server.port" = "9090"
}
}
"searxng-webapp" = {
terraform_resource = "docker_container.searxng-webapp"
compose_project = "core"
compose_service = "searxng-webapp"
compose_file = "apps/searxng/docker-compose.yml"
container_name = "searxng-webapp"
image = "searxng/searxng"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["traefik"]
mounts = []
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {
"traefik.enable" = "true"
"traefik.http.routers.searxng.entrypoints" = "websecure"
"traefik.http.routers.searxng.rule" = "Host(`searxng.lan.ddnsgeek.com`)"
"traefik.http.routers.searxng.tls.certresolver" = "myresolver"
"traefik.http.services.searxng.loadbalancer.server.port" = "8080"
}
}
"telegraf" = {
terraform_resource = "docker_container.telegraf"
compose_project = "core"
compose_service = "telegraf"
compose_file = "monitoring/telegraf/docker-compose.yml"
container_name = "telegraf"
image = "telegraf:latest"
image_source = "declared_image"
restart_policy = "unless-stopped"
network_mode = null
networks = ["monitor"]
mounts = ["bind:/home/nixos/docker/monitoring/telegraf/telegraf.conf->/etc/telegraf/telegraf.conf:ro", "bind:/home/nixos/docker/monitoring/node-red/data->/var/log/node-red:ro"]
published_ports = []
build_context = null
build_dockerfile = null
useful_labels = {}
}
"traefik" = {
terraform_resource = "docker_container.traefik"
compose_project = "core"
compose_service = "traefik"
compose_file = "core/traefik/docker-compose.yml"
container_name = "traefik"
image = "traefik:3"
image_source = "declared_image"
restart_policy = "always"
network_mode = null
networks = ["traefik"]
mounts = ["bind:/home/nixos/docker/core/traefik/data/letsencrypt->/letsencrypt", "bind:/home/nixos/docker/core/traefik/data/logs->/logs", "bind:/home/nixos/docker/core/traefik/certs->/etc/traefik/certs:ro", "bind:/home/nixos/docker/core/traefik/dynamic.yml->/etc/traefik/dynamic.yml:ro", "bind:/home/nixos/docker/core/traefik/traefik.yml->/etc/traefik/traefik.yml:ro", "bind:/home/nixos/docker/core/traefik/data/plugins->/plugins-storage"]
published_ports = ["80:80/tcp", "443:443/tcp"]
build_context = "/home/nixos/docker/core"
build_dockerfile = "Dockerfile"
useful_labels = {
"traefik.docker.network" = "core_traefik"
"traefik.enable" = "true"
"traefik.http.routers.traefik.entrypoints" = "websecure"
"traefik.http.routers.traefik.middlewares" = "authelia"
"traefik.http.routers.traefik.observability.tracing" = "true"
"traefik.http.routers.traefik.rule" = "Host(`traefik.lan.ddnsgeek.com`)"
"traefik.http.routers.traefik.service" = "api@internal"
"traefik.http.routers.traefik.tls.certresolver" = "myresolver"
"traefik.http.routers.traefik.tls.options" = "mtls-private-admin@file"
}
}
}
}
@@ -0,0 +1,14 @@
resource "docker_container" "crowdsec" {
name = local.docker_containers["crowdsec"].container_name
image = local.docker_containers["crowdsec"].image
restart = local.docker_containers["crowdsec"].restart_policy
lifecycle {
ignore_changes = [
env,
labels,
]
}
}
@@ -0,0 +1,14 @@
resource "docker_container" "docker_socket_proxy" {
name = local.docker_containers["docker-socket-proxy"].container_name
image = local.docker_containers["docker-socket-proxy"].image
restart = local.docker_containers["docker-socket-proxy"].restart_policy
lifecycle {
ignore_changes = [
env,
labels,
]
}
}
@@ -0,0 +1,14 @@
resource "docker_container" "docker_update_exporter" {
name = local.docker_containers["docker-update-exporter"].container_name
image = local.docker_containers["docker-update-exporter"].image
restart = local.docker_containers["docker-update-exporter"].restart_policy
lifecycle {
ignore_changes = [
env,
labels,
]
}
}
@@ -0,0 +1,14 @@
resource "docker_container" "error_pages" {
name = local.docker_containers["error-pages"].container_name
image = local.docker_containers["error-pages"].image
restart = local.docker_containers["error-pages"].restart_policy
lifecycle {
ignore_changes = [
env,
labels,
]
}
}
+14
View File
@@ -0,0 +1,14 @@
resource "docker_container" "gitea" {
name = local.docker_containers["gitea"].container_name
image = local.docker_containers["gitea"].image
restart = local.docker_containers["gitea"].restart_policy
lifecycle {
ignore_changes = [
env,
labels,
]
}
}
+14
View File
@@ -0,0 +1,14 @@
resource "docker_container" "gotify" {
name = local.docker_containers["gotify"].container_name
image = local.docker_containers["gotify"].image
restart = local.docker_containers["gotify"].restart_policy
lifecycle {
ignore_changes = [
env,
labels,
]
}
}
@@ -0,0 +1,14 @@
resource "docker_container" "grafana" {
name = local.docker_containers["grafana"].container_name
image = local.docker_containers["grafana"].image
restart = local.docker_containers["grafana"].restart_policy
lifecycle {
ignore_changes = [
env,
labels,
]
}
}

Some files were not shown because too many files have changed in this diff Show More