xcad 3 месяцев назад
Родитель
Сommit
e1cb44cb84
100 измененных файлов с 5126 добавлено и 2259 удалено
  1. 1 1
      .github/workflows/docs-update-wiki.yaml
  2. 1 0
      AGENTS.md
  3. 1 0
      CHANGELOG.md
  4. 8 0
      cli/core/display/display_base.py
  5. 48 0
      cli/core/display/display_icons.py
  6. 7 3
      cli/core/display/display_status.py
  7. 4 1
      cli/core/display/display_variable.py
  8. 13 27
      cli/core/schema/compose/v1.2.json
  9. 76 3
      cli/core/schema/terraform/v1.0.json
  10. 31 6
      cli/core/template/template.py
  11. 5 0
      cli/core/template/variable_collection.py
  12. 39 0
      library/compose/adguardhome/common/networks.yaml.j2
  13. 28 0
      library/compose/adguardhome/common/volumes.yaml.j2
  14. 14 204
      library/compose/adguardhome/compose.yaml.j2
  15. 157 0
      library/compose/adguardhome/services/adguardhome.yaml.j2
  16. 47 31
      library/compose/adguardhome/template.yaml
  17. 51 35
      library/compose/alloy/compose.yaml.j2
  18. 2 4
      library/compose/alloy/template.yaml
  19. 9 0
      library/compose/authentik/common/networks.yaml.j2
  20. 55 0
      library/compose/authentik/common/volumes.yaml.j2
  21. 19 304
      library/compose/authentik/compose.yaml.j2
  22. 326 0
      library/compose/authentik/compose.yaml.j2.backup
  23. 28 0
      library/compose/authentik/services/postgres.yaml.j2
  24. 24 0
      library/compose/authentik/services/redis.yaml.j2
  25. 124 0
      library/compose/authentik/services/server.yaml.j2
  26. 48 0
      library/compose/authentik/services/worker.yaml.j2
  27. 2 1
      library/compose/authentik/template.yaml
  28. 95 38
      library/compose/bind9/compose.yaml.j2
  29. 3 1
      library/compose/bind9/template.yaml
  30. 41 32
      library/compose/checkmk/compose.yaml.j2
  31. 2 1
      library/compose/checkmk/template.yaml
  32. 0 19
      library/compose/clamav/compose.yaml.j2
  33. 0 81
      library/compose/clamav/config/clamd.conf
  34. 0 21
      library/compose/clamav/config/freshclam.conf
  35. 0 30
      library/compose/clamav/template.yaml
  36. 35 35
      library/compose/dockge/compose.yaml.j2
  37. 2 1
      library/compose/dockge/template.yaml
  38. 68 50
      library/compose/gitea/compose.yaml.j2
  39. 2 0
      library/compose/gitea/template.yaml
  40. 9 0
      library/compose/gitlab-runner/compose.yaml.j2
  41. 72 40
      library/compose/gitlab/compose.yaml.j2
  42. 2 11
      library/compose/gitlab/template.yaml
  43. 0 1
      library/compose/grafana/.env.secret.grafana_db_password.j2
  44. 0 1
      library/compose/grafana/.env.secret.grafana_oauth_client_id.j2
  45. 0 1
      library/compose/grafana/.env.secret.grafana_oauth_client_secret.j2
  46. 58 35
      library/compose/grafana/compose.yaml.j2
  47. 1 0
      library/compose/grafana/template.yaml
  48. 13 0
      library/compose/heimdall/compose.yaml.j2
  49. 58 42
      library/compose/homepage/compose.yaml.j2
  50. 2 1
      library/compose/homepage/template.yaml
  51. 42 35
      library/compose/homer/compose.yaml.j2
  52. 2 0
      library/compose/homer/template.yaml
  53. 33 27
      library/compose/influxdb/compose.yaml.j2
  54. 1 0
      library/compose/influxdb/template.yaml
  55. 138 0
      library/compose/komodo/compose.yaml.j2.final
  56. 130 0
      library/compose/komodo/template.yaml.backup
  57. 154 0
      library/compose/loki/compose.yaml.j2.final
  58. 148 0
      library/compose/loki/compose.yaml.j2.portfix
  59. 46 0
      library/compose/loki/template.yaml.backup
  60. 84 43
      library/compose/mariadb/compose.yaml.j2
  61. 2 2
      library/compose/mariadb/template.yaml
  62. 36 23
      library/compose/n8n-worker/compose.yaml.j2
  63. 2 12
      library/compose/n8n-worker/template.yaml
  64. 0 296
      library/compose/n8n/compose.yaml.j2
  65. 305 0
      library/compose/n8n/compose.yaml.j2.bak3
  66. 302 0
      library/compose/n8n/compose.yaml.j2.final
  67. 2 12
      library/compose/n8n/template.yaml
  68. 1 0
      library/compose/netbox/.env.secret.database_password.j2
  69. 1 0
      library/compose/netbox/.env.secret.email_password.j2
  70. 1 0
      library/compose/netbox/.env.secret.netbox_secret_key.j2
  71. 1 0
      library/compose/netbox/.env.secret.redis_password.j2
  72. 18 0
      library/compose/netbox/common/networks.yaml.j2
  73. 15 0
      library/compose/netbox/common/secrets.yaml.j2
  74. 64 0
      library/compose/netbox/common/volumes.yaml.j2
  75. 23 282
      library/compose/netbox/compose.yaml.j2
  76. 205 0
      library/compose/netbox/services/netbox.yaml.j2
  77. 28 0
      library/compose/netbox/services/postgres.yaml.j2
  78. 88 0
      library/compose/netbox/services/redis.yaml.j2
  79. 101 0
      library/compose/netbox/services/worker.yaml.j2
  80. 102 99
      library/compose/netbox/template.yaml
  81. 1 46
      library/compose/nextcloud/compose.yaml.j2
  82. 169 0
      library/compose/nextcloud/compose.yaml.j2.bak3
  83. 142 0
      library/compose/nextcloud/compose.yaml.j2.final
  84. 124 0
      library/compose/nextcloud/compose.yaml.j2.portfix
  85. 36 27
      library/compose/nginx/compose.yaml.j2
  86. 1 0
      library/compose/nginx/template.yaml
  87. 0 40
      library/compose/nginxproxymanager/compose.yaml.j2
  88. 188 0
      library/compose/nginxproxymanager/compose.yaml.j2.bak3
  89. 32 32
      library/compose/openwebui/compose.yaml.j2
  90. 1 0
      library/compose/openwebui/template.yaml
  91. 137 0
      library/compose/pangolin/compose.yaml.j2.final
  92. 113 0
      library/compose/pangolin/template.yaml.backup
  93. 1 0
      library/compose/pihole/.env.secret.webpassword.j2
  94. 38 0
      library/compose/pihole/common/networks.yaml.j2
  95. 3 0
      library/compose/pihole/common/secrets.yaml.j2
  96. 21 0
      library/compose/pihole/common/volumes.yaml.j2
  97. 11 196
      library/compose/pihole/compose.yaml.j2
  98. 266 0
      library/compose/pihole/compose.yaml.j2.backup
  99. 133 0
      library/compose/pihole/services/pihole.yaml.j2
  100. 3 26
      library/compose/pihole/template.yaml

+ 1 - 1
.github/workflows/docs-update-wiki.yaml

@@ -82,7 +82,7 @@ jobs:
           git config user.name "github-actions[bot]"
           git config user.email "github-actions[bot]@users.noreply.github.com"
           git commit -m "Auto-update wiki pages"
-          
+
           # Pull with rebase to handle any remote changes, then push
           # GitHub wikis use master as default branch
           git pull --rebase origin master

+ 1 - 0
AGENTS.md

@@ -460,6 +460,7 @@ class ExampleModule(Module):
 - **Jinja2 Templates (`.j2`)**: Rendered by Jinja2, `.j2` extension removed in output. Support `{% include %}` and `{% import %}`.
 - **Static Files**: Non-`.j2` files copied as-is.
 - **Sanitization**: Auto-sanitized (single blank lines, no leading blanks, trimmed whitespace, single trailing newline).
+- **Shortcodes**: Template descriptions support emoji-style shortcodes (e.g., `:warning:`, `:info:`, `:docker:`) which are automatically replaced with Nerd Font icons during display. Add new shortcodes to `IconManager.SHORTCODES` dict.
 
 ### Docker Compose Best Practices
 

+ 1 - 0
CHANGELOG.md

@@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Changed
 - Schema is now managed in JSON for better standardization and clarity (#1555)
+- Compose Schema 1.2: Removed `traefik_entrypoint` and `traefik_tls_entrypoint` variables
 - Removed Jinja2 `| default()` filter extraction and merging (#1410) - All defaults must now be defined in template/module specs
 - Refactored code quality (#1364) for all core modules from single files to package structure with specific submodules
 - Improved debug logging to capture module discovery and registration during initialization

+ 8 - 0
cli/core/display/display_base.py

@@ -155,6 +155,14 @@ class BaseDisplay:
         table.header_style = self.settings.STYLE_TABLE_HEADER
         console.print(table)
 
+    def _print_markdown(self, markdown) -> None:
+        """Print a pre-built Rich Markdown object.
+
+        Args:
+            markdown: Rich Markdown object to print
+        """
+        console.print(markdown)
+
     def code(self, code_text: str, language: str | None = None) -> None:
         """Display code with optional syntax highlighting.
 

+ 48 - 0
cli/core/display/display_icons.py

@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 from pathlib import Path
+from typing import ClassVar
 
 
 class IconManager:
@@ -46,6 +47,32 @@ class IconManager:
     UI_LIBRARY_GIT = "\uf418"  #  (git icon)
     UI_LIBRARY_STATIC = "\uf07c"  #  (folder icon)
 
+    # Shortcode Mappings (emoji-style codes to Nerd Font icons)
+    # Format: ":code:" -> "\uf000"
+    # To add new shortcodes:
+    # 1. Add entry to this dict: ":mycode:": "\uf000"
+    # 2. Use in template descriptions: ":mycode: Some text"
+    # 3. Shortcode will be automatically replaced when markdown is rendered
+    # Find Nerd Font codes at: https://www.nerdfonts.com/cheat-sheet
+    SHORTCODES: ClassVar[dict[str, str]] = {
+        ":warning:": "\uf071",  #  (exclamation-triangle)
+        ":info:": "\uf05a",  #  (info-circle)
+        ":check:": "\uf00c",  #  (check)
+        ":error:": "\uf00d",  #  (times/x)
+        ":lock:": "\uf084",  #  (lock)
+        ":folder:": "\uf07b",  #  (folder)
+        ":file:": "\uf15b",  #  (file)
+        ":gear:": "\uf013",  #  (settings/gear)
+        ":rocket:": "\uf135",  #  (rocket)
+        ":star:": "\uf005",  #  (star)
+        ":lightning:": "\uf0e7",  #  (bolt/lightning)
+        ":cloud:": "\uf0c2",  #  (cloud)
+        ":database:": "\uf1c0",  #  (database)
+        ":network:": "\uf6ff",  #  (network)
+        ":docker:": "\uf308",  #  (docker)
+        ":kubernetes:": "\ue287",  #  (kubernetes/helm)
+    }
+
     @classmethod
     def get_file_icon(cls, file_path: str | Path) -> str:
         """Get the appropriate icon for a file based on its extension or name.
@@ -136,3 +163,24 @@ class IconManager:
     def arrow_right(cls) -> str:
         """Get the right arrow icon (for showing transitions/changes)."""
         return cls.UI_ARROW_RIGHT
+
+    @classmethod
+    def replace_shortcodes(cls, text: str) -> str:
+        """Replace emoji-style shortcodes with Nerd Font icons.
+
+        Args:
+            text: Text containing shortcodes like :warning:, :info:, etc.
+
+        Returns:
+            Text with shortcodes replaced by Nerd Font icons
+
+        Examples:
+            >>> IconManager.replace_shortcodes(":warning: This is a warning")
+            ' This is a warning'
+            >>> IconManager.replace_shortcodes(":docker: :kubernetes: Stack")
+            '  Stack'
+        """
+        result = text
+        for shortcode, icon in cls.SHORTCODES.items():
+            result = result.replace(shortcode, icon)
+        return result

+ 7 - 3
cli/core/display/display_status.py

@@ -204,9 +204,13 @@ class StatusDisplay:
     def markdown(self, content: str) -> None:
         """Render markdown content with left-aligned headings.
 
+        Replaces emoji-style shortcodes (e.g., :warning:, :info:) with Nerd Font icons
+        before rendering.
+
         Args:
-            content: Markdown-formatted text to render
+            content: Markdown-formatted text to render (may contain shortcodes)
         """
         if not self.quiet:
-            console = Console()
-            console.print(LeftAlignedMarkdown(content))
+            # Replace shortcodes with Nerd Font icons before rendering
+            processed_content = IconManager.replace_shortcodes(content)
+            self.base._print_markdown(LeftAlignedMarkdown(processed_content))

+ 4 - 1
cli/core/display/display_variable.py

@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import logging
 from typing import TYPE_CHECKING
 
 from rich.table import Table
@@ -7,6 +8,8 @@ from rich.table import Table
 from .display_icons import IconManager
 from .display_settings import DisplaySettings
 
+logger = logging.getLogger(__name__)
+
 if TYPE_CHECKING:
     from ..template import Template
     from .display_base import BaseDisplay
@@ -74,7 +77,7 @@ class VariableDisplay:
                 curr = str(variable.value) if variable.value else settings.TEXT_EMPTY_OVERRIDE
             arrow = IconManager.arrow_right()
             color = settings.COLOR_WARNING
-            return f"{orig} [bold {color}]{arrow} {curr}[/bold {color}]"
+            return f"[dim]{orig}[/dim] [bold {color}]{arrow} {curr}[/bold {color}]"
 
         # Default formatting
         settings = self.settings

+ 13 - 27
cli/core/schema/compose/v1.2.json

@@ -115,14 +115,14 @@
   {
     "key": "ports",
     "title": "Ports",
-    "needs": "network_mode!=host,macvlan",
+    "needs": ["network_mode!=host,macvlan"],
     "description": "Expose service ports to the host.",
     "vars": [
       {
         "name": "ports_http",
         "description": "HTTP port on host",
         "type": "int",
-        "needs": "traefik_enabled=false",
+        "needs": ["traefik_enabled=false"],
         "default": 8080,
         "required": true
       },
@@ -130,7 +130,7 @@
         "name": "ports_https",
         "description": "HTTPS port on host",
         "type": "int",
-        "needs": "traefik_enabled=false",
+        "needs": ["traefik_enabled=false"],
         "default": 8443,
         "required": true
       },
@@ -168,7 +168,7 @@
     "key": "traefik",
     "title": "Traefik",
     "toggle": "traefik_enabled",
-    "needs": "network_mode!=host,macvlan",
+    "needs": ["network_mode!=host,macvlan"],
     "description": "Traefik routes external traffic to your service.",
     "vars": [
       {
@@ -196,13 +196,6 @@
         "type": "str",
         "default": "home.arpa",
         "required": true
-      },
-      {
-        "name": "traefik_entrypoint",
-        "description": "HTTP entrypoint (non-TLS)",
-        "type": "str",
-        "default": "web",
-        "required": true
       }
     ]
   },
@@ -210,7 +203,7 @@
     "key": "traefik_tls",
     "title": "Traefik TLS/SSL",
     "toggle": "traefik_tls_enabled",
-    "needs": "traefik_enabled=true;network_mode!=host,macvlan",
+    "needs": ["traefik_enabled=true", "network_mode!=host,macvlan"],
     "description": "Enable HTTPS/TLS for Traefik with certificate management.",
     "vars": [
       {
@@ -219,13 +212,6 @@
         "type": "bool",
         "default": true
       },
-      {
-        "name": "traefik_tls_entrypoint",
-        "description": "TLS entrypoint",
-        "type": "str",
-        "default": "websecure",
-        "required": true
-      },
       {
         "name": "traefik_tls_certresolver",
         "description": "Traefik certificate resolver name",
@@ -246,14 +232,14 @@
         "type": "enum",
         "options": ["local", "mount", "nfs"],
         "default": "local",
-        "extra": "local: Docker-managed volumes | mount: Bind mount from host | nfs: Network filesystem"
+        "required": true
       },
       {
         "name": "volume_mount_path",
         "description": "Host path for bind mounts",
         "type": "str",
         "default": "/mnt/storage",
-        "needs": "volume_mode=mount",
+        "needs": ["volume_mode=mount"],
         "required": true
       },
       {
@@ -261,7 +247,7 @@
         "description": "NFS server address",
         "type": "str",
         "default": "192.168.1.1",
-        "needs": "volume_mode=nfs",
+        "needs": ["volume_mode=nfs"],
         "required": true
       },
       {
@@ -269,7 +255,7 @@
         "description": "NFS export path",
         "type": "str",
         "default": "/export",
-        "needs": "volume_mode=nfs",
+        "needs": ["volume_mode=nfs"],
         "required": true
       },
       {
@@ -277,7 +263,7 @@
         "description": "NFS mount options (comma-separated)",
         "type": "str",
         "default": "rw,nolock,soft",
-        "needs": "volume_mode=nfs",
+        "needs": ["volume_mode=nfs"],
         "required": true
       }
     ]
@@ -306,7 +292,7 @@
         "description": "Reserved CPU cores",
         "type": "str",
         "default": "0.25",
-        "needs": "swarm_enabled=true",
+        "needs": ["swarm_enabled=true"],
         "required": true
       },
       {
@@ -321,7 +307,7 @@
         "description": "Reserved memory",
         "type": "str",
         "default": "512M",
-        "needs": "swarm_enabled=true",
+        "needs": ["swarm_enabled=true"],
         "required": true
       }
     ]
@@ -330,7 +316,7 @@
     "key": "swarm",
     "title": "Docker Swarm",
     "toggle": "swarm_enabled",
-    "needs": "network_mode!=host,macvlan",
+    "needs": ["network_mode!=host,macvlan"],
     "description": "Deploy service in Docker Swarm mode.",
     "vars": [
       {

+ 76 - 3
cli/core/schema/terraform/v1.0.json

@@ -5,9 +5,82 @@
     "required": true,
     "vars": [
       {
-        "name": "playbook_name",
-        "description": "Ansible playbook name",
-        "type": "str"
+        "name": "resource_name",
+        "description": "Terraform resource name (alphanumeric and underscores only)",
+        "type": "str",
+        "default": "resource"
+      }
+    ]
+  },
+  {
+    "key": "depends_on",
+    "title": "Dependencies",
+    "toggle": "depends_on_enabled",
+    "required": false,
+    "vars": [
+      {
+        "name": "depends_on_enabled",
+        "description": "Enable resource dependencies",
+        "type": "bool",
+        "default": false
+      },
+      {
+        "name": "dependencies",
+        "description": "Comma-separated list of resource dependencies",
+        "type": "str",
+        "default": ""
+      }
+    ]
+  },
+  {
+    "key": "lifecycle",
+    "title": "Lifecycle",
+    "toggle": "lifecycle_enabled",
+    "required": false,
+    "vars": [
+      {
+        "name": "lifecycle_enabled",
+        "description": "Enable lifecycle rules",
+        "type": "bool",
+        "default": false
+      },
+      {
+        "name": "prevent_destroy",
+        "description": "Prevent resource destruction",
+        "type": "bool",
+        "default": false
+      },
+      {
+        "name": "create_before_destroy",
+        "description": "Create replacement before destroying",
+        "type": "bool",
+        "default": false
+      },
+      {
+        "name": "ignore_changes",
+        "description": "Comma-separated list of attributes to ignore changes for",
+        "type": "str",
+        "default": ""
+      }
+    ]
+  },
+  {
+    "key": "tags",
+    "title": "Tags",
+    "toggle": "tags_enabled",
+    "required": false,
+    "vars": [
+      {
+        "name": "tags_enabled",
+        "description": "Enable resource tags",
+        "type": "bool",
+        "default": false
+      },
+      {
+        "name": "tags_json",
+        "description": "Resource tags in JSON format (e.g., {\"Environment\": \"Production\"})",
+        "type": "str",
+        "default": "{}"
       }
     ]
   }

+ 31 - 6
cli/core/template/template.py

@@ -501,6 +501,8 @@ class Template:
         """
         used_variables: set[str] = set()
         syntax_errors = []
+        # Track which file uses which variable (for debugging)
+        self._variable_usage_map: dict[str, list[str]] = {}
 
         for template_file in self.template_files:  # Iterate over TemplateFile objects
             if template_file.file_type == "j2":
@@ -509,7 +511,13 @@ class Template:
                     with file_path.open(encoding="utf-8") as f:
                         content = f.read()
                         ast = self.jinja_env.parse(content)  # Use lazy-loaded jinja_env
-                        used_variables.update(meta.find_undeclared_variables(ast))
+                        file_vars = meta.find_undeclared_variables(ast)
+                        used_variables.update(file_vars)
+                        # Track which file uses each variable
+                        for var in file_vars:
+                            if var not in self._variable_usage_map:
+                                self._variable_usage_map[var] = []
+                            self._variable_usage_map[var].append(str(template_file.relative_path))
                 except OSError as e:
                     relative_path = file_path.relative_to(self.template_dir)
                     syntax_errors.append(f"  - {relative_path}: File I/O error: {e}")
@@ -613,10 +621,25 @@ class Template:
         undefined_variables = used_variables - defined_variables
         if undefined_variables:
             undefined_list = sorted(undefined_variables)
+            
+            # Build file location info for each undefined variable
+            file_locations = []
+            for var_name in undefined_list:
+                if hasattr(self, '_variable_usage_map') and var_name in self._variable_usage_map:
+                    files = self._variable_usage_map[var_name]
+                    file_locations.append(f"  • {var_name}: {', '.join(files)}")
+            
             error_msg = (
                 f"Template validation error in '{self.id}': "
-                f"Variables used in template content but not defined in spec: {undefined_list}\n\n"
-                f"Please add these variables to your template's template.yaml spec. "
+                f"Variables used in template content but not defined in spec:\n"
+            )
+            if file_locations:
+                error_msg += "\n".join(file_locations) + "\n"
+            else:
+                error_msg += f"{undefined_list}\n"
+            
+            error_msg += (
+                f"\nPlease add these variables to your template's template.yaml spec. "
                 f"Each variable must have a default value.\n\n"
                 f"Example:\n"
                 f"spec:\n"
@@ -630,7 +653,8 @@ class Template:
                     f"        description: Description for {var_name}\n"
                     f"        default: <your_default_value_here>\n"
                 )
-            logger.error(error_msg)
+            # Only log at DEBUG level - the exception will be displayed to user
+            logger.debug(error_msg)
             raise TemplateValidationError(error_msg)
 
     @staticmethod
@@ -755,8 +779,9 @@ class Template:
             if template_file.file_type == "j2":
                 try:
                     content = self._render_jinja2_file(template_file, variable_values, available_vars, debug)
-                    # Skip empty files (only whitespace or empty string)
-                    if content.strip():
+                    # Skip empty files (only whitespace, empty string, or just YAML document separator)
+                    stripped = content.strip()
+                    if stripped and stripped != "---":
                         rendered_files[str(template_file.output_path)] = content
                     else:
                         skipped_files.append(str(template_file.output_path))

+ 5 - 0
cli/core/template/variable_collection.py

@@ -903,6 +903,11 @@ class VariableCollection:
             if variable.autogenerated and not variable.value:
                 return
 
+            # Skip variables with unsatisfied needs (even if required)
+            if not self.is_variable_satisfied(var_name):
+                logger.debug(f"Skipping validation for variable '{var_name}' - needs not satisfied")
+                return
+
             # Check required fields
             if variable.value is None:
                 if variable.is_required():

+ 39 - 0
library/compose/adguardhome/common/networks.yaml.j2

@@ -0,0 +1,39 @@
+---
+{#
+  Network definitions (only when needed):
+  - When network_mode is empty: no definition needed (uses Docker's default bridge)
+  - When network_mode is 'bridge': define custom bridge network
+  - When network_mode is 'macvlan': configure macvlan with static IP (for Compose mode)
+    Note: In Swarm mode, macvlan networks must be created manually with config-only networks on each node
+  - When swarm_enabled: use overlay network for multi-host communication
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %}
+networks:
+  {% if network_mode == 'bridge' or network_mode == 'macvlan'%}
+  {{ network_name }}:
+    {% if network_external %}
+    external: true
+    {% else %}
+    {% if network_mode == 'macvlan' %}
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+    name: {{ network_name }}
+    {% elif swarm_enabled %}
+    driver: overlay
+    attachable: true
+    {% else %}
+    driver: bridge
+    {% endif %}
+    {% endif %}
+  {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}

+ 28 - 0
library/compose/adguardhome/common/volumes.yaml.j2

@@ -0,0 +1,28 @@
+---
+{#
+  Volume definitions:
+  - When volume_mode is 'local' (default): use docker-managed local volumes
+  - When volume_mode is 'nfs': configure NFS-backed volumes
+  - When volume_mode is 'mount': no volume definition needed (bind mounts used directly)
+#}
+{% if volume_mode == 'local' %}
+volumes:
+  {{ service_name }}-work:
+    driver: local
+  {{ service_name }}-conf:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-work:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/work"
+  {{ service_name }}-conf:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/conf"
+{% endif %}

+ 14 - 204
library/compose/adguardhome/compose.yaml.j2

@@ -1,206 +1,16 @@
 ---
-services:
-  {{ service_name }}:
-    image: docker.io/adguard/adguardhome:latest
-    {% if not swarm_enabled %}
-    restart: {{ restart_policy }}
-    container_name: {{ container_name }}
-    {% endif %}
-    hostname: {{ container_hostname }}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if traefik_enabled %}
-      {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
-    {% endif %}
-    {% if network_mode == 'bridge' %}
-    ports:
-      {% if not traefik_enabled %}
-      {% if swarm_enabled %}
-      - target: 80
-        published: {{ ports_http }}
-        protocol: tcp
-        mode: host
-      - target: 443
-        published: {{ ports_https }}
-        protocol: tcp
-        mode: host
-      - target: 3000
-        published: 3000
-        protocol: tcp
-        mode: host
-      {% else %}
-      - "{{ ports_http }}:80/tcp"
-      - "{{ ports_https }}:443/tcp"
-      - "3000:3000/tcp"
-      {% endif %}
-      {% endif %}
-      {% if swarm_enabled %}
-      - target: 53
-        published: 53
-        protocol: tcp
-        mode: host
-      - target: 53
-        published: 53
-        protocol: udp
-        mode: host
-      - target: 67
-        published: 67
-        protocol: udp
-        mode: host
-      - target: 68
-        published: 68
-        protocol: udp
-        mode: host
-      - target: 853
-        published: 853
-        protocol: tcp
-        mode: host
-      - target: 853
-        published: 853
-        protocol: udp
-        mode: host
-      - target: 5443
-        published: 5443
-        protocol: tcp
-        mode: host
-      - target: 5443
-        published: 5443
-        protocol: udp
-        mode: host
-      {% else %}
-      - "53:53/tcp"
-      - "53:53/udp"
-      - "67:67/udp"
-      - "68:68/udp"
-      - "853:853/tcp"
-      - "853:853/udp"
-      - "5443:5443/tcp"
-      - "5443:5443/udp"
-      {% endif %}
-    {% endif %}
-    volumes:
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/work:/opt/adguardhome/work:rw
-      - {{ volume_mount_path }}/conf:/opt/adguardhome/conf:rw
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-work:/opt/adguardhome/work
-      - {{ service_name }}-conf:/opt/adguardhome/conf
-      {% endif %}
-    cap_add:
-      - NET_ADMIN
-      - NET_BIND_SERVICE
-      - NET_RAW
-    {% if swarm_enabled or resources_enabled %}
-    deploy:
-      {% if swarm_enabled %}
-      mode: replicated
-      replicas: 1
-      placement:
-        constraints:
-          - node.hostname == {{ swarm_placement_host }}
-      restart_policy:
-        condition: on-failure
-      {% endif %}
-      {% if resources_enabled %}
-      resources:
-        limits:
-          cpus: '{{ resources_cpu_limit }}'
-          memory: {{ resources_memory_limit }}
-        {% if swarm_enabled %}
-        reservations:
-          cpus: '{{ resources_cpu_reservation }}'
-          memory: {{ resources_memory_reservation }}
-        {% endif %}
-      {% endif %}
-      {% if swarm_enabled and traefik_enabled %}
-      labels:
-        - traefik.enable=true
-        - traefik.docker.network={{ traefik_network }}
-        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
-        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
-        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
-        {% if traefik_tls_enabled %}
-        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
-        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
-        - traefik.http.routers.{{ service_name }}-https.tls=true
-        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
-        {% endif %}
-      {% endif %}
-    {% endif %}
-    {% if traefik_enabled and not swarm_enabled %}
-    labels:
-      - traefik.enable=true
-      - traefik.docker.network={{ traefik_network }}
-      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
-      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
-      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
-      {% if traefik_tls_enabled %}
-      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
-      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
-      - traefik.http.routers.{{ service_name }}-https.tls=true
-      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
-      {% endif %}
-    {% endif %}
-
-{% if volume_mode == 'local' %}
-volumes:
-  {{ service_name }}-work:
-    driver: local
-  {{ service_name }}-conf:
-    driver: local
-{% elif volume_mode == 'nfs' %}
-volumes:
-  {{ service_name }}-work:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/work"
-  {{ service_name }}-conf:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/conf"
-{% endif %}
-
-{% if network_mode != 'host' %}
-networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-  {% if traefik_enabled %}
-  {{ traefik_network }}:
-    external: true
+{#
+  AdGuard Home Docker Compose Configuration
+  
+  This is the main orchestration file that includes all service definitions.
+  Services are split into separate files for better maintainability:
+  - services/adguardhome.yaml: Main AdGuard Home service
+  - common/networks.yaml: Network definitions
+  - common/volumes.yaml: Volume definitions
+#}
+include:
+  - services/adguardhome.yaml
+  {% if network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %}
+  - common/networks.yaml
   {% endif %}
-{% endif %}
+  - common/volumes.yaml

+ 157 - 0
library/compose/adguardhome/services/adguardhome.yaml.j2

@@ -0,0 +1,157 @@
+---
+services:
+  {{ service_name }}:
+    image: docker.io/adguard/adguardhome:v0.107.69
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    {% if container_name %}
+    container_name: {{ container_name }}
+    {% endif %}
+    {% endif %}
+    {% if container_hostname %}
+    hostname: {{ container_hostname }}
+    {% endif %}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% elif network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if network_mode == '' or network_mode == 'bridge' or traefik_enabled %}
+    ports:
+      {% if not traefik_enabled %}
+      {% if swarm_enabled %}
+      - target: 80
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      - target: 443
+        published: {{ ports_https }}
+        protocol: tcp
+        mode: host
+      {% if initial_setup %}
+      - target: 3000
+        published: {{ ports_initial }}
+        protocol: tcp
+        mode: host
+      {% endif %}
+      {% else %}
+      - "{{ ports_http }}:80/tcp"
+      - "{{ ports_https }}:443/tcp"
+      {% if initial_setup %}
+      - "{{ ports_initial }}:3000/tcp"
+      {% endif %}
+      {% endif %}
+      {% endif %}
+      {% if swarm_enabled %}
+      - target: 443
+        published: {{ ports_https }}
+        protocol: udp
+        mode: host
+      - target: 53
+        published: {{ ports_dns }}
+        protocol: tcp
+        mode: host
+      - target: 53
+        published: {{ ports_dns }}
+        protocol: udp
+        mode: host
+      - target: 853
+        published: {{ ports_tls }}
+        protocol: tcp
+        mode: host
+      - target: 5443
+        published: {{ ports_dnscrypt }}
+        protocol: tcp
+        mode: host
+      - target: 5443
+        published: {{ ports_dnscrypt }}
+        protocol: udp
+        mode: host
+      {% else %}
+      - "{{ ports_https }}:443/udp"
+      - "{{ ports_dns }}:53/tcp"
+      - "{{ ports_dns }}:53/udp"
+      - "{{ ports_tls }}:853/tcp"
+      - "{{ ports_dnscrypt }}:5443/tcp"
+      - "{{ ports_dnscrypt }}:5443/udp"
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/work:/opt/adguardhome/work:rw
+      - {{ volume_mount_path }}/conf:/opt/adguardhome/conf:rw
+      {% else  %}
+      - {{ service_name }}-work:/opt/adguardhome/work
+      - {{ service_name }}-conf:/opt/adguardhome/conf
+      {% endif %}
+    cap_add:
+      - NET_ADMIN
+      - NET_BIND_SERVICE
+      - NET_RAW
+    {% if swarm_enabled %}
+    deploy:
+      mode: replicated
+      replicas: 1
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      restart_policy:
+        condition: on-failure
+      {% if traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+        {% if initial_setup %}
+        - traefik.http.services.{{ service_name }}-setup.loadBalancer.server.port=3000
+        - traefik.http.routers.{{ service_name }}-setup.service={{ service_name }}-setup
+        - traefik.http.routers.{{ service_name }}-setup.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`) && PathPrefix(`/setup`)
+        - traefik.http.routers.{{ service_name }}-setup.entrypoints={{ traefik_entrypoint }}
+        - traefik.http.middlewares.{{ service_name }}-setup-strip.stripprefix.prefixes=/setup
+        - traefik.http.routers.{{ service_name }}-setup.middlewares={{ service_name }}-setup-strip
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+      {% if initial_setup %}
+      - traefik.http.services.{{ service_name }}-setup.loadBalancer.server.port=3000
+      - traefik.http.routers.{{ service_name }}-setup.service={{ service_name }}-setup
+      - traefik.http.routers.{{ service_name }}-setup.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`) && PathPrefix(`/setup`)
+      - traefik.http.routers.{{ service_name }}-setup.entrypoints={{ traefik_entrypoint }}
+      - traefik.http.middlewares.{{ service_name }}-setup-strip.stripprefix.prefixes=/setup
+      - traefik.http.routers.{{ service_name }}-setup.middlewares={{ service_name }}-setup-strip
+      {% endif %}
+    {% endif %}

+ 47 - 31
library/compose/adguardhome/template.yaml

@@ -3,23 +3,25 @@ kind: compose
 schema: "1.2"
 metadata:
   name: AdGuard Home
-  description: |
+  description: |-
     Network-wide software for blocking ads and tracking. AdGuard Home operates as a DNS server that
     re-routes tracking domains to a "black hole", thus preventing your devices from connecting to those servers.
     It features advanced DNS filtering, parental controls, safe browsing, and HTTPS/DNS-over-TLS/DNS-over-QUIC support.
-    ## Swarm Deployment Warning
-    AdGuard Home uses local storage and configuration files and does NOT support running multiple replicas.
+    
+    :warning: In Docker Swarm, AdGuard Home does NOT support running multiple replicas.
     This template enforces a single replica with node placement constraints to ensure stable DNS resolution.
     ## References
-    * **Project:** https://adguard.com/adguard-home/overview.html
-    * **Documentation:** https://github.com/AdguardTeam/AdGuardHome/wiki
-    * **GitHub:** https://github.com/AdguardTeam/AdGuardHome
-  version: latest
+    - **Project:** https://adguard.com/adguard-home/overview.html
+    - **Documentation:** https://github.com/AdguardTeam/AdGuardHome/wiki
+    - **GitHub:** https://github.com/AdguardTeam/AdGuardHome
+  version: v0.107.69
   author: Christian Lempa
   date: '2025-11-13'
   tags:
     - traefik
     - swarm
+    - network_modes
+    - volume_modes
   next_steps: |
     ### 1. Deploy the Service
     {% if swarm_enabled -%}
@@ -33,14 +35,25 @@ metadata:
     docker compose up -d
     ```
     {% endif -%}
+    {% if initial_setup -%}
     ### 2. Initial Setup
+    **NOTE:** Port {{ ports_initial }} is only used for the initial setup wizard. After completing the setup, AdGuard Home will automatically switch to port {{ ports_http }} for the web interface, and port {{ ports_initial }} will become inactive. Make sure to switch the `initial_setup` variable to `false` after the initial configuration to avoid exposing the setup wizard.
     {% if traefik_enabled -%}
-    * Navigate to: **http://{{ traefik_host }}.{{ traefik_domain }}:3000**
+    * Navigate to: **http://{{ traefik_host }}.{{ traefik_domain }}/setup**
     {% else -%}
-    * Navigate to: **http://localhost:3000**
+    * Navigate to: **http://localhost:{{ ports_initial }}**
     {% endif -%}
-    * Complete the initial setup wizard to configure admin credentials and DNS settings.
+    * Complete the initial setup wizard to configure:
+      - Admin credentials (username and password)
+      - DNS server settings and upstream DNS providers
+      - Network interface bindings
+      - Optional features (DHCP, DNS-over-HTTPS, etc.)
+    * **IMPORTANT:** During setup, configure the web interface to use port {{ ports_http }}.
     ### 3. Access the Web Interface
+    After completing the initial setup:
+    {% else -%}
+    ### 2. Access the Web Interface
+    {% endif -%}
     {% if traefik_enabled -%}
     * Navigate to: **https://{{ traefik_host }}.{{ traefik_domain }}**
     {% else -%}
@@ -52,14 +65,16 @@ spec:
     vars:
       service_name:
         default: "adguardhome"
-      container_name:
-        default: "adguardhome"
-      container_hostname:
-        default: "adguardhome"
+      initial_setup:
+        description: "Enable initial setup wizard on port 3000"
+        type: bool
+        default: true
+        extra: >
+          Port 3000 is only used during the initial setup wizard.
+          After completing setup, AdGuard Home switches to the configured HTTP port and port 3000 becomes inactive.
+          Set to false if you've already completed the initial setup.
   traefik:
     vars:
-      traefik_enabled:
-        needs: "network_mode=bridge"
       traefik_host:
         default: "adguardhome"
   network:
@@ -73,20 +88,21 @@ spec:
   ports:
     vars:
       ports_http:
-        description: "External HTTP port (admin panel)"
-        type: int
-        default: 3080
-        needs: ["traefik_enabled=false", "network_mode=bridge"]
+        default: 80
       ports_https:
-        description: "External HTTPS port"
+        default: 443
+      ports_initial:
+        description: "Initial setup wizard port"
         type: int
-        default: 3443
-        needs: ["traefik_enabled=false", "network_mode=bridge"]
-  swarm:
-    vars:
-      swarm_enabled:
-        needs: "network_mode=bridge"
-      swarm_placement_host:
-        required: true
-        optional: false
-        needs: null
+        default: 3000
+        needs: ["traefik_enabled=false", "initial_setup=true"]
+        extra: >
+          Only used during first-time setup. After configuration, port becomes inactive.
+      ports_tls:
+        description: "DNS over TLS Port"
+        type: "int"
+        default: 853
+      ports_dnscrypt:
+        description: "DNSCrypt Port"
+        type: "int"
+        default: 5443

+ 51 - 35
library/compose/alloy/compose.yaml.j2

@@ -1,31 +1,41 @@
 services:
   {{ service_name }}:
     image: grafana/alloy:v1.11.3
+    {#
+      If not in swarm mode, check whether container_name is set and apply restart policy,
+      else swarm mode handles restarts via deploy.restart_policy
+    #}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     container_name: {{ container_name }}
     {% endif %}
+    {#
+      Set container hostname for identification in metrics/logs
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Alloy command configuration:
+      - HTTP server listens on 0.0.0.0:12345
+      - Data storage in /var/lib/alloy/data
+      - Configuration file at /etc/alloy/config.alloy
+    #}
     command:
       - run
       - --server.http.listen-addr=0.0.0.0:12345
       - --storage.path=/var/lib/alloy/data
       - /etc/alloy/config.alloy
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
     networks:
-      {% if traefik_enabled %}
       {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
+    {#
+      Port mappings for HTTP server (only when Traefik is disabled)
+      Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
+    #}
+    {% if not traefik_enabled %}
     ports:
       {% if swarm_enabled %}
       - target: 12345
@@ -36,6 +46,12 @@ services:
       - "{{ ports_main }}:12345"
       {% endif %}
     {% endif %}
+    {#
+      Volume configuration:
+      - Config file: Alloy configuration (mounted from local directory)
+      - Data: Persistent storage for Alloy data
+      - System mounts: Required for log/metric collection from host
+    #}
     volumes:
       - ./config/config.alloy:/etc/alloy/config.alloy
       - alloy_data:/var/lib/alloy/data
@@ -53,6 +69,10 @@ services:
       {% if metrics_enabled and metrics_system %}
       - /run/udev/data:/run/udev/data:ro
       {% endif %}
+    {#
+      When traefik_enabled is set, and not running in swarm mode, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
     {% if traefik_enabled and not swarm_enabled %}
     labels:
       - traefik.enable=true
@@ -69,6 +89,12 @@ services:
       - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
       {% endif %}
     {% endif %}
+    {#
+      Deploy configuration for Swarm mode and/or resource limits:
+      - Swarm: Configure replicas, placement constraints, and restart policy
+      - Resources: Set CPU/memory limits (and reservations in Swarm mode)
+      - Traefik: Labels for reverse proxy integration (Swarm mode)
+    #}
     {% if swarm_enabled or resources_enabled %}
     deploy:
       {% if swarm_enabled %}
@@ -95,6 +121,10 @@ services:
           memory: {{ resources_memory_reservation }}
         {% endif %}
       {% endif %}
+      {#
+        When traefik_enabled is set in swarm mode, add traefik labels
+        (optionally enable TLS if traefik_tls_enabled is set, and authentik middleware if enabled)
+      #}
       {% if swarm_enabled and traefik_enabled %}
       labels:
         - traefik.enable=true
@@ -119,34 +149,20 @@ services:
       {% endif %}
     {% endif %}
 
+{#
+  Volume definitions:
+  - alloy_data: Persistent storage for Alloy data
+#}
 volumes:
   alloy_data:
     driver: local
 
-{% if network_mode != 'host' %}
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
 {% endif %}

+ 2 - 4
library/compose/alloy/template.yaml

@@ -20,6 +20,8 @@ metadata:
   date: '2025-10-13'
   tags:
     - traefik
+    - swarm
+    - authentik
 spec:
   general:
     vars:
@@ -34,10 +36,6 @@ spec:
         type: int
         description: Main port for Alloy HTTP server
         default: 12345
-  traefik:
-    vars:
-      traefik_host:
-        default: alloy.home.arpa
   logs:
     name: Log Collection
     description: Configure log collection for Docker containers and system logs

+ 9 - 0
library/compose/authentik/common/networks.yaml.j2

@@ -0,0 +1,9 @@
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
+networks:
+  {{ traefik_network }}:
+    external: true
+{% endif %}

+ 55 - 0
library/compose/authentik/common/volumes.yaml.j2

@@ -0,0 +1,55 @@
+{#
+  Volume definitions:
+  - When volume_mode is 'local' (default): use docker-managed local volumes
+  - When volume_mode is 'nfs': configure NFS-backed volumes
+  - When volume_mode is 'mount': no volume definition needed (bind mounts used directly)
+#}
+{% if volume_mode == 'local' %}
+volumes:
+  {% if not database_external %}
+  {{ service_name }}-postgres:
+    driver: local
+  {% endif %}
+  {{ service_name }}-redis:
+    driver: local
+  {{ service_name }}-media:
+    driver: local
+  {{ service_name }}-certs:
+    driver: local
+  {{ service_name }}-templates:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {% if not database_external %}
+  {{ service_name }}-postgres:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/postgres"
+  {% endif %}
+  {{ service_name }}-redis:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/redis"
+  {{ service_name }}-media:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/media"
+  {{ service_name }}-certs:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/certs"
+  {{ service_name }}-templates:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/templates"
+{% endif %}

+ 19 - 304
library/compose/authentik/compose.yaml.j2

@@ -1,308 +1,23 @@
-services:
-  {{ service_name }}:
-    image: ghcr.io/goauthentik/server:2025.10.1
-    {% if not swarm_enabled %}
-    restart: {{ restart_policy }}
-    container_name: {{ container_name }}
-    {% endif %}
-    hostname: {{ container_hostname }}
-    command: server
-    environment:
-      - TZ={{ container_timezone }}
-      - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
-      - AUTHENTIK_ERROR_REPORTING__ENABLED={{ authentik_error_reporting }}
-      - AUTHENTIK_REDIS__HOST={{ service_name }}-redis
-      - AUTHENTIK_POSTGRESQL__HOST={{ service_name }}-postgres
-      - AUTHENTIK_POSTGRESQL__USER={{ database_user }}
-      - AUTHENTIK_POSTGRESQL__NAME={{ database_name }}
-      - AUTHENTIK_POSTGRESQL__PASSWORD=${DATABASE_PASSWORD}
-      {% if email_enabled %}
-      - AUTHENTIK_EMAIL__HOST={{ email_host }}
-      - AUTHENTIK_EMAIL__PORT={{ email_port }}
-      - AUTHENTIK_EMAIL__USERNAME={{ email_username }}
-      - AUTHENTIK_EMAIL__PASSWORD=${EMAIL_PASSWORD}
-      - AUTHENTIK_EMAIL__USE_TLS={{ email_use_tls }}
-      - AUTHENTIK_EMAIL__USE_SSL={{ email_use_ssl }}
-      - AUTHENTIK_EMAIL__FROM={{ email_from }}
-      {% endif %}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if traefik_enabled %}
-      {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
-    {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
-    ports:
-      {% if swarm_enabled %}
-      - target: 9000
-        published: {{ ports_http }}
-        protocol: tcp
-        mode: host
-      - target: 9443
-        published: {{ ports_https }}
-        protocol: tcp
-        mode: host
-      {% else %}
-      - "{{ ports_http }}:9000"
-      - "{{ ports_https }}:9443"
-      {% endif %}
-    {% endif %}
-    volumes:
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/media:/media
-      - {{ volume_mount_path }}/templates:/templates
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-media:/media
-      - {{ service_name }}-templates:/templates
-      {% endif %}
-    {% if traefik_enabled and not swarm_enabled %}
-    labels:
-      - traefik.enable=true
-      - traefik.docker.network={{ traefik_network }}
-      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=9000
-      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
-      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
-      {% if traefik_tls_enabled %}
-      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
-      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
-      - traefik.http.routers.{{ service_name }}-https.tls=true
-      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
-      {% endif %}
-    {% endif %}
-    depends_on:
-      - {{ service_name }}-postgres
-      - {{ service_name }}-redis
-    {% if swarm_enabled or resources_enabled %}
-    deploy:
-      {% if swarm_enabled %}
-      mode: {{ swarm_placement_mode }}
-      {% if swarm_placement_mode == 'replicated' %}
-      replicas: {{ swarm_replicas }}
-      {% endif %}
-      {% if swarm_placement_host %}
-      placement:
-        constraints:
-          - node.hostname == {{ swarm_placement_host }}
-      {% endif %}
-      restart_policy:
-        condition: on-failure
-      {% endif %}
-      {% if resources_enabled %}
-      resources:
-        limits:
-          cpus: '{{ resources_cpu_limit }}'
-          memory: {{ resources_memory_limit }}
-        {% if swarm_enabled %}
-        reservations:
-          cpus: '{{ resources_cpu_reservation }}'
-          memory: {{ resources_memory_reservation }}
-        {% endif %}
-      {% endif %}
-      {% if swarm_enabled and traefik_enabled %}
-      labels:
-        - traefik.enable=true
-        - traefik.docker.network={{ traefik_network }}
-        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=9000
-        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
-        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
-        {% if traefik_tls_enabled %}
-        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
-        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
-        - traefik.http.routers.{{ service_name }}-https.tls=true
-        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
-        {% endif %}
-      {% endif %}
-    {% endif %}
-
-  {{ service_name }}-worker:
-    image: ghcr.io/goauthentik/server:2025.10.1
-    {% if not swarm_enabled %}
-    restart: {{ restart_policy }}
-    container_name: {{ service_name }}-worker
-    {% endif %}
-    command: worker
-    environment:
-      - TZ={{ container_timezone }}
-      - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
-      - AUTHENTIK_ERROR_REPORTING__ENABLED={{ authentik_error_reporting }}
-      - AUTHENTIK_REDIS__HOST={{ service_name }}-redis
-      - AUTHENTIK_POSTGRESQL__HOST={{ service_name }}-postgres
-      - AUTHENTIK_POSTGRESQL__USER={{ database_user }}
-      - AUTHENTIK_POSTGRESQL__NAME={{ database_name }}
-      - AUTHENTIK_POSTGRESQL__PASSWORD=${DATABASE_PASSWORD}
-      {% if email_enabled %}
-      - AUTHENTIK_EMAIL__HOST={{ email_host }}
-      - AUTHENTIK_EMAIL__PORT={{ email_port }}
-      - AUTHENTIK_EMAIL__USERNAME={{ email_username }}
-      - AUTHENTIK_EMAIL__PASSWORD=${EMAIL_PASSWORD}
-      - AUTHENTIK_EMAIL__USE_TLS={{ email_use_tls }}
-      - AUTHENTIK_EMAIL__USE_SSL={{ email_use_ssl }}
-      - AUTHENTIK_EMAIL__FROM={{ email_from }}
-      {% endif %}
-    user: root
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if traefik_enabled %}
-      {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
-    {% endif %}
-    volumes:
-      - /run/docker.sock:/run/docker.sock
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/media:/media
-      - {{ volume_mount_path }}/certs:/certs
-      - {{ volume_mount_path }}/templates:/templates
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-media:/media
-      - {{ service_name }}-certs:/certs
-      - {{ service_name }}-templates:/templates
-      {% endif %}
-    depends_on:
-      - {{ service_name }}-postgres
-      - {{ service_name }}-redis
-
-  {{ service_name }}-redis:
-    image: docker.io/library/redis:8.2.3
-    {% if not swarm_enabled %}
-    restart: {{ restart_policy }}
-    container_name: {{ service_name }}-redis
-    {% endif %}
-    command: --save 60 1 --loglevel warning
-    healthcheck:
-      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
-      start_period: 20s
-      interval: 30s
-      retries: 5
-      timeout: 3s
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if traefik_enabled %}
-      {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
-    {% endif %}
-    volumes:
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/redis:/data
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-redis:/data
-      {% endif %}
-
+{#
+  Authentik Docker Compose Configuration
+  
+  This is the main orchestration file that includes all service definitions.
+  Services are split into separate files for better maintainability:
+  - services/server.yaml: Main Authentik web application and API
+  - services/worker.yaml: Background task worker
+  - services/redis.yaml: Redis cache and message broker
+  - services/postgres.yaml: PostgreSQL database (if not external)
+  - common/networks.yaml: Network definitions
+  - common/volumes.yaml: Volume definitions
+#}
+include:
+  - services/server.yaml
+  - services/worker.yaml
+  - services/redis.yaml
   {% if not database_external %}
-  {{ service_name }}-postgres:
-    image: docker.io/library/postgres:17.6
-    {% if not swarm_enabled %}
-    restart: {{ restart_policy }}
-    container_name: {{ service_name }}-db
-    {% endif %}
-    environment:
-      - TZ={{ container_timezone }}
-      - POSTGRES_USER={{ database_user }}
-      - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
-      - POSTGRES_DB={{ database_name }}
-    healthcheck:
-      test: ["CMD-SHELL", "pg_isready -U {{ database_user }}"]
-      start_period: 30s
-      interval: 10s
-      timeout: 10s
-      retries: 5
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if traefik_enabled %}
-      {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
-    {% endif %}
-    volumes:
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/postgres:/var/lib/postgresql/data
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-postgres:/var/lib/postgresql/data
-      {% endif %}
+  - services/postgres.yaml
   {% endif %}
-
-{% if volume_mode == 'local' %}
-volumes:
-  {% if not database_external %}
-  {{ service_name }}-postgres:
-    driver: local
-  {% endif %}
-  {{ service_name }}-redis:
-    driver: local
-{% elif volume_mode == 'nfs' %}
-volumes:
-  {{ service_name }}-postgres:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/postgres"
-  {{ service_name }}-redis:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/redis"
-{% endif %}
-
-{% if network_mode != 'host' %}
-networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
   {% if traefik_enabled %}
-  {{ traefik_network }}:
-    external: true
+  - common/networks.yaml
   {% endif %}
-{% endif %}
+  - common/volumes.yaml

+ 326 - 0
library/compose/authentik/compose.yaml.j2.backup

@@ -0,0 +1,326 @@
+services:
+  {{ service_name }}:
+    image: ghcr.io/goauthentik/server:2025.10.1
+    {#
+      If not in swarm mode, check whether container_name is set and apply restart policy,
+      else swarm mode handles restarts via deploy.restart_policy
+    #}
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    {#
+      Set container hostname for identification
+    #}
+    hostname: {{ container_hostname }}
+    command: server
+    {#
+      Environment variables for Authentik server configuration:
+      - Secret key, error reporting, Redis/PostgreSQL connection settings
+      - Optional email configuration for notifications
+    #}
+    environment:
+      - TZ={{ container_timezone }}
+      - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
+      - AUTHENTIK_ERROR_REPORTING__ENABLED={{ authentik_error_reporting }}
+      - AUTHENTIK_REDIS__HOST={{ service_name }}-redis
+      - AUTHENTIK_POSTGRESQL__HOST={{ service_name }}-postgres
+      - AUTHENTIK_POSTGRESQL__USER={{ database_user }}
+      - AUTHENTIK_POSTGRESQL__NAME={{ database_name }}
+      - AUTHENTIK_POSTGRESQL__PASSWORD=${DATABASE_PASSWORD}
+      {% if email_enabled %}
+      - AUTHENTIK_EMAIL__HOST={{ email_host }}
+      - AUTHENTIK_EMAIL__PORT={{ email_port }}
+      - AUTHENTIK_EMAIL__USERNAME={{ email_username }}
+      - AUTHENTIK_EMAIL__PASSWORD=${EMAIL_PASSWORD}
+      - AUTHENTIK_EMAIL__USE_TLS={{ email_use_tls }}
+      - AUTHENTIK_EMAIL__USE_SSL={{ email_use_ssl }}
+      - AUTHENTIK_EMAIL__FROM={{ email_from }}
+      {% endif %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
+    networks:
+      {{ traefik_network }}:
+    {% endif %}
+    {#
+      Port mappings for HTTP/HTTPS (only when Traefik is disabled)
+      Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
+    #}
+    {% if not traefik_enabled %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 9000
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      - target: 9443
+        published: {{ ports_https }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:9000"
+      - "{{ ports_https }}:9443"
+      {% endif %}
+    {% endif %}
+    {#
+      Volume configuration for persistent data:
+      - media: User-uploaded media files
+      - templates: Custom template files
+    #}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/media:/media
+      - {{ volume_mount_path }}/templates:/templates
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-media:/media
+      - {{ service_name }}-templates:/templates
+      {% endif %}
+    {#
+      When traefik_enabled is set, and not running in swarm mode, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=9000
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}
+    depends_on:
+      - {{ service_name }}-postgres
+      - {{ service_name }}-redis
+    {#
+      Deploy configuration for Swarm mode and/or resource limits:
+      - Swarm: Configure replicas, placement constraints, and restart policy
+      - Resources: Set CPU/memory limits (and reservations in Swarm mode)
+      - Traefik: Labels for reverse proxy integration (Swarm mode)
+    #}
+    {% if swarm_enabled or resources_enabled %}
+    deploy:
+      {% if swarm_enabled %}
+      mode: {{ swarm_placement_mode }}
+      {% if swarm_placement_mode == 'replicated' %}
+      replicas: {{ swarm_replicas }}
+      {% endif %}
+      {% if swarm_placement_host %}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      {% endif %}
+      restart_policy:
+        condition: on-failure
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        {% if swarm_enabled %}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+        {% endif %}
+      {% endif %}
+      {#
+        When traefik_enabled is set in swarm mode, add traefik labels
+        (optionally enable TLS if traefik_tls_enabled is set)
+      #}
+      {% if swarm_enabled and traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=9000
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+
+  {#
+    Authentik Worker: Background task processor
+    Handles long-running tasks like email sending, cleanup jobs, and scheduled tasks
+  #}
+  {{ service_name }}-worker:
+    image: ghcr.io/goauthentik/server:2025.10.1
+    {#
+      If not in swarm mode, apply restart policy and container name
+    #}
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-worker
+    {% endif %}
+    command: worker
+    {#
+      Environment variables (same as server for consistency)
+    #}
+    environment:
+      - TZ={{ container_timezone }}
+      - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
+      - AUTHENTIK_ERROR_REPORTING__ENABLED={{ authentik_error_reporting }}
+      - AUTHENTIK_REDIS__HOST={{ service_name }}-redis
+      - AUTHENTIK_POSTGRESQL__HOST={{ service_name }}-postgres
+      - AUTHENTIK_POSTGRESQL__USER={{ database_user }}
+      - AUTHENTIK_POSTGRESQL__NAME={{ database_name }}
+      - AUTHENTIK_POSTGRESQL__PASSWORD=${DATABASE_PASSWORD}
+      {% if email_enabled %}
+      - AUTHENTIK_EMAIL__HOST={{ email_host }}
+      - AUTHENTIK_EMAIL__PORT={{ email_port }}
+      - AUTHENTIK_EMAIL__USERNAME={{ email_username }}
+      - AUTHENTIK_EMAIL__PASSWORD=${EMAIL_PASSWORD}
+      - AUTHENTIK_EMAIL__USE_TLS={{ email_use_tls }}
+      - AUTHENTIK_EMAIL__USE_SSL={{ email_use_ssl }}
+      - AUTHENTIK_EMAIL__FROM={{ email_from }}
+      {% endif %}
+    user: root
+    {#
+      Volume configuration:
+      - Docker socket: Required for worker to manage containers
+      - media/certs/templates: Shared with server container
+    #}
+    volumes:
+      - /run/docker.sock:/run/docker.sock
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/media:/media
+      - {{ volume_mount_path }}/certs:/certs
+      - {{ volume_mount_path }}/templates:/templates
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-media:/media
+      - {{ service_name }}-certs:/certs
+      - {{ service_name }}-templates:/templates
+      {% endif %}
+    depends_on:
+      - {{ service_name }}-postgres
+      - {{ service_name }}-redis
+
+  {#
+    Redis: Cache and message broker for Authentik
+    Stores sessions, caching data, and task queue messages
+  #}
+  {{ service_name }}-redis:
+    image: docker.io/library/redis:8.2.3
+    {#
+      If not in swarm mode, apply restart policy and container name
+    #}
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-redis
+    {% endif %}
+    command: --save 60 1 --loglevel warning
+    {#
+      Health check: Verify Redis is responding to ping commands
+    #}
+    healthcheck:
+      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
+      start_period: 20s
+      interval: 30s
+      retries: 5
+      timeout: 3s
+    {#
+      Volume configuration for Redis data persistence
+    #}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/redis:/data
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-redis:/data
+      {% endif %}
+
+  {#
+    PostgreSQL: Main database for Authentik (only if not using external database)
+    Stores all user data, configurations, and authentication flows
+  #}
+  {% if not database_external %}
+  {{ service_name }}-postgres:
+    image: docker.io/library/postgres:17.6
+    {#
+      If not in swarm mode, apply restart policy and container name
+    #}
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-db
+    {% endif %}
+    {#
+      Environment variables for PostgreSQL configuration
+    #}
+    environment:
+      - TZ={{ container_timezone }}
+      - POSTGRES_USER={{ database_user }}
+      - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
+      - POSTGRES_DB={{ database_name }}
+    {#
+      Health check: Verify PostgreSQL is ready to accept connections
+    #}
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U {{ database_user }}"]
+      start_period: 30s
+      interval: 10s
+      timeout: 10s
+      retries: 5
+    {#
+      Volume configuration for PostgreSQL data persistence
+    #}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/postgres:/var/lib/postgresql/data
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-postgres:/var/lib/postgresql/data
+      {% endif %}
+  {% endif %}
+
+{#
+  Volume definitions:
+  - When volume_mode is 'local' (default): use docker-managed local volumes
+  - When volume_mode is 'nfs': configure NFS-backed volumes
+  - When volume_mode is 'mount': no volume definition needed (bind mounts used directly)
+#}
+{% if volume_mode == 'local' %}
+volumes:
+  {% if not database_external %}
+  {{ service_name }}-postgres:
+    driver: local
+  {% endif %}
+  {{ service_name }}-redis:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-postgres:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/postgres"
+  {{ service_name }}-redis:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/redis"
+{% endif %}
+
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
+networks:
+  {{ traefik_network }}:
+    external: true
+{% endif %}

+ 28 - 0
library/compose/authentik/services/postgres.yaml.j2

@@ -0,0 +1,28 @@
+{#
+  PostgreSQL: Main database for Authentik
+  Stores all user data, configurations, and authentication flows
+#}
+services:
+  {{ service_name }}-postgres:
+    image: docker.io/library/postgres:17.6
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-db
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      - POSTGRES_USER={{ database_user }}
+      - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
+      - POSTGRES_DB={{ database_name }}
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U {{ database_user }}"]
+      start_period: 30s
+      interval: 10s
+      timeout: 10s
+      retries: 5
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/postgres:/var/lib/postgresql/data
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-postgres:/var/lib/postgresql/data
+      {% endif %}

+ 24 - 0
library/compose/authentik/services/redis.yaml.j2

@@ -0,0 +1,24 @@
+{#
+  Redis: Cache and message broker for Authentik
+  Stores sessions, caching data, and task queue messages
+#}
+services:
+  {{ service_name }}-redis:
+    image: docker.io/library/redis:8.2.3
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-redis
+    {% endif %}
+    command: --save 60 1 --loglevel warning
+    healthcheck:
+      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
+      start_period: 20s
+      interval: 30s
+      retries: 5
+      timeout: 3s
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/redis:/data
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-redis:/data
+      {% endif %}

+ 124 - 0
library/compose/authentik/services/server.yaml.j2

@@ -0,0 +1,124 @@
+{#
+  Authentik Server: Main web application and API
+  Handles authentication, authorization, and user interface
+#}
+services:
+  {{ service_name }}:
+    image: ghcr.io/goauthentik/server:2025.10.1
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    hostname: {{ container_hostname }}
+    command: server
+    environment:
+      - TZ={{ container_timezone }}
+      - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
+      - AUTHENTIK_ERROR_REPORTING__ENABLED={{ authentik_error_reporting }}
+      - AUTHENTIK_REDIS__HOST={{ service_name }}-redis
+      - AUTHENTIK_POSTGRESQL__HOST={{ service_name }}-postgres
+      - AUTHENTIK_POSTGRESQL__USER={{ database_user }}
+      - AUTHENTIK_POSTGRESQL__NAME={{ database_name }}
+      - AUTHENTIK_POSTGRESQL__PASSWORD=${DATABASE_PASSWORD}
+      {% if email_enabled %}
+      - AUTHENTIK_EMAIL__HOST={{ email_host }}
+      - AUTHENTIK_EMAIL__PORT={{ email_port }}
+      - AUTHENTIK_EMAIL__USERNAME={{ email_username }}
+      - AUTHENTIK_EMAIL__PASSWORD=${EMAIL_PASSWORD}
+      {% if email_encryption == "starttls" %}
+      - AUTHENTIK_EMAIL__USE_TLS=true
+      {% elif email_encryption == "ssl" %}
+      - AUTHENTIK_EMAIL__USE_SSL=true
+      {% endif %}
+      - AUTHENTIK_EMAIL__FROM={{ email_from }}
+      {% endif %}
+    {% if traefik_enabled %}
+    networks:
+      {{ traefik_network }}:
+    {% endif %}
+    {% if not traefik_enabled %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 9000
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      - target: 9443
+        published: {{ ports_https }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:9000"
+      - "{{ ports_https }}:9443"
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/media:/media
+      - {{ volume_mount_path }}/templates:/templates
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-media:/media
+      - {{ service_name }}-templates:/templates
+      {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=9000
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}
+    depends_on:
+      - {{ service_name }}-postgres
+      - {{ service_name }}-redis
+    {% if swarm_enabled or resources_enabled %}
+    deploy:
+      {% if swarm_enabled %}
+      mode: {{ swarm_placement_mode }}
+      {% if swarm_placement_mode == 'replicated' %}
+      replicas: {{ swarm_replicas }}
+      {% endif %}
+      {% if swarm_placement_host %}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      {% endif %}
+      restart_policy:
+        condition: on-failure
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        {% if swarm_enabled %}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+        {% endif %}
+      {% endif %}
+      {% if swarm_enabled and traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=9000
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}

+ 48 - 0
library/compose/authentik/services/worker.yaml.j2

@@ -0,0 +1,48 @@
+{#
+  Authentik Worker: Background task processor
+  Handles long-running tasks like email sending, cleanup jobs, and scheduled tasks
+#}
+services:
+  {{ service_name }}-worker:
+    image: ghcr.io/goauthentik/server:2025.10.1
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-worker
+    {% endif %}
+    command: worker
+    environment:
+      - TZ={{ container_timezone }}
+      - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
+      - AUTHENTIK_ERROR_REPORTING__ENABLED={{ authentik_error_reporting }}
+      - AUTHENTIK_REDIS__HOST={{ service_name }}-redis
+      - AUTHENTIK_POSTGRESQL__HOST={{ service_name }}-postgres
+      - AUTHENTIK_POSTGRESQL__USER={{ database_user }}
+      - AUTHENTIK_POSTGRESQL__NAME={{ database_name }}
+      - AUTHENTIK_POSTGRESQL__PASSWORD=${DATABASE_PASSWORD}
+      {% if email_enabled %}
+      - AUTHENTIK_EMAIL__HOST={{ email_host }}
+      - AUTHENTIK_EMAIL__PORT={{ email_port }}
+      - AUTHENTIK_EMAIL__USERNAME={{ email_username }}
+      - AUTHENTIK_EMAIL__PASSWORD=${EMAIL_PASSWORD}
+      {% if email_encryption == "starttls" %}
+      - AUTHENTIK_EMAIL__USE_TLS=true
+      {% elif email_encryption == "ssl" %}
+      - AUTHENTIK_EMAIL__USE_SSL=true
+      {% endif %}
+      - AUTHENTIK_EMAIL__FROM={{ email_from }}
+      {% endif %}
+    user: root
+    volumes:
+      - /run/docker.sock:/run/docker.sock
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/media:/media
+      - {{ volume_mount_path }}/certs:/certs
+      - {{ volume_mount_path }}/templates:/templates
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-media:/media
+      - {{ service_name }}-certs:/certs
+      - {{ service_name }}-templates:/templates
+      {% endif %}
+    depends_on:
+      - {{ service_name }}-postgres
+      - {{ service_name }}-redis

+ 2 - 1
library/compose/authentik/template.yaml

@@ -20,7 +20,8 @@ metadata:
   date: '2025-11-05'
   tags:
     - traefik
-    - authentik
+    - swarm
+    - volume_modes
   next_steps: |
     1. Start Authentik:
        docker compose up -d

+ 95 - 38
library/compose/bind9/compose.yaml.j2

@@ -1,56 +1,113 @@
 services:
   {{ service_name }}:
     image: docker.io/ubuntu/bind9:{{ bind9_version }}
+    {#
+      If not in swarm mode, check whether container_name is set and apply restart policy,
+      else swarm mode handles restarts via deploy.restart_policy
+    #}
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    {% if container_name %}
     container_name: {{ container_name }}
+    {% endif %}
+    {% endif %}
+    {#
+      Set container hostname (BIND9 uses this for identification)
+    #}
+    {% if container_hostname %}
     hostname: {{ container_hostname }}
+    {% endif %}
+    {#
+      Environment variables for BIND9 configuration
+      - TZ: Timezone for proper log rotation
+      - BIND9_USER: User to run BIND9 as
+    #}
     environment:
       - TZ={{ container_timezone }}
       - BIND9_USER=bind
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
-    {% endif %}
-    {% if network_mode != 'host' %}
+    {#
+      Port mappings for DNS queries:
+      - DNS ports: Port 53 TCP/UDP (always exposed for DNS queries)
+      Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
+    #}
     ports:
+      {% if swarm_enabled %}
+      - target: 53
+        published: 53
+        protocol: tcp
+        mode: host
+      - target: 53
+        published: 53
+        protocol: udp
+        mode: host
+      {% else %}
       - "53:53/tcp"
       - "53:53/udp"
-    {% endif %}
+      {% endif %}
+    {#
+      Volume configuration for persistent data
+      - When volume_mode is 'mount': bind mount from host path
+      - When volume_mode is 'local', 'nfs', or empty: use docker-managed volumes
+    #}
     volumes:
-      - ./config:/etc/bind
-      - bind9_zones:/var/lib/bind
-      - bind9_cache:/var/cache/bind
-    restart: {{ restart_policy }}
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/config:/etc/bind:rw
+      - {{ volume_mount_path }}/zones:/var/lib/bind:rw
+      - {{ volume_mount_path }}/cache:/var/cache/bind:rw
+      {% else %}
+      - {{ service_name }}-config:/etc/bind
+      - {{ service_name }}-zones:/var/lib/bind
+      - {{ service_name }}-cache:/var/cache/bind
+      {% endif %}
+    {#
+      Deploy configuration for Swarm mode:
+      - Single replica with node placement constraint (BIND9 doesn't support multi-replica)
+      - Multiple instances would conflict with DNS services
+    #}
+    {% if swarm_enabled %}
+    deploy:
+      mode: replicated
+      replicas: 1
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      restart_policy:
+        condition: on-failure
+    {% endif %}
 
+{#
+  Volume definitions:
+  - When volume_mode is 'local' (default): use docker-managed local volumes
+  - When volume_mode is 'nfs': configure NFS-backed volumes
+  - When volume_mode is 'mount': no volume definition needed (bind mounts used directly)
+#}
+{% if volume_mode == 'local' %}
 volumes:
-  bind9_zones:
+  {{ service_name }}-config:
     driver: local
-  bind9_cache:
+  {{ service_name }}-zones:
+    driver: local
+  {{ service_name }}-cache:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-config:
     driver: local
-
-{% if network_mode != 'host' %}
-networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
     driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-    name: {{ network_name }}
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/config"
+  {{ service_name }}-zones:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/zones"
+  {{ service_name }}-cache:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/cache"
 {% endif %}
+

+ 3 - 1
library/compose/bind9/template.yaml

@@ -15,7 +15,9 @@ metadata:
   version: 9.20-24.10_edge
   author: Christian Lempa
   date: '2025-10-02'
-  tags: []
+  tags:
+    - swarm
+    - volume_modes
   next_steps: |
     1. Start the DNS server:
        docker compose up -d

+ 41 - 32
library/compose/checkmk/compose.yaml.j2

@@ -2,36 +2,56 @@ services:
   {{ service_name }}:
     image: checkmk/check-mk-raw:2.4.0-{{ monitoring_version }}
     container_name: {{ container_name }}
+    {#
+      Set container hostname for Checkmk identification
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Environment variables for Checkmk configuration:
+      - TZ: Timezone for proper log timestamps
+      - CMK_PASSWORD: Admin password for web interface
+      - CMK_SITE_ID: Site identifier for Checkmk instance
+    #}
     environment:
       - TZ={{ container_timezone }}
       - CMK_PASSWORD={{ cmk_password }}
       - CMK_SITE_ID={{ cmk_site_id }}
+    {#
+      Temporary filesystem for Checkmk runtime files
+    #}
     tmpfs:
       - /opt/omd/sites/{{ cmk_site_id }}/tmp:uid={{ user_uid }},gid={{ user_gid }}
+    {#
+      Volume configuration:
+      - /etc/localtime: System time synchronization
+      - data: Persistent storage for Checkmk sites and configuration
+    #}
     volumes:
       - /etc/localtime:/etc/localtime:ro
       - data:/omd/sites:rw
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
     networks:
-      {% if traefik_enabled %}
       {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
+    {#
+      Port mappings (only when Traefik is disabled):
+      - HTTP: Web interface
+      - Agent: Checkmk agent communication
+      - SNMP: SNMP trap receiver
+    #}
+    {% if not traefik_enabled %}
     ports:
       - "{{ ports_http }}:8000"
       - "{{ ports_agent }}:5000"
       - "{{ ports_snmp }}:162/udp"
     {% endif %}
+    {#
+      When traefik_enabled is set, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
     {% if traefik_enabled %}
     labels:
       - traefik.enable=true
@@ -51,31 +71,20 @@ services:
     {% endif %}
     restart: {{ restart_policy }}
 
+{#
+  Volume definitions:
+  - data: Persistent storage for Checkmk sites and configuration
+#}
 volumes:
   data:
     driver: local
 
-{% if network_mode != 'host' %}
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-    name: {{ network_name }}
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
 {% endif %}

+ 2 - 1
library/compose/checkmk/template.yaml

@@ -18,7 +18,8 @@ metadata:
   version: 2.4.0-latest
   author: Christian Lempa
   date: '2025-09-28'
-  tags: []
+  tags:
+    - traefik
 spec:
   general:
     vars:

+ 0 - 19
library/compose/clamav/compose.yaml.j2

@@ -1,19 +0,0 @@
-services:
-  {{ service_name }}:
-    image: docker.io/clamav/clamav:1.5.1
-    container_name: {{ container_name }}
-    volumes:
-      - ./config/clamd.conf:/etc/clamav/clamd.conf:ro
-      - ./config/freshclam.conf:/etc/clamav/freshclam.conf:ro
-      - clamav-data:/var/lib/clamav
-      # --> (Optional) Add a directory to scan
-      # - ./scandir:/scandir:rw
-      # <--
-    # -- Change logging driver here... (required for Wazuh integration)
-    logging:
-      driver: syslog
-      options:
-        tag: "clamd"
-    restart: unless-stopped
-volumes:
-  clamav-data:

+ 0 - 81
library/compose/clamav/config/clamd.conf

@@ -1,81 +0,0 @@
-# -- Change Log settings here...
-LogSyslog yes
-LogTime yes
-# --> (Optional) Enable logging to file, can work together with LogSyslog
-# LogFile /var/log/clamav/clamd.log
-# LogRotate no
-# <--
-
-# -- Change process settings here...
-PidFile /tmp/clamd.pid
-LocalSocket /run/clamav/clamd.sock
-
-# -- Change TCP port settings here...
-TCPSocket 3310
-
-# -- Change user settings here...
-User clamav
-
-# -- Change detection settings here...
-# DetectPUA no
-# HeuristicAlerts yes
-# HeuristicScanPrecedence no
-
-# -- Change Heuristic Alerts here...
-# AlertBrokenExecutables no
-# AlertBrokenMedia no
-# AlertEncrypted no
-# AlertEncryptedArchive no
-# AlertEncryptedDoc no
-# AlertOLE2Macros no
-# AlertPhishingSSLMismatch no
-# AlertPhishingCloak no
-# AlertPartitionIntersection no
-
-# -- Change Executable files settings here...
-# ScanPE yes
-# DisableCertCheck no
-# ScanELF yes
-
-# -- Change Documents settings here...
-# ScanOLE2 yes
-# ScanPDF yes
-# ScanSWF yes
-# ScanXMLDOCS yes
-# ScanHWP3 yes
-# ScanOneNote yes
-
-# -- Change other file types settings here...
-# ScanImage yes
-# ScanImageFuzzyHash yes
-
-# -- Change Mail files settings here...
-# ScanMail yes
-# ScanPartialMessages no
-# PhishingSignatures yes
-# PhishingScanURLs yes
-
-# -- Change Data Loss Prevention (DLP) settings here...
-# StructuredDataDetection no
-# StructuredMinCreditCardCount 3
-# StructuredCCOnly no
-# StructuredMinSSNCount 3
-# StructuredSSNFormatNormal yes
-# StructuredSSNFormatStripped no
-
-# -- Change HTML settings here...
-# ScanHTML yes
-
-# -- Change Archives settings here...
-# ScanArchive yes
-
-# -- Change On-access Scan settings here...
-# OnAccessMaxFileSize 5M
-# OnAccessMaxThreads 5
-# --> (Optional) Set include paths, exclude paths, mount paths, etc...
-#OnAccessIncludePath /home
-#OnAccessExcludePath /home/user
-#OnAccessExtraScanning no
-#OnAccessMountPath /
-#OnAccessMountPath /home/user
-# <--

+ 0 - 21
library/compose/clamav/config/freshclam.conf

@@ -1,21 +0,0 @@
-# -- Change Log settings here...
-LogSyslog no
-LogTime yes
-# --> (Optional) Enable logging to file, can work together with LogSyslog
-# UpdateLogFile /var/log/clamav/freshclam.log
-# LogRotate no
-# <--
-
-# -- Change process settings here...
-PidFile /tmp/freshclam.pid
-
-# -- Change database settings here...
-DatabaseOwner clamav
-DatabaseMirror database.clamav.net
-
-# -- Change update and notification settings here...
-ScriptedUpdates yes
-NotifyClamd /etc/clamav/clamd.conf
-
-# -- Change custom sources for databases here...
-#DatabaseCustomURL http://myserver.example.com/mysigs.ndb

+ 0 - 30
library/compose/clamav/template.yaml

@@ -1,30 +0,0 @@
----
-kind: compose
-schema: "1.2"
-metadata:
-  name: Clamav
-  description: >
-    Docker compose setup for ClamAV, an open-source antivirus engine for detecting trojans, viruses, malware & other malicious threats.
-    This template configures ClamAV with a freshclam service for automatic virus database updates and a clamav service for scanning files.
-
-
-    Project: https://www.clamav.net/
-
-    Documentation: https://docs.clamav.net/
-
-    GitHub: https://github.com/Cisco-Talos/clamav
-  version: 1.5.1
-  author: Christian Lempa
-  date: '2025-10-31'
-  tags: []
-spec:
-  general:
-    vars:
-      service_name:
-        default: clamav
-      container_name:
-        default: clamav
-      clamav_version:
-        type: str
-        description: Clamav version
-        default: latest

+ 35 - 35
library/compose/dockge/compose.yaml.j2

@@ -2,32 +2,46 @@ services:
   {{ service_name }}:
     image: docker.io/louislam/dockge:{{ dockge_version }}
     container_name: {{ container_name }}
+    {#
+      Set container hostname for Dockge identification
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Environment variables for Dockge configuration:
+      - TZ: Timezone for proper log timestamps
+      - DOCKGE_STACKS_DIR: Directory where Docker Compose stacks are stored
+    #}
     environment:
       - TZ={{ container_timezone }}
       - DOCKGE_STACKS_DIR={{ stacks_path }}
+    {#
+      Volume configuration:
+      - Docker socket: Required for managing Docker containers (read-only)
+      - dockge-data: Persistent storage for Dockge configuration
+      - stacks_path: Directory for Docker Compose stacks management
+    #}
     volumes:
       - /var/run/docker.sock:/var/run/docker.sock:ro
       - dockge-data:/app/data
       - {{ stacks_path }}:{{ stacks_path }}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
     networks:
-      {% if traefik_enabled %}
       {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
+    {#
+      Port mappings for web interface (only when Traefik is disabled)
+    #}
+    {% if not traefik_enabled %}
     ports:
       - "{{ ports_http }}:5001"
     {% endif %}
+    {#
+      When traefik_enabled is set, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
     {% if traefik_enabled %}
     labels:
       - traefik.enable=true
@@ -46,34 +60,20 @@ services:
     {% endif %}
     restart: {{ restart_policy }}
 
+{#
+  Volume definitions:
+  - dockge-data: Persistent storage for Dockge configuration and data
+#}
 volumes:
   dockge-data:
     driver: local
 
-{% if network_mode != 'host' %}
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
 {% endif %}

+ 2 - 1
library/compose/dockge/template.yaml

@@ -15,7 +15,8 @@ metadata:
   version: 1.5.0
   author: Christian Lempa
   date: '2025-09-28'
-  tags: []
+  tags:
+    - traefik
 spec:
   general:
     vars:

+ 68 - 50
library/compose/gitea/compose.yaml.j2

@@ -1,11 +1,24 @@
 services:
   {{ service_name }}:
     image: docker.io/gitea/gitea:1.25.1
+    {#
+      If not in swarm mode, check whether container_name is set and apply restart policy,
+      else swarm mode handles restarts via deploy.restart_policy
+    #}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     container_name: {{ container_name }}
     {% endif %}
+    {#
+      Set container hostname for identification
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Environment variables for Gitea configuration:
+      - User/group IDs for file permissions
+      - PostgreSQL database connection settings
+      - SSH port and root URL for clone operations
+    #}
     environment:
       - TZ={{ container_timezone }}
       - USER_UID={{ user_uid }}
@@ -17,21 +30,19 @@ services:
       - GITEA__database__PASSWD=${DATABASE_PASSWORD}
       - GITEA__server__SSH_PORT={{ gitea_ssh_port }}
       - GITEA__server__ROOT_URL={{ gitea_root_url }}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
     networks:
-      {% if traefik_enabled %}
       {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
-    {% if network_mode == 'bridge' %}
+    {#
+      Port mappings:
+      - HTTP: Web interface (only if Traefik is disabled)
+      - SSH: Git SSH access (always exposed, cannot be proxied)
+      Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
+    #}
     ports:
       {% if not traefik_enabled %}
       {% if swarm_enabled %}
@@ -44,11 +55,19 @@ services:
       {% endif %}
       {% endif %}
       - "{{ ports_ssh }}:22"
-    {% endif %}
+    {#
+      Volume configuration:
+      - gitea-data: Persistent storage for repositories and configuration
+      - System time synchronization files
+    #}
     volumes:
       - gitea-data:/data
       - /etc/timezone:/etc/timezone:ro
       - /etc/localtime:/etc/localtime:ro
+    {#
+      When traefik_enabled is set, and not running in swarm mode, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
     {% if traefik_enabled and not swarm_enabled %}
     labels:
       - traefik.enable=true
@@ -67,6 +86,12 @@ services:
     {% endif %}
     depends_on:
       - {{ service_name }}-postgres
+    {#
+      Deploy configuration for Swarm mode and/or resource limits:
+      - Swarm: Configure replicas, placement constraints, and restart policy
+      - Resources: Set CPU/memory limits (and reservations in Swarm mode)
+      - Traefik: Labels for reverse proxy integration (Swarm mode)
+    #}
     {% if swarm_enabled or resources_enabled %}
     deploy:
       {% if swarm_enabled %}
@@ -93,6 +118,10 @@ services:
           memory: {{ resources_memory_reservation }}
         {% endif %}
       {% endif %}
+      {#
+        When traefik_enabled is set in swarm mode, add traefik labels
+        (optionally enable TLS if traefik_tls_enabled is set, and authentik middleware if enabled)
+      #}
       {% if swarm_enabled and traefik_enabled %}
       labels:
         - traefik.enable=true
@@ -117,42 +146,49 @@ services:
       {% endif %}
     {% endif %}
 
+  {#
+    PostgreSQL: Main database for Gitea (only if not using external database)
+    Stores repositories metadata, users, issues, and pull requests
+  #}
   {% if not database_external %}
   {{ service_name }}-postgres:
     image: docker.io/library/postgres:17.6
+    {#
+      If not in swarm mode, apply restart policy and container name
+    #}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     container_name: {{ service_name }}-db
     {% endif %}
+    {#
+      Environment variables for PostgreSQL configuration
+    #}
     environment:
       - TZ={{ container_timezone }}
       - POSTGRES_USER={{ database_user }}
       - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
       - POSTGRES_DB={{ database_name }}
+    {#
+      Health check: Verify PostgreSQL is ready to accept connections
+    #}
     healthcheck:
       test: ["CMD-SHELL", "pg_isready -U {{ database_user }}"]
       start_period: 30s
       interval: 10s
       timeout: 10s
       retries: 5
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if traefik_enabled %}
-      {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
-    {% endif %}
+    {#
+      Volume configuration for PostgreSQL data persistence
+    #}
     volumes:
       - gitea-db:/var/lib/postgresql/data
   {% endif %}
 
+{#
+  Volume definitions:
+  - gitea-data: Persistent storage for repositories and configuration
+  - gitea-db: PostgreSQL database storage (only if not using external database)
+#}
 volumes:
   gitea-data:
     driver: local
@@ -161,30 +197,12 @@ volumes:
     driver: local
   {% endif %}
 
-{% if network_mode != 'host' %}
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
 {% endif %}

+ 2 - 0
library/compose/gitea/template.yaml

@@ -19,6 +19,8 @@ metadata:
   date: '2025-11-05'
   tags:
     - traefik
+    - swarm
+    - authentik
 spec:
   general:
     vars:

+ 9 - 0
library/compose/gitlab-runner/compose.yaml.j2

@@ -1,7 +1,16 @@
+{#
+  GitLab Runner: CI/CD job executor for GitLab
+  Executes pipeline jobs using Docker executor
+#}
 services:
   gitlab-runner:
     image: docker.io/gitlab/gitlab-runner:alpine-v17.9.1
     container_name: gitlab-runner-1
+    {#
+      Volume configuration:
+      - config.toml: Runner configuration (read-only)
+      - Docker socket: Required for Docker executor to spawn job containers
+    #}
     volumes:
       - ./config/config.toml:/etc/gitlab-runner/config.toml:ro
       - /var/run/docker.sock:/var/run/docker.sock

+ 72 - 40
library/compose/gitlab/compose.yaml.j2

@@ -1,34 +1,49 @@
 services:
   {{ service_name }}:
     image: docker.io/gitlab/gitlab-ce:18.5.1-ce.0
+    {#
+      If not in swarm mode, check whether container_name is set and apply restart policy,
+      else swarm mode handles restarts via deploy.restart_policy
+    #}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     container_name: {{ container_name }}
     {% endif %}
+    {#
+      Set container hostname for GitLab identification
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Shared memory size for GitLab (required for proper operation)
+    #}
     shm_size: '256m'
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
     networks:
-      {% if traefik_enabled %}
       {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
+    {#
+      Environment file containing GitLab configuration variables
+    #}
     env_file:
       - ./.env
+    {#
+      When swarm_enabled is set, use Docker configs for GitLab configuration
+    #}
     {% if swarm_enabled %}
     configs:
       - source: gitlab_config
         target: /etc/gitlab/gitlab.rb
     {% endif %}
-    {% if network_mode == 'bridge' %}
+    {#
+      Port mappings:
+      - HTTP: Web interface (only if Traefik is disabled)
+      - SSH: Git SSH access (always exposed, cannot be proxied)
+      - Registry: Container registry (if enabled)
+      Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
+    #}
     ports:
       {% if not traefik_enabled %}
       {% if swarm_enabled %}
@@ -44,7 +59,12 @@ services:
       {% if registry_enabled %}
       - "{{ ports_registry }}:5000"
       {% endif %}
-    {% endif %}
+    {#
+      Volume configuration:
+      - config: GitLab configuration files
+      - logs: GitLab log files
+      - data: GitLab data (repositories, uploads, etc.)
+    #}
     volumes:
       {% if not swarm_enabled %}
       - ./config/gitlab.rb:/etc/gitlab/gitlab.rb:ro
@@ -58,6 +78,11 @@ services:
       - gitlab-logs:/var/log/gitlab
       - gitlab-data:/var/opt/gitlab
       {% endif %}
+    {#
+      When traefik_enabled is set, and not running in swarm mode, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+      Also configure registry routing if registry is enabled
+    #}
     {% if traefik_enabled and not swarm_enabled %}
     labels:
       - traefik.enable=true
@@ -87,16 +112,25 @@ services:
       {% endif %}
       {% endif %}
     {% endif %}
+    {#
+      When swarm_enabled is set, use Docker secrets for sensitive data
+    #}
     {% if swarm_enabled %}
     secrets:
-      - source: {{ gitlab_root_password_secret_name }}
+      - source: {{ service_name }}_root_password
         target: /run/secrets/gitlab_root_password
         mode: 0400
       {% if registry_enabled %}
-      - source: {{ gitlab_registry_secret_name }}
+      - source: {{ service_name }}_registry_secret
         target: /run/secrets/gitlab_registry_secret
         mode: 0400
       {% endif %}
+    {#
+      Deploy configuration for Swarm mode:
+      - Configure replicas, placement constraints, and restart policy
+      - Resources: Set CPU/memory limits (and reservations in Swarm mode)
+      - Traefik: Labels for reverse proxy integration (Swarm mode)
+    #}
     deploy:
       mode: {{ swarm_placement_mode }}
       {% if swarm_placement_mode == 'replicated' %}
@@ -120,6 +154,11 @@ services:
           memory: {{ resources_memory_reservation }}
         {% endif %}
       {% endif %}
+      {#
+        When traefik_enabled is set in swarm mode, add traefik labels
+        (optionally enable TLS if traefik_tls_enabled is set)
+        Also configure registry routing if registry is enabled
+      #}
       {% if swarm_enabled and traefik_enabled %}
       labels:
         - traefik.enable=true
@@ -151,6 +190,12 @@ services:
       {% endif %}
     {% endif %}
 
+{#
+  Volume definitions:
+  - When volume_mode is 'local' (default): use docker-managed local volumes
+  - When volume_mode is 'nfs': configure NFS-backed volumes
+  - When volume_mode is 'mount': no volume definition needed (bind mounts used directly)
+#}
 {% if volume_mode == 'local' %}
 volumes:
   gitlab-config:
@@ -181,44 +226,31 @@ volumes:
       device: ":{{ volume_nfs_path }}/data"
 {% endif %}
 
+{#
+  Docker Swarm configs and secrets (only when swarm_enabled is set):
+  - Config: GitLab configuration file
+  - Secrets: Root password and registry secret (if registry enabled)
+#}
 {% if swarm_enabled %}
 configs:
   gitlab_config:
     file: ./config/gitlab.rb
 
 secrets:
-  {{ gitlab_root_password_secret_name }}:
+  {{ service_name }}_root_password:
     file: ./.env.secret
   {% if registry_enabled %}
-  {{ gitlab_registry_secret_name }}:
+  {{ service_name }}_registry_secret:
     file: ./.env.registry.secret
   {% endif %}
 {% endif %}
 
-{% if network_mode != 'host' %}
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
 {% endif %}

+ 2 - 11
library/compose/gitlab/template.yaml

@@ -25,6 +25,8 @@ metadata:
   date: '2025-10-31'
   tags:
     - traefik
+    - swarm
+    - volume_modes
   next_steps: |
     ## Post-Installation Steps
     1. **Start GitLab**:
@@ -135,17 +137,6 @@ spec:
         type: str
         description: External URL for Container Registry
         default: http://localhost:5000
-  swarm:
-    needs: []
-    vars:
-      gitlab_root_password_secret_name:
-        type: str
-        description: Docker Swarm secret name for root password
-        default: gitlab_root_password
-      gitlab_registry_secret_name:
-        type: str
-        description: Docker Swarm secret name for registry
-        default: gitlab_registry_secret
   advanced:
     title: Advanced Settings
     description: Performance tuning and advanced configuration options

+ 0 - 1
library/compose/grafana/.env.secret.grafana_db_password.j2

@@ -1 +0,0 @@
-{% if swarm_enabled and database_type == 'postgres' %}{{ database_password }}{% endif %}

+ 0 - 1
library/compose/grafana/.env.secret.grafana_oauth_client_id.j2

@@ -1 +0,0 @@
-{% if swarm_enabled and authentik_enabled %}{{ authentik_client_id }}{% endif %}

+ 0 - 1
library/compose/grafana/.env.secret.grafana_oauth_client_secret.j2

@@ -1 +0,0 @@
-{% if swarm_enabled and authentik_enabled %}{{ authentik_client_secret }}{% endif %}

+ 58 - 35
library/compose/grafana/compose.yaml.j2

@@ -1,11 +1,24 @@
 services:
   {{ service_name }}:
     image: docker.io/grafana/grafana-oss:12.1.1
+    {#
+      If not in swarm mode, check whether container_name is set and apply restart policy,
+      else swarm mode handles restarts via deploy.restart_policy
+    #}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     container_name: {{ container_name }}
     {% endif %}
+    {#
+      Set container hostname for identification
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Environment variables for Grafana configuration:
+      - User/group IDs for file permissions
+      - PostgreSQL database connection (if enabled)
+      - Authentik OAuth integration (if enabled)
+    #}
     environment:
       - TZ={{ container_timezone }}
       - UID={{ user_uid }}
@@ -44,21 +57,18 @@ services:
       - GF_AUTH_OAUTH_ALLOW_INSECURE_EMAIL_LOOKUP=true
       - GF_AUTH_GENERIC_OAUTH_SKIP_ORG_ROLE_SYNC=true
       {% endif %}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
     networks:
-      {% if traefik_enabled %}
       {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
+    {#
+      Port mappings for web interface (only when Traefik is disabled)
+      Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
+    #}
+    {% if not traefik_enabled %}
     ports:
       {% if swarm_enabled %}
       - target: 3000
@@ -69,12 +79,18 @@ services:
       - "{{ ports_http }}:3000"
       {% endif %}
     {% endif %}
+    {#
+      Volume configuration for persistent data
+    #}
     volumes:
       {% if volume_mode == 'mount' %}
       - {{ volume_mount_path }}/data:/var/lib/grafana:rw
       {% else %}
       - {{ service_name }}-data:/var/lib/grafana
       {% endif %}
+    {#
+      When swarm_enabled is set, use Docker secrets for sensitive data
+    #}
     {% if swarm_enabled %}
     secrets:
       {% if database_type == 'postgres' %}
@@ -85,6 +101,10 @@ services:
       - {{ service_name }}_oauth_client_secret
       {% endif %}
     {% endif %}
+    {#
+      When traefik_enabled is set, and not running in swarm mode, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
     {% if traefik_enabled and not swarm_enabled %}
     labels:
       - traefik.enable=true
@@ -101,6 +121,12 @@ services:
       - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
       {% endif %}
     {% endif %}
+    {#
+      Deploy configuration for Swarm mode and/or resource limits:
+      - Swarm: Configure replicas, placement constraints, and restart policy
+      - Resources: Set CPU/memory limits (and reservations in Swarm mode)
+      - Traefik: Labels for reverse proxy integration (Swarm mode)
+    #}
     {% if swarm_enabled or resources_enabled %}
     deploy:
       {% if swarm_enabled %}
@@ -127,6 +153,10 @@ services:
           memory: {{ resources_memory_reservation }}
         {% endif %}
       {% endif %}
+      {#
+        When traefik_enabled is set in swarm mode, add traefik labels
+        (optionally enable TLS if traefik_tls_enabled is set)
+      #}
       {% if swarm_enabled and traefik_enabled %}
       labels:
         - traefik.enable=true
@@ -145,6 +175,12 @@ services:
       {% endif %}
     {% endif %}
 
+{#
+  Volume definitions:
+  - When volume_mode is 'local' (default): use docker-managed local volumes
+  - When volume_mode is 'nfs': configure NFS-backed volumes
+  - When volume_mode is 'mount': no volume definition needed (bind mounts used directly)
+#}
 {% if volume_mode == 'local' %}
 volumes:
   {{ service_name }}-data:
@@ -159,34 +195,21 @@ volumes:
       device: ":{{ volume_nfs_path }}/data"
 {% endif %}
 
-{% if network_mode != 'host' %}
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
 {% endif %}
 
+{#
+  Docker Swarm secrets (only when swarm_enabled is set):
+  - Database password for PostgreSQL (if using external database)
+  - OAuth credentials for Authentik integration (if enabled)
+#}
 {% if swarm_enabled %}
 secrets:
   {% if database_type == 'postgres' %}

+ 1 - 0
library/compose/grafana/template.yaml

@@ -20,6 +20,7 @@ metadata:
     - traefik
     - swarm
     - authentik
+    - volume_modes
 spec:
   general:
     vars:

+ 13 - 0
library/compose/heimdall/compose.yaml.j2

@@ -1,12 +1,25 @@
+{#
+  Heimdall: Application dashboard
+  Organizes and provides quick access to web applications
+#}
 services:
   {{ service_name }}:
     image: lscr.io/linuxserver/heimdall:2.7.6
     container_name: {{ container_name }}
+    {#
+      Environment variables for user/group permissions
+    #}
     environment:
       - PUID=1000
       - PGID=1000
+    {#
+      Volume configuration for persistent data
+    #}
     volumes:
       - ./heimdall/config:/config
+    {#
+      Port mappings for HTTP and HTTPS access
+    #}
     ports:
       - 80:80
       - 443:443

+ 58 - 42
library/compose/homepage/compose.yaml.j2

@@ -1,11 +1,23 @@
 services:
   {{ service_name }}:
     image: ghcr.io/gethomepage/homepage:v1.6.1
+    {#
+      If not in swarm mode, check whether container_name is set and apply restart policy,
+      else swarm mode handles restarts via deploy.restart_policy
+    #}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     container_name: {{ container_name }}
     {% endif %}
+    {#
+      Set container hostname for identification
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Environment variables for Homepage configuration:
+      - Timezone, log level, and allowed hosts
+      - Optional user/group IDs for file permissions
+    #}
     environment:
       - TZ={{ container_timezone }}
       - LOG_LEVEL={{ container_loglevel }}
@@ -14,10 +26,26 @@ services:
       - PUID={{ homepage_puid }}
       - PGID={{ homepage_pgid }}
       {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
+    networks:
+      {{ traefik_network }}:
+    {% endif %}
+    {#
+      Port mappings for web interface (only when Traefik is disabled)
+    #}
+    {% if not traefik_enabled %}
     ports:
       - {{ ports_http }}:3000
     {% endif %}
+    {#
+      Volume configuration for persistent data:
+      - config: Dashboard configuration files
+      - images: Custom images for dashboard
+      - icons: Custom icons for services
+    #}
     volumes:
       {% if volume_mode == 'local' %}
       - {{ service_name }}_config:/app/config
@@ -28,22 +56,11 @@ services:
       - {{ volume_mount_path }}/{{ service_name }}/images:/app/images
       - {{ volume_mount_path }}/{{ service_name }}/icons:/app/icons
       {% endif %}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if traefik_enabled %}
-      {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
-    {% endif %}
-    {% if traefik_enabled %}
-    {% if not swarm_enabled %}
+    {#
+      When traefik_enabled is set, and not running in swarm mode, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
+    {% if traefik_enabled and not swarm_enabled %}
     labels:
       - traefik.enable=true
       - traefik.docker.network={{ traefik_network }}
@@ -54,12 +71,17 @@ services:
       {% if traefik_tls_enabled %}
       - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
       - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint %}
       - traefik.http.routers.{{ service_name }}-https.tls=true
       - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
       {% endif %}
     {% endif %}
-    {% endif %}
+    {#
+      Deploy configuration for Swarm mode:
+      - Configure replicas or global mode, placement constraints, and restart policy
+      - Traefik: Labels for reverse proxy integration (Swarm mode)
+      - Resources: Set CPU/memory limits and reservations
+    #}
     {% if swarm_enabled %}
     deploy:
       {% if swarm_placement_mode == 'replicated' %}
@@ -70,6 +92,10 @@ services:
       {% else %}
       mode: global
       {% endif %}
+      {#
+        When traefik_enabled is set in swarm mode, add traefik labels
+        (optionally enable TLS if traefik_tls_enabled is set)
+      #}
       {% if traefik_enabled %}
       labels:
         - traefik.enable=true
@@ -96,33 +122,23 @@ services:
           memory: {{ resources_memory_reservation }}
       {% endif %}
     {% endif %}
-{% if network_mode != 'host' %}
+
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
 {% endif %}
+
+{#
+  Volume definitions (only when volume_mode is 'local'):
+  - config: Dashboard configuration files
+  - images: Custom images for dashboard
+  - icons: Custom icons for services
+#}
 {% if volume_mode == 'local' %}
 volumes:
   {{ service_name }}_config:

+ 2 - 1
library/compose/homepage/template.yaml

@@ -14,8 +14,9 @@ metadata:
   author: Christian Lempa
   date: '2025-11-05'
   tags:
-    - dashboard
     - traefik
+    - swarm
+    - volume_modes
 spec:
   general:
     vars:

+ 42 - 35
library/compose/homer/compose.yaml.j2

@@ -1,28 +1,35 @@
 services:
   {{ service_name }}:
     image: docker.io/b4bz/homer:v25.10.1
+    {#
+      If not in swarm mode, check whether container_name is set and apply restart policy,
+      else swarm mode handles restarts via deploy.restart_policy
+    #}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     container_name: {{ container_name }}
     {% endif %}
+    {#
+      Set container hostname for identification
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Environment variables for Homer configuration
+    #}
     environment:
       - TZ={{ container_timezone }}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
     networks:
-      {% if traefik_enabled %}
       {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
+    {#
+      Port mappings for web interface (only when Traefik is disabled)
+      Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
+    #}
+    {% if not traefik_enabled %}
     ports:
       {% if swarm_enabled %}
       - target: 8080
@@ -33,8 +40,16 @@ services:
       - "{{ ports_http }}:8080"
       {% endif %}
     {% endif %}
+    {#
+      Volume configuration:
+      - assets: Homer dashboard configuration and assets
+    #}
     volumes:
       - ./assets:/www/assets
+    {#
+      When traefik_enabled is set, and not running in swarm mode, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
     {% if traefik_enabled and not swarm_enabled %}
     labels:
       - traefik.enable=true
@@ -51,6 +66,12 @@ services:
       - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
       {% endif %}
     {% endif %}
+    {#
+      Deploy configuration for Swarm mode and/or resource limits:
+      - Swarm: Configure replicas, placement constraints, and restart policy
+      - Resources: Set CPU/memory limits (and reservations in Swarm mode)
+      - Traefik: Labels for reverse proxy integration (Swarm mode)
+    #}
     {% if swarm_enabled or resources_enabled %}
     deploy:
       {% if swarm_enabled %}
@@ -77,6 +98,10 @@ services:
           memory: {{ resources_memory_reservation }}
         {% endif %}
       {% endif %}
+      {#
+        When traefik_enabled is set in swarm mode, add traefik labels
+        (optionally enable TLS if traefik_tls_enabled is set, and authentik middleware if enabled)
+      #}
       {% if swarm_enabled and traefik_enabled %}
       labels:
         - traefik.enable=true
@@ -101,30 +126,12 @@ services:
       {% endif %}
     {% endif %}
 
-{% if network_mode != 'host' %}
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
 {% endif %}

+ 2 - 0
library/compose/homer/template.yaml

@@ -15,6 +15,8 @@ metadata:
   date: '2025-10-31'
   tags:
     - traefik
+    - swarm
+    - authentik
   next_steps: |
     1. Start the Homer dashboard:
        docker compose up -d

+ 33 - 27
library/compose/influxdb/compose.yaml.j2

@@ -1,11 +1,22 @@
 services:
   {{ service_name }}:
     image: docker.io/library/influxdb:2.7.12-alpine
+    {#
+      If not in swarm mode, apply restart policy and container name
+    #}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     container_name: {{ container_name }}
     {% endif %}
+    {#
+      Set container hostname for identification
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Environment variables for InfluxDB initialization:
+      - Initial admin credentials, organization, and bucket
+      - Optional retention period and admin token
+    #}
     environment:
       - TZ={{ container_timezone }}
       - DOCKER_INFLUXDB_INIT_MODE=setup
@@ -19,21 +30,18 @@ services:
       {% if influxdb_init_token %}
       - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=${INFLUXDB_INIT_TOKEN}
       {% endif %}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
     networks:
-      {% if traefik_enabled %}
       {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
+    {#
+      Port mappings for HTTP API (only when Traefik is disabled)
+      Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
+    #}
+    {% if not traefik_enabled %}
     ports:
       {% if swarm_enabled %}
       - target: 8086
@@ -44,6 +52,9 @@ services:
       - "{{ ports_http }}:8086"
       {% endif %}
     {% endif %}
+    {#
+      Volume configuration for persistent data
+    #}
     volumes:
       - influxdb-data:/var/lib/influxdb2
       - /etc/influxdb2:/etc/influxdb2
@@ -109,25 +120,20 @@ services:
       {% endif %}
     {% endif %}
 
+{#
+  Volume definitions:
+  - influxdb-data: Persistent storage for InfluxDB time series data
+#}
 volumes:
   influxdb-data:
     driver: local
 
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
-  {% if network_mode == 'macvlan' %}
-  {{ network_name }}:
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-  {% elif network_mode == 'bridge' %}
-  {{ network_name }}:
-    driver: {% if swarm_enabled %}overlay{% else %}bridge{% endif %}
-  {% endif %}
+{% endif %}

+ 1 - 0
library/compose/influxdb/template.yaml

@@ -18,6 +18,7 @@ metadata:
   date: '2025-09-28'
   tags:
     - traefik
+    - swarm
 spec:
   ports:
     vars:

+ 138 - 0
library/compose/komodo/compose.yaml.j2.final

@@ -0,0 +1,138 @@
+---
+services:
+  {{ service_name }}:
+    image: ghcr.io/moghtech/komodo:latest
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    hostname: {{ container_hostname }}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
+    networks:
+      {{ traefik_network }}:
+    {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if network_mode == 'bridge' and not traefik_enabled %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 9120
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:9120/tcp"
+      {% endif %}
+    {% endif %}
+    {% if environment_enabled %}
+    environment:
+      KOMODO_DATABASE_ADDRESS: "{{ environment_database_address }}"
+      KOMODO_DATABASE_DB_NAME: "{{ environment_database_name }}"
+      {% if environment_database_username %}
+      KOMODO_DATABASE_USERNAME: "{{ environment_database_username }}"
+      {% endif %}
+      {% if environment_database_password %}
+      KOMODO_DATABASE_PASSWORD: "{{ environment_database_password }}"
+      {% endif %}
+      {% if environment_jwt_secret %}
+      KOMODO_JWT_SECRET: "{{ environment_jwt_secret }}"
+      {% endif %}
+      LOG_LEVEL: "{{ environment_log_level }}"
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/data:/app/data:rw
+      - {{ volume_mount_path }}/repos:/app/repos:rw
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-data:/app/data
+      - {{ service_name }}-repos:/app/repos
+      {% endif %}
+    {% if swarm_enabled or resources_enabled %}
+    deploy:
+      {% if swarm_enabled %}
+      mode: replicated
+      replicas: 1
+      restart_policy:
+        condition: on-failure
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        {% if swarm_enabled %}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+        {% endif %}
+      {% endif %}
+      {% if swarm_enabled and traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=9120
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=9120
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}
+
+{% if volume_mode == 'local' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+  {{ service_name }}-repos:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/data"
+  {{ service_name }}-repos:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/repos"
+{% endif %}
+
+    {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}

+ 130 - 0
library/compose/komodo/template.yaml.backup

@@ -0,0 +1,130 @@
+---
+kind: compose
+schema: "1.2"
+metadata:
+  name: Komodo
+  description: |
+    Build and deployment automation tool for managing software across multiple servers. Komodo provides
+    unlimited server connections, flexible API access, and comprehensive management of Docker deployments,
+    stacks, and builds. Features include real-time container monitoring, batch operations, and integration
+    with Docker, Docker Compose, and build systems. Supports both MongoDB and FerretDB as database backends.
+    ## Important Notes
+    * Requires MongoDB or FerretDB for data storage (database not included in this template)
+    * Requires Periphery agent on managed servers for remote operations
+    * Web interface and API accessible through configured ports
+    ## References
+    * **Project:** https://github.com/moghtech/komodo
+    * **Documentation:** https://github.com/moghtech/komodo/tree/main/docsite/docs
+    * **Docker Hub:** https://hub.docker.com/r/moghtech/komodo
+  version: latest
+  author: Christian Lempa
+  date: '2025-11-13'
+  tags:
+    - traefik
+    - swarm
+    - deployment
+    - automation
+  next_steps: |
+    ### 1. Prerequisites
+    * Deploy MongoDB or FerretDB database
+    * Configure database connection in environment variables
+    * Install Periphery agent on servers you want to manage
+    ### 2. Deploy the Service
+    {% if swarm_enabled -%}
+    Deploy to Docker Swarm:
+    ```bash
+    docker stack deploy -c compose.yaml komodo
+    ```
+    {% else -%}
+    Start Komodo using Docker Compose:
+    ```bash
+    docker compose up -d
+    ```
+    {% endif -%}
+    ### 3. Access the Web Interface
+    {% if traefik_enabled -%}
+    * Navigate to: **https://{{ traefik_host }}.{{ traefik_domain }}**
+    {% else -%}
+    * Navigate to: **http://localhost:{{ ports_http }}**
+    {% endif -%}
+    * Complete initial setup and create admin user
+    ### 4. Install Periphery Agent
+    On each server you want to manage:
+    ```bash
+    curl -sSL https://raw.githubusercontent.com/moghtech/komodo/main/scripts/setup-periphery.py | python3
+    ```
+    ### 5. Configure Servers
+    * Add servers to Komodo through the web interface
+    * Configure API keys for programmatic access
+    * Start managing deployments, stacks, and builds
+spec:
+  general:
+    vars:
+      service_name:
+        default: "komodo"
+      container_name:
+        default: "komodo"
+      container_hostname:
+        default: "komodo"
+  traefik:
+    vars:
+      traefik_host:
+        default: "komodo"
+  network:
+    vars:
+      network_name:
+        default: "komodo_network"
+  ports:
+    vars:
+      ports_http:
+        description: "External HTTP port (web interface and API)"
+        type: int
+        default: 9120
+        needs: ["traefik_enabled=false", "network_mode=bridge"]
+  volume:
+    vars:
+      volume_mount_path:
+        default: "/mnt/storage/komodo"
+  environment:
+    title: "Environment Variables"
+    toggle: environment_enabled
+    required: true
+    vars:
+      environment_enabled:
+        type: bool
+        default: true
+        description: "Configure environment variables (required)"
+      environment_database_address:
+        type: str
+        default: "mongodb://mongo:27017"
+        description: "Database connection address (MongoDB or FerretDB)"
+        needs: "environment_enabled=true"
+      environment_database_name:
+        type: str
+        default: "komodo"
+        description: "Database name"
+        needs: "environment_enabled=true"
+      environment_database_username:
+        type: str
+        default: ""
+        description: "Database username (optional)"
+        needs: "environment_enabled=true"
+      environment_database_password:
+        type: str
+        default: ""
+        sensitive: true
+        description: "Database password (optional)"
+        needs: "environment_enabled=true"
+      environment_jwt_secret:
+        type: str
+        default: ""
+        sensitive: true
+        autogenerated: true
+        description: "JWT secret for authentication (auto-generated if empty)"
+        needs: "environment_enabled=true"
+      environment_log_level:
+        type: enum
+        default: "info"
+        options: ["debug", "info", "warn", "error"]
+        description: "Log level"
+        needs: "environment_enabled=true"

+ 154 - 0
library/compose/loki/compose.yaml.j2.final

@@ -0,0 +1,154 @@
+services:
+  {{ service_name }}:
+    image: docker.io/grafana/loki:3.5.8
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    hostname: {{ container_hostname }}
+    command: "-config.file=/etc/loki/config.yaml"
+    environment:
+      - TZ={{ container_timezone }}
+      - UID={{ user_uid }}
+      - GID={{ user_gid }}
+    {% if not traefik_enabled and network_mode == 'bridge' %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 3100
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:3100"
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/data:/loki:rw
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-data:/loki:rw
+      {% endif %}
+      {% if not swarm_enabled %}
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/config/config.yaml:/etc/loki/config.yaml:ro
+      {% else %}
+      - ./config/config.yaml:/etc/loki/config.yaml:ro
+      {% endif %}
+      {% endif %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
+    networks:
+      {{ traefik_network }}:
+    {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if swarm_enabled %}
+    configs:
+      - source: {{ service_name }}_config
+        target: /etc/loki/config.yaml
+    {% endif %}
+    {% if swarm_enabled or resources_enabled %}
+    deploy:
+      {% if swarm_enabled %}
+      mode: {{ swarm_placement_mode }}
+      {% if swarm_placement_mode == 'replicated' %}
+      replicas: {{ swarm_replicas }}
+      {% endif %}
+      {% if swarm_placement_host %}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      {% endif %}
+      restart_policy:
+        condition: on-failure
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        {% if swarm_enabled %}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+        {% endif %}
+      {% endif %}
+      {% if swarm_enabled and traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=3100
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if authentik_enabled %}
+        - traefik.http.routers.{{ service_name }}-http.middlewares={{ authentik_traefik_middleware }}
+        {% endif %}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% if authentik_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.middlewares={{ authentik_traefik_middleware }}
+        {% endif %}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=3100
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if authentik_enabled %}
+      - traefik.http.routers.{{ service_name }}-http.middlewares={{ authentik_traefik_middleware }}
+      {% endif %}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% if authentik_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.middlewares={{ authentik_traefik_middleware }}
+      {% endif %}
+      {% endif %}
+    {% endif %}
+
+volumes:
+{% if volume_mode == 'local' %}
+  {{ service_name }}-data:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/data"
+{% endif %}
+
+    {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}
+
+{% if swarm_enabled %}
+configs:
+  {{ service_name }}_config:
+    file: ./config/config.yaml
+{% endif %}

+ 148 - 0
library/compose/loki/compose.yaml.j2.portfix

@@ -0,0 +1,148 @@
+services:
+  {{ service_name }}:
+    image: docker.io/grafana/loki:3.5.8
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    hostname: {{ container_hostname }}
+    command: "-config.file=/etc/loki/config.yaml"
+    environment:
+      - TZ={{ container_timezone }}
+      - UID={{ user_uid }}
+      - GID={{ user_gid }}
+    {% if not traefik_enabled and network_mode == 'bridge' %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 3100
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:3100"
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/data:/loki:rw
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-data:/loki:rw
+      {% endif %}
+      {% if not swarm_enabled %}
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/config/config.yaml:/etc/loki/config.yaml:ro
+      {% else %}
+      - ./config/config.yaml:/etc/loki/config.yaml:ro
+      {% endif %}
+      {% endif %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
+    networks:
+      {{ traefik_network }}:
+    {% endif %}
+    {% endif %}
+    {% if swarm_enabled %}
+    configs:
+      - source: {{ service_name }}_config
+        target: /etc/loki/config.yaml
+    {% endif %}
+    {% if swarm_enabled or resources_enabled %}
+    deploy:
+      {% if swarm_enabled %}
+      mode: {{ swarm_placement_mode }}
+      {% if swarm_placement_mode == 'replicated' %}
+      replicas: {{ swarm_replicas }}
+      {% endif %}
+      {% if swarm_placement_host %}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      {% endif %}
+      restart_policy:
+        condition: on-failure
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        {% if swarm_enabled %}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+        {% endif %}
+      {% endif %}
+      {% if swarm_enabled and traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=3100
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if authentik_enabled %}
+        - traefik.http.routers.{{ service_name }}-http.middlewares={{ authentik_traefik_middleware }}
+        {% endif %}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% if authentik_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.middlewares={{ authentik_traefik_middleware }}
+        {% endif %}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=3100
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if authentik_enabled %}
+      - traefik.http.routers.{{ service_name }}-http.middlewares={{ authentik_traefik_middleware }}
+      {% endif %}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% if authentik_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.middlewares={{ authentik_traefik_middleware }}
+      {% endif %}
+      {% endif %}
+    {% endif %}
+
+volumes:
+{% if volume_mode == 'local' %}
+  {{ service_name }}-data:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/data"
+{% endif %}
+
+    {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}
+
+{% if swarm_enabled %}
+configs:
+  {{ service_name }}_config:
+    file: ./config/config.yaml
+{% endif %}

+ 46 - 0
library/compose/loki/template.yaml.backup

@@ -0,0 +1,46 @@
+---
+kind: compose
+schema: "1.2"
+metadata:
+  name: Loki
+  description: >
+    Loki is a horizontally scalable, highly available, multi-tenant log aggregation system inspired by Prometheus.
+    This template sets up Loki in a Docker container using Docker Compose.
+
+
+    Project: https://grafana.com/oss/loki/
+
+    Documentation: https://grafana.com/docs/loki/latest/
+
+    GitHub: https://github.com/grafana/loki
+  version: 3.5.8
+  author: Christian Lempa
+  date: '2025-11-07'
+  tags:
+    - traefik
+    - swarm
+    - authentik
+spec:
+  general:
+    vars:
+      service_name:
+        default: loki
+      container_name:
+        default: loki
+      container_hostname:
+        default: loki
+  ports:
+    vars:
+      ports_http:
+        description: "Loki HTTP API port"
+        type: int
+        default: 3100
+        needs: ["traefik_enabled=false", "network_mode=bridge"]
+  network:
+    vars:
+      network_name:
+        default: "loki_network"
+  traefik:
+    vars:
+      traefik_host:
+        default: "loki.home.arpa"

+ 84 - 43
library/compose/mariadb/compose.yaml.j2

@@ -1,11 +1,26 @@
 services:
   {{ service_name }}:
     image: docker.io/library/mariadb:{{ mariadb_version }}
+    {#
+      If not in swarm mode, apply restart policy and container_name,
+      else swarm mode handles restarts via deploy.restart_policy
+    #}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     container_name: {{ container_name }}
     {% endif %}
+    {#
+      Set container hostname
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Environment variables for MariaDB configuration
+      - TZ: Timezone
+      - MARIADB_ROOT_PASSWORD: Root password (set, random, or empty)
+      - MARIADB_DATABASE: Default database name
+      - MARIADB_USER: Default user name
+      - MARIADB_PASSWORD: Default user password (from env or secret)
+    #}
     environment:
       - TZ={{ container_timezone }}
       {% if mariadb_root_password_mode == 'set' %}
@@ -22,38 +37,53 @@ services:
       {% else %}
       - MARIADB_PASSWORD={{ mariadb_password }}
       {% endif %}
+    {#
+      Network configuration:
+      - Databases typically use bridge networking for internal communication
+      - Default to bridge network if not specified
+    #}
+    networks:
+      {{ network_name }}:
+    {#
+      Port mappings (only expose if enabled):
+      - MariaDB default port 3306
+      Note: Swarm mode uses 'host' mode for port publishing
+    #}
     {% if mariadb_port_enabled %}
     ports:
-      - {{ mariadb_port }}:3306
+      {% if swarm_enabled %}
+      - target: 3306
+        published: {{ mariadb_port }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ mariadb_port }}:3306"
+      {% endif %}
     {% endif %}
+    {#
+      Volume configuration for persistent data
+      - When volume_mode is 'mount': bind mount from host path
+      - When volume_mode is 'local', 'nfs', or empty: use docker-managed volumes
+    #}
     volumes:
-      {% if volume_mode == 'local' %}
-      - {{ service_name }}_data:/var/lib/mysql
-      {% elif volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/{{ service_name }}:/var/lib/mysql
-      {% elif volume_mode == 'nfs' %}
-      - type: volume
-        source: {{ service_name }}_data
-        target: /var/lib/mysql
-        volume:
-          nocopy: true
-      {% endif %}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/{{ service_name }}:/var/lib/mysql:rw
+      {% else %}
+      - {{ service_name }}-data:/var/lib/mysql
       {% endif %}
-    {% endif %}
+    {#
+      Deploy configuration for Swarm mode:
+      - Single replica (MariaDB doesn't support multi-replica without replication setup)
+      - For HA, use Galera Cluster or MariaDB replication
+      - Uses Docker secrets for password management
+      - Optional resource limits and reservations
+    #}
     {% if swarm_enabled %}
     secrets:
       - mariadb_password
     deploy:
       {% if swarm_placement_mode == 'replicated' %}
+      mode: replicated
       replicas: {{ swarm_replicas }}
       placement:
         constraints:
@@ -61,6 +91,8 @@ services:
       {% else %}
       mode: global
       {% endif %}
+      restart_policy:
+        condition: on-failure
       {% if resources_enabled %}
       resources:
         limits:
@@ -71,38 +103,47 @@ services:
           memory: {{ resources_memory_reservation }}
       {% endif %}
     {% endif %}
-{% if network_mode != 'host' %}
+{#
+  Volume definitions:
+  - When volume_mode is 'local' (default): use docker-managed local volumes
+  - When volume_mode is 'nfs': configure NFS-backed volumes
+  - When volume_mode is 'mount': no volume definition needed (bind mounts used directly)
+#}
+{% if volume_mode == 'local' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/{{ service_name }}"
+{% endif %}
+
+{#
+  Network definitions:
+  - Bridge network for service communication
+  - Use overlay network in Swarm mode for multi-host communication
+#}
 networks:
   {{ network_name }}:
     {% if network_external %}
     external: true
     {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
+    {% if swarm_enabled %}
     driver: overlay
     attachable: true
     {% else %}
     driver: bridge
     {% endif %}
     {% endif %}
-{% endif %}
-volumes:
-  {{ service_name }}_data:
-    {% if volume_mode == 'nfs' %}
-    driver: local
-    driver_opts:
-      type: nfs
-      o: {{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/{{ service_name }}"
-    {% endif %}
+
+{#
+  Docker Swarm secrets (external secrets managed via docker secret create)
+#}
 {% if swarm_enabled %}
 secrets:
   mariadb_password:

+ 2 - 2
library/compose/mariadb/template.yaml

@@ -14,8 +14,8 @@ metadata:
   author: Christian Lempa
   date: '2025-09-28'
   tags:
-    - database
-    - mysql
+    - swarm
+    - volume_mode
 spec:
   general:
     vars:

+ 36 - 23
library/compose/n8n-worker/compose.yaml.j2

@@ -2,10 +2,20 @@ services:
   {{ service_name }}:
     image: n8nio/n8n:1.118.1
     command: worker
+    {#
+      If not in swarm mode, apply container name and hostname
+    #}
     {% if not swarm_enabled -%}
     container_name: {{ container_name }}
     hostname: {{ container_hostname }}
     {% endif -%}
+    {#
+      Environment variables for n8n worker configuration:
+      - Database connection (must match n8n-server)
+      - Encryption key (must match n8n-server)
+      - Redis queue configuration
+      - Optional metrics
+    #}
     environment:
       - N8N_LOG_LEVEL={{ container_loglevel }}
       - GENERIC_TIMEZONE={{ container_timezone }}
@@ -18,7 +28,7 @@ services:
       - DB_POSTGRESDB_DATABASE={{ database_name }}
       - DB_POSTGRESDB_USER={{ database_user }}
       {% if swarm_enabled -%}
-      - DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
+      - DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/{{ service_name }}_database_password
       {% else -%}
       - DB_POSTGRESDB_PASSWORD={{ database_password }}
       {% endif -%}
@@ -29,14 +39,14 @@ services:
       - DB_MYSQLDB_DATABASE={{ database_name }}
       - DB_MYSQLDB_USER={{ database_user }}
       {% if swarm_enabled -%}
-      - DB_MYSQLDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
+      - DB_MYSQLDB_PASSWORD_FILE=/run/secrets/{{ service_name }}_database_password
       {% else -%}
       - DB_MYSQLDB_PASSWORD={{ database_password }}
       {% endif -%}
       {% endif -%}
       {% endif -%}
       {% if swarm_enabled -%}
-      - N8N_ENCRYPTION_KEY_FILE=/run/secrets/{{ encryption_key_secret_name }}
+      - N8N_ENCRYPTION_KEY_FILE=/run/secrets/{{ service_name }}_encryption_key
       {% else -%}
       - N8N_ENCRYPTION_KEY={{ encryption_key }}
       {% endif -%}
@@ -51,18 +61,23 @@ services:
       - N8N_METRICS_INCLUDE_NODE_TYPE_LABEL=true
       {% endif -%}
       {% endif -%}
+    {#
+      Volume configuration for persistent data
+    #}
     volumes:
       - /etc/localtime:/etc/localtime:ro
       - data:/home/node/.n8n
-    {% if network_mode == 'bridge' -%}
-    networks:
-      - {{ network_name }}
-    {% else -%}
-    network_mode: {{ network_mode }}
-    {% endif -%}
+    {#
+      If not in swarm mode, apply restart policy
+    #}
     {% if not swarm_enabled -%}
     restart: {{ restart_policy }}
     {% endif -%}
+    {#
+      Deploy configuration for Swarm mode:
+      - Multiple worker replicas for scaling
+      - Uses Docker secrets for sensitive data
+    #}
     {% if swarm_enabled -%}
     deploy:
       replicas: {{ swarm_replicas }}
@@ -72,32 +87,30 @@ services:
           - node.hostname == {{ swarm_placement_host }}
       {% endif -%}
     secrets:
-      - {{ encryption_key_secret_name }}
+      - {{ service_name }}_encryption_key
       {% if database_enabled -%}
-      - {{ database_password_secret_name }}
+      - {{ service_name }}_database_password
       {% endif -%}
     {% endif -%}
 
+{#
+  Volume definitions:
+  - data: Persistent storage for n8n worker data
+#}
 volumes:
   data:
     driver: local
 
-{% if network_mode == 'bridge' -%}
-networks:
-  {{ network_name }}:
-  {% if network_external -%}
-    external: true
-  {% else -%}
-    driver: bridge
-  {% endif -%}
-{% endif -%}
-
+{#
+  Docker Swarm secrets (only when swarm_enabled is set):
+  - Encryption key and database password (external secrets)
+#}
 {% if swarm_enabled -%}
 secrets:
-  {{ encryption_key_secret_name }}:
+  {{ service_name }}_encryption_key:
     external: true
   {% if database_enabled -%}
-  {{ database_password_secret_name }}:
+  {{ service_name }}_database_password:
     external: true
   {% endif -%}
 {% endif -%}

+ 2 - 12
library/compose/n8n-worker/template.yaml

@@ -27,9 +27,9 @@ metadata:
   next_steps: |
     {% if swarm_enabled -%}
     1. Ensure secrets are created (shared with n8n-server):
-       echo "your-encryption-key" | docker secret create {{ encryption_key_secret_name }} -
+       echo "your-encryption-key" | docker secret create {{ service_name }}_encryption_key -
        {% if database_enabled and database_type == 'postgres' -%}
-       echo "your-db-password" | docker secret create {{ database_password_secret_name }} -
+       echo "your-db-password" | docker secret create {{ service_name }}_database_password -
        {%- endif %}
     2. Deploy to Docker Swarm:
        docker stack deploy -c compose.yaml {{ service_name }}
@@ -141,13 +141,3 @@ spec:
         default: 1
         needs: "swarm_enabled"
         extra: "Scale workers based on workload (typically 1-5)"
-      encryption_key_secret_name:
-        type: str
-        description: "Docker Swarm secret name for encryption key"
-        default: "n8n_encryption_key"
-        needs: "swarm_enabled"
-      database_password_secret_name:
-        type: str
-        description: "Docker Swarm secret name for database password"
-        default: "n8n_database_password"
-        needs: ["swarm_enabled", "database_enabled"]

+ 0 - 296
library/compose/n8n/compose.yaml.j2

@@ -7,299 +7,3 @@ services:
     {% endif -%}
     volumes:
       - redis_data:/data
-    {% if network_mode == 'bridge' -%}
-    networks:
-      - {{ network_name }}
-    {% else -%}
-    network_mode: {{ network_mode }}
-    {% endif -%}
-    {% if not swarm_enabled -%}
-    restart: {{ restart_policy }}
-    {% endif -%}
-    healthcheck:
-      test: ["CMD", "redis-cli", "ping"]
-      interval: 5s
-      timeout: 3s
-      retries: 5
-    {% if swarm_enabled -%}
-    deploy:
-      replicas: 1
-      placement:
-        constraints:
-          - node.role == manager
-    {% endif -%}
-
-  {% endif -%}
-  {{ service_name }}:
-    image: n8nio/n8n:1.118.1
-    {% if not swarm_enabled -%}
-    container_name: {{ container_name }}
-    hostname: {{ container_hostname }}
-    {% endif -%}
-    environment:
-      - N8N_LOG_LEVEL={{ container_loglevel }}
-      - GENERIC_TIMEZONE={{ container_timezone }}
-      - TZ={{ container_timezone }}
-      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
-      - N8N_RUNNERS_ENABLED=true
-      {% if traefik_enabled -%}
-      - N8N_HOST={{ traefik_host }}
-      {% if traefik_tls_enabled -%}
-      - N8N_PROTOCOL=https
-      - N8N_EDITOR_BASE_URL=https://{{ traefik_host }}
-      {% else -%}
-      - N8N_PROTOCOL=http
-      - N8N_EDITOR_BASE_URL=http://{{ traefik_host }}
-      {% endif -%}
-      {% endif -%}
-      - NODE_ENV=production
-      {% if database_enabled -%}
-      {% if database_type == 'postgres' -%}
-      - DB_TYPE=postgresdb
-      - DB_POSTGRESDB_HOST={{ database_host }}
-      - DB_POSTGRESDB_PORT={{ database_port }}
-      - DB_POSTGRESDB_DATABASE={{ database_name }}
-      - DB_POSTGRESDB_USER={{ database_user }}
-      {% if swarm_enabled -%}
-      - DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
-      {% else -%}
-      - DB_POSTGRESDB_PASSWORD={{ database_password }}
-      {% endif -%}
-      {% elif database_type == 'mysql' -%}
-      - DB_TYPE=mysqldb
-      - DB_MYSQLDB_HOST={{ database_host }}
-      - DB_MYSQLDB_PORT={{ database_port }}
-      - DB_MYSQLDB_DATABASE={{ database_name }}
-      - DB_MYSQLDB_USER={{ database_user }}
-      {% if swarm_enabled -%}
-      - DB_MYSQLDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
-      {% else -%}
-      - DB_MYSQLDB_PASSWORD={{ database_password }}
-      {% endif -%}
-      {% endif -%}
-      {% endif -%}
-      {% if swarm_enabled -%}
-      - N8N_ENCRYPTION_KEY_FILE=/run/secrets/{{ encryption_key_secret_name }}
-      {% else -%}
-      - N8N_ENCRYPTION_KEY={{ encryption_key }}
-      {% endif -%}
-      {% if webhook_url -%}
-      - WEBHOOK_URL={{ webhook_url }}
-      {% endif -%}
-      - N8N_PROXY_HOPS={{ proxy_hops }}
-      {% if metrics_enabled -%}
-      - N8N_METRICS=true
-      {% if metrics_detailed -%}
-      - N8N_METRICS_INCLUDE_WORKFLOW_ID_LABELS=true
-      - N8N_METRICS_INCLUDE_NODE_TYPE_LABEL=true
-      - N8N_METRICS_INCLUDE_API_ENDPOINTS=true
-      - N8N_METRICS_INCLUDE_API_STATUS_CODE_LABELS=true
-      - N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL=true
-      {% endif -%}
-      {% endif -%}
-      - EXECUTIONS_DATA_SAVE_ON_ERROR={{ execution_save_on_error }}
-      - EXECUTIONS_DATA_SAVE_ON_SUCCESS={{ execution_save_on_success }}
-      {% if queue_enabled -%}
-      - EXECUTIONS_MODE=queue
-      - QUEUE_BULL_REDIS_HOST={{ queue_redis_host }}
-      - QUEUE_BULL_REDIS_PORT={{ queue_redis_port }}
-      - QUEUE_HEALTH_CHECK_ACTIVE=true
-      {% if metrics_enabled -%}
-      - N8N_METRICS_INCLUDE_QUEUE_METRICS=true
-      {% endif -%}
-      {% endif -%}
-    volumes:
-      - /etc/localtime:/etc/localtime:ro
-      - data:/home/node/.n8n
-    {% if network_mode == 'bridge' -%}
-    networks:
-      {% if traefik_enabled -%}
-      - {{ traefik_network }}
-      {% endif -%}
-      - {{ network_name }}
-    {% else -%}
-    network_mode: {{ network_mode }}
-    {% endif -%}
-    {% if queue_enabled and not queue_redis_external -%}
-    depends_on:
-      redis:
-        condition: service_healthy
-    {% endif -%}
-    {% if traefik_enabled -%}
-    labels:
-      - traefik.enable=true
-      {% if network_mode == 'bridge' -%}
-      - traefik.docker.network={{ traefik_network }}
-      {% endif -%}
-      {% if traefik_webhook_host -%}
-      - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`) || Host(`{{ traefik_webhook_host }}`)
-      {% else -%}
-      - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-      {% endif -%}
-      {% if traefik_tls_enabled -%}
-      - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_tls_entrypoint }}
-      - traefik.http.routers.{{ service_name }}.tls=true
-      - traefik.http.routers.{{ service_name }}.tls.certresolver={{ traefik_tls_certresolver }}
-      {% else -%}
-      - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_entrypoint }}
-      {% endif -%}
-      - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=5678
-      - traefik.http.routers.{{ service_name }}.service={{ service_name }}-web
-    {% endif -%}
-    {% if not swarm_enabled -%}
-    restart: {{ restart_policy }}
-    {% endif -%}
-    {% if swarm_enabled -%}
-    deploy:
-      replicas: {{ swarm_replicas }}
-      {% if swarm_placement_host -%}
-      placement:
-        constraints:
-          - node.hostname == {{ swarm_placement_host }}
-      {% endif -%}
-      {% if traefik_enabled -%}
-      labels:
-        - traefik.enable=true
-        - traefik.docker.network={{ traefik_network }}
-        {% if traefik_webhook_host -%}
-        - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`) || Host(`{{ traefik_webhook_host }}`)
-        {% else -%}
-        - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-        {% endif -%}
-        {% if traefik_tls_enabled -%}
-        - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_tls_entrypoint }}
-        - traefik.http.routers.{{ service_name }}.tls=true
-        - traefik.http.routers.{{ service_name }}.tls.certresolver={{ traefik_tls_certresolver }}
-        {% else -%}
-        - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_entrypoint }}
-        {% endif -%}
-        - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=5678
-        - traefik.http.routers.{{ service_name }}.service={{ service_name }}-web
-      {% endif -%}
-    secrets:
-      - {{ encryption_key_secret_name }}
-      {% if database_enabled -%}
-      - {{ database_password_secret_name }}
-      {% endif -%}
-    {% endif -%}
-{% if queue_enabled and queue_embedded_worker -%}
-
-  {{ service_name }}-worker:
-    image: n8nio/n8n:1.118.1
-    command: worker
-    {% if not swarm_enabled -%}
-    container_name: {{ container_name }}-worker
-    {% endif -%}
-    environment:
-      - N8N_LOG_LEVEL={{ container_loglevel }}
-      - GENERIC_TIMEZONE={{ container_timezone }}
-      - TZ={{ container_timezone }}
-      {% if database_enabled -%}
-      {% if database_type == 'postgres' -%}
-      - DB_TYPE=postgresdb
-      - DB_POSTGRESDB_HOST={{ database_host }}
-      - DB_POSTGRESDB_PORT={{ database_port }}
-      - DB_POSTGRESDB_DATABASE={{ database_name }}
-      - DB_POSTGRESDB_USER={{ database_user }}
-      {% if swarm_enabled -%}
-      - DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
-      {% else -%}
-      - DB_POSTGRESDB_PASSWORD={{ database_password }}
-      {% endif -%}
-      {% elif database_type == 'mysql' -%}
-      - DB_TYPE=mysqldb
-      - DB_MYSQLDB_HOST={{ database_host }}
-      - DB_MYSQLDB_PORT={{ database_port }}
-      - DB_MYSQLDB_DATABASE={{ database_name }}
-      - DB_MYSQLDB_USER={{ database_user }}
-      {% if swarm_enabled -%}
-      - DB_MYSQLDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
-      {% else -%}
-      - DB_MYSQLDB_PASSWORD={{ database_password }}
-      {% endif -%}
-      {% endif -%}
-      {% endif -%}
-      {% if swarm_enabled -%}
-      - N8N_ENCRYPTION_KEY_FILE=/run/secrets/{{ encryption_key_secret_name }}
-      {% else -%}
-      - N8N_ENCRYPTION_KEY={{ encryption_key }}
-      {% endif -%}
-      - EXECUTIONS_MODE=queue
-      - QUEUE_BULL_REDIS_HOST={{ queue_redis_host }}
-      - QUEUE_BULL_REDIS_PORT={{ queue_redis_port }}
-      - QUEUE_HEALTH_CHECK_ACTIVE=true
-      {% if metrics_enabled -%}
-      - N8N_METRICS=true
-      {% if metrics_detailed -%}
-      - N8N_METRICS_INCLUDE_WORKFLOW_ID_LABELS=true
-      - N8N_METRICS_INCLUDE_NODE_TYPE_LABEL=true
-      {% endif -%}
-      {% endif -%}
-    volumes:
-      - /etc/localtime:/etc/localtime:ro
-      - data:/home/node/.n8n
-    {% if network_mode == 'bridge' -%}
-    networks:
-      - {{ network_name }}
-    {% else -%}
-    network_mode: {{ network_mode }}
-    {% endif -%}
-    {% if not queue_redis_external -%}
-    depends_on:
-      redis:
-        condition: service_healthy
-    {% endif -%}
-    {% if not swarm_enabled -%}
-    restart: {{ restart_policy }}
-    {% endif -%}
-    {% if swarm_enabled -%}
-    deploy:
-      replicas: 1
-      {% if swarm_placement_host -%}
-      placement:
-        constraints:
-          - node.hostname == {{ swarm_placement_host }}
-      {% endif -%}
-    secrets:
-      - {{ encryption_key_secret_name }}
-      {% if database_enabled -%}
-      - {{ database_password_secret_name }}
-      {% endif -%}
-    {% endif -%}
-{% endif -%}
-
-volumes:
-  data:
-    driver: local
-{% if queue_enabled and not queue_redis_external -%}
-  redis_data:
-    driver: local
-{% endif -%}
-
-{% if network_mode == 'bridge' -%}
-networks:
-  {{ network_name }}:
-  {% if network_external -%}
-    external: true
-  {% else -%}
-    driver: bridge
-  {% endif -%}
-  {% if traefik_enabled -%}
-  {{ traefik_network }}:
-  {% if traefik_network_external -%}
-    external: true
-  {% else -%}
-    driver: bridge
-  {% endif -%}
-  {% endif -%}
-{% endif -%}
-
-{% if swarm_enabled -%}
-secrets:
-  {{ encryption_key_secret_name }}:
-    external: true
-  {% if database_enabled -%}
-  {{ database_password_secret_name }}:
-    external: true
-  {% endif -%}
-{% endif -%}

+ 305 - 0
library/compose/n8n/compose.yaml.j2.bak3

@@ -0,0 +1,305 @@
+{% if queue_enabled and not queue_redis_external -%}
+services:
+  redis:
+    image: redis:7-alpine
+    {% if not swarm_enabled -%}
+    container_name: {{ service_name }}-redis
+    {% endif -%}
+    volumes:
+      - redis_data:/data
+    {% if network_mode == 'bridge' -%}
+    networks:
+      - {{ network_name }}
+    {% else -%}
+    network_mode: {{ network_mode }}
+    {% endif -%}
+    {% if not swarm_enabled -%}
+    restart: {{ restart_policy }}
+    {% endif -%}
+    healthcheck:
+      test: ["CMD", "redis-cli", "ping"]
+      interval: 5s
+      timeout: 3s
+      retries: 5
+    {% if swarm_enabled -%}
+    deploy:
+      replicas: 1
+      placement:
+        constraints:
+          - node.role == manager
+    {% endif -%}
+
+  {% endif -%}
+  {{ service_name }}:
+    image: n8nio/n8n:1.118.1
+    {% if not swarm_enabled -%}
+    container_name: {{ container_name }}
+    hostname: {{ container_hostname }}
+    {% endif -%}
+    environment:
+      - N8N_LOG_LEVEL={{ container_loglevel }}
+      - GENERIC_TIMEZONE={{ container_timezone }}
+      - TZ={{ container_timezone }}
+      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
+      - N8N_RUNNERS_ENABLED=true
+      {% if traefik_enabled -%}
+      - N8N_HOST={{ traefik_host }}
+      {% if traefik_tls_enabled -%}
+      - N8N_PROTOCOL=https
+      - N8N_EDITOR_BASE_URL=https://{{ traefik_host }}
+      {% else -%}
+      - N8N_PROTOCOL=http
+      - N8N_EDITOR_BASE_URL=http://{{ traefik_host }}
+      {% endif -%}
+      {% endif -%}
+      - NODE_ENV=production
+      {% if database_enabled -%}
+      {% if database_type == 'postgres' -%}
+      - DB_TYPE=postgresdb
+      - DB_POSTGRESDB_HOST={{ database_host }}
+      - DB_POSTGRESDB_PORT={{ database_port }}
+      - DB_POSTGRESDB_DATABASE={{ database_name }}
+      - DB_POSTGRESDB_USER={{ database_user }}
+      {% if swarm_enabled -%}
+      - DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
+      {% else -%}
+      - DB_POSTGRESDB_PASSWORD={{ database_password }}
+      {% endif -%}
+      {% elif database_type == 'mysql' -%}
+      - DB_TYPE=mysqldb
+      - DB_MYSQLDB_HOST={{ database_host }}
+      - DB_MYSQLDB_PORT={{ database_port }}
+      - DB_MYSQLDB_DATABASE={{ database_name }}
+      - DB_MYSQLDB_USER={{ database_user }}
+      {% if swarm_enabled -%}
+      - DB_MYSQLDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
+      {% else -%}
+      - DB_MYSQLDB_PASSWORD={{ database_password }}
+      {% endif -%}
+      {% endif -%}
+      {% endif -%}
+      {% if swarm_enabled -%}
+      - N8N_ENCRYPTION_KEY_FILE=/run/secrets/{{ encryption_key_secret_name }}
+      {% else -%}
+      - N8N_ENCRYPTION_KEY={{ encryption_key }}
+      {% endif -%}
+      {% if webhook_url -%}
+      - WEBHOOK_URL={{ webhook_url }}
+      {% endif -%}
+      - N8N_PROXY_HOPS={{ proxy_hops }}
+      {% if metrics_enabled -%}
+      - N8N_METRICS=true
+      {% if metrics_detailed -%}
+      - N8N_METRICS_INCLUDE_WORKFLOW_ID_LABELS=true
+      - N8N_METRICS_INCLUDE_NODE_TYPE_LABEL=true
+      - N8N_METRICS_INCLUDE_API_ENDPOINTS=true
+      - N8N_METRICS_INCLUDE_API_STATUS_CODE_LABELS=true
+      - N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL=true
+      {% endif -%}
+      {% endif -%}
+      - EXECUTIONS_DATA_SAVE_ON_ERROR={{ execution_save_on_error }}
+      - EXECUTIONS_DATA_SAVE_ON_SUCCESS={{ execution_save_on_success }}
+      {% if queue_enabled -%}
+      - EXECUTIONS_MODE=queue
+      - QUEUE_BULL_REDIS_HOST={{ queue_redis_host }}
+      - QUEUE_BULL_REDIS_PORT={{ queue_redis_port }}
+      - QUEUE_HEALTH_CHECK_ACTIVE=true
+      {% if metrics_enabled -%}
+      - N8N_METRICS_INCLUDE_QUEUE_METRICS=true
+      {% endif -%}
+      {% endif -%}
+    volumes:
+      - /etc/localtime:/etc/localtime:ro
+      - data:/home/node/.n8n
+    {% if network_mode == 'bridge' -%}
+    networks:
+      {% if traefik_enabled -%}
+      - {{ traefik_network }}
+      {% endif -%}
+      - {{ network_name }}
+    {% else -%}
+    network_mode: {{ network_mode }}
+    {% endif -%}
+    {% if queue_enabled and not queue_redis_external -%}
+    depends_on:
+      redis:
+        condition: service_healthy
+    {% endif -%}
+    {% if traefik_enabled -%}
+    labels:
+      - traefik.enable=true
+      {% if network_mode == 'bridge' -%}
+      - traefik.docker.network={{ traefik_network }}
+      {% endif -%}
+      {% if traefik_webhook_host -%}
+      - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`) || Host(`{{ traefik_webhook_host }}`)
+      {% else -%}
+      - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      {% endif -%}
+      {% if traefik_tls_enabled -%}
+      - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}.tls=true
+      - traefik.http.routers.{{ service_name }}.tls.certresolver={{ traefik_tls_certresolver }}
+      {% else -%}
+      - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_entrypoint }}
+      {% endif -%}
+      - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=5678
+      - traefik.http.routers.{{ service_name }}.service={{ service_name }}-web
+    {% endif -%}
+    {% if not swarm_enabled -%}
+    restart: {{ restart_policy }}
+    {% endif -%}
+    {% if swarm_enabled -%}
+    deploy:
+      replicas: {{ swarm_replicas }}
+      {% if swarm_placement_host -%}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      {% endif -%}
+      {% if traefik_enabled -%}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        {% if traefik_webhook_host -%}
+        - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`) || Host(`{{ traefik_webhook_host }}`)
+        {% else -%}
+        - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        {% endif -%}
+        {% if traefik_tls_enabled -%}
+        - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}.tls=true
+        - traefik.http.routers.{{ service_name }}.tls.certresolver={{ traefik_tls_certresolver }}
+        {% else -%}
+        - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_entrypoint }}
+        {% endif -%}
+        - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=5678
+        - traefik.http.routers.{{ service_name }}.service={{ service_name }}-web
+      {% endif -%}
+    secrets:
+      - {{ encryption_key_secret_name }}
+      {% if database_enabled -%}
+      - {{ database_password_secret_name }}
+      {% endif -%}
+    {% endif -%}
+{% if queue_enabled and queue_embedded_worker -%}
+
+  {{ service_name }}-worker:
+    image: n8nio/n8n:1.118.1
+    command: worker
+    {% if not swarm_enabled -%}
+    container_name: {{ container_name }}-worker
+    {% endif -%}
+    environment:
+      - N8N_LOG_LEVEL={{ container_loglevel }}
+      - GENERIC_TIMEZONE={{ container_timezone }}
+      - TZ={{ container_timezone }}
+      {% if database_enabled -%}
+      {% if database_type == 'postgres' -%}
+      - DB_TYPE=postgresdb
+      - DB_POSTGRESDB_HOST={{ database_host }}
+      - DB_POSTGRESDB_PORT={{ database_port }}
+      - DB_POSTGRESDB_DATABASE={{ database_name }}
+      - DB_POSTGRESDB_USER={{ database_user }}
+      {% if swarm_enabled -%}
+      - DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
+      {% else -%}
+      - DB_POSTGRESDB_PASSWORD={{ database_password }}
+      {% endif -%}
+      {% elif database_type == 'mysql' -%}
+      - DB_TYPE=mysqldb
+      - DB_MYSQLDB_HOST={{ database_host }}
+      - DB_MYSQLDB_PORT={{ database_port }}
+      - DB_MYSQLDB_DATABASE={{ database_name }}
+      - DB_MYSQLDB_USER={{ database_user }}
+      {% if swarm_enabled -%}
+      - DB_MYSQLDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
+      {% else -%}
+      - DB_MYSQLDB_PASSWORD={{ database_password }}
+      {% endif -%}
+      {% endif -%}
+      {% endif -%}
+      {% if swarm_enabled -%}
+      - N8N_ENCRYPTION_KEY_FILE=/run/secrets/{{ encryption_key_secret_name }}
+      {% else -%}
+      - N8N_ENCRYPTION_KEY={{ encryption_key }}
+      {% endif -%}
+      - EXECUTIONS_MODE=queue
+      - QUEUE_BULL_REDIS_HOST={{ queue_redis_host }}
+      - QUEUE_BULL_REDIS_PORT={{ queue_redis_port }}
+      - QUEUE_HEALTH_CHECK_ACTIVE=true
+      {% if metrics_enabled -%}
+      - N8N_METRICS=true
+      {% if metrics_detailed -%}
+      - N8N_METRICS_INCLUDE_WORKFLOW_ID_LABELS=true
+      - N8N_METRICS_INCLUDE_NODE_TYPE_LABEL=true
+      {% endif -%}
+      {% endif -%}
+    volumes:
+      - /etc/localtime:/etc/localtime:ro
+      - data:/home/node/.n8n
+    {% if network_mode == 'bridge' -%}
+    networks:
+      - {{ network_name }}
+    {% else -%}
+    network_mode: {{ network_mode }}
+    {% endif -%}
+    {% if not queue_redis_external -%}
+    depends_on:
+      redis:
+        condition: service_healthy
+    {% endif -%}
+    {% if not swarm_enabled -%}
+    restart: {{ restart_policy }}
+    {% endif -%}
+    {% if swarm_enabled -%}
+    deploy:
+      replicas: 1
+      {% if swarm_placement_host -%}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      {% endif -%}
+    secrets:
+      - {{ encryption_key_secret_name }}
+      {% if database_enabled -%}
+      - {{ database_password_secret_name }}
+      {% endif -%}
+    {% endif -%}
+{% endif -%}
+
+volumes:
+  data:
+    driver: local
+{% if queue_enabled and not queue_redis_external -%}
+  redis_data:
+    driver: local
+{% endif -%}
+
+{% if network_mode == 'bridge' -%}
+networks:
+  {{ network_name }}:
+  {% if network_external -%}
+    external: true
+  {% else -%}
+    driver: bridge
+  {% endif -%}
+  {% if traefik_enabled -%}
+  {{ traefik_network }}:
+  {% if traefik_network_external -%}
+    external: true
+  {% else -%}
+    driver: bridge
+  {% endif -%}
+  {% endif -%}
+{% endif -%}
+
+{% if swarm_enabled -%}
+secrets:
+  {{ encryption_key_secret_name }}:
+    external: true
+  {% if database_enabled -%}
+  {{ database_password_secret_name }}:
+    external: true
+  {% endif -%}
+{% endif -%}

+ 302 - 0
library/compose/n8n/compose.yaml.j2.final

@@ -0,0 +1,302 @@
+{% if queue_enabled and not queue_redis_external -%}
+services:
+  redis:
+    image: redis:7-alpine
+    {% if not swarm_enabled -%}
+    container_name: {{ service_name }}-redis
+    {% endif -%}
+    volumes:
+      - redis_data:/data
+    {% if network_mode == 'bridge' -%}
+    networks:
+      - {{ network_name }}
+    {% else -%}
+    {% endif -%}
+    {% if not swarm_enabled -%}
+    restart: {{ restart_policy }}
+    {% endif -%}
+    healthcheck:
+      test: ["CMD", "redis-cli", "ping"]
+      interval: 5s
+      timeout: 3s
+      retries: 5
+    {% if swarm_enabled -%}
+    deploy:
+      replicas: 1
+      placement:
+        constraints:
+          - node.role == manager
+    {% endif -%}
+
+  {% endif -%}
+  {{ service_name }}:
+    image: n8nio/n8n:1.118.1
+    {% if not swarm_enabled -%}
+    container_name: {{ container_name }}
+    hostname: {{ container_hostname }}
+    {% endif -%}
+    environment:
+      - N8N_LOG_LEVEL={{ container_loglevel }}
+      - GENERIC_TIMEZONE={{ container_timezone }}
+      - TZ={{ container_timezone }}
+      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
+      - N8N_RUNNERS_ENABLED=true
+      {% if traefik_enabled -%}
+      - N8N_HOST={{ traefik_host }}
+      {% if traefik_tls_enabled -%}
+      - N8N_PROTOCOL=https
+      - N8N_EDITOR_BASE_URL=https://{{ traefik_host }}
+      {% else -%}
+      - N8N_PROTOCOL=http
+      - N8N_EDITOR_BASE_URL=http://{{ traefik_host }}
+      {% endif -%}
+      {% endif -%}
+      - NODE_ENV=production
+      {% if database_enabled -%}
+      {% if database_type == 'postgres' -%}
+      - DB_TYPE=postgresdb
+      - DB_POSTGRESDB_HOST={{ database_host }}
+      - DB_POSTGRESDB_PORT={{ database_port }}
+      - DB_POSTGRESDB_DATABASE={{ database_name }}
+      - DB_POSTGRESDB_USER={{ database_user }}
+      {% if swarm_enabled -%}
+      - DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
+      {% else -%}
+      - DB_POSTGRESDB_PASSWORD={{ database_password }}
+      {% endif -%}
+      {% elif database_type == 'mysql' -%}
+      - DB_TYPE=mysqldb
+      - DB_MYSQLDB_HOST={{ database_host }}
+      - DB_MYSQLDB_PORT={{ database_port }}
+      - DB_MYSQLDB_DATABASE={{ database_name }}
+      - DB_MYSQLDB_USER={{ database_user }}
+      {% if swarm_enabled -%}
+      - DB_MYSQLDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
+      {% else -%}
+      - DB_MYSQLDB_PASSWORD={{ database_password }}
+      {% endif -%}
+      {% endif -%}
+      {% endif -%}
+      {% if swarm_enabled -%}
+      - N8N_ENCRYPTION_KEY_FILE=/run/secrets/{{ encryption_key_secret_name }}
+      {% else -%}
+      - N8N_ENCRYPTION_KEY={{ encryption_key }}
+      {% endif -%}
+      {% if webhook_url -%}
+      - WEBHOOK_URL={{ webhook_url }}
+      {% endif -%}
+      - N8N_PROXY_HOPS={{ proxy_hops }}
+      {% if metrics_enabled -%}
+      - N8N_METRICS=true
+      {% if metrics_detailed -%}
+      - N8N_METRICS_INCLUDE_WORKFLOW_ID_LABELS=true
+      - N8N_METRICS_INCLUDE_NODE_TYPE_LABEL=true
+      - N8N_METRICS_INCLUDE_API_ENDPOINTS=true
+      - N8N_METRICS_INCLUDE_API_STATUS_CODE_LABELS=true
+      - N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL=true
+      {% endif -%}
+      {% endif -%}
+      - EXECUTIONS_DATA_SAVE_ON_ERROR={{ execution_save_on_error }}
+      - EXECUTIONS_DATA_SAVE_ON_SUCCESS={{ execution_save_on_success }}
+      {% if queue_enabled -%}
+      - EXECUTIONS_MODE=queue
+      - QUEUE_BULL_REDIS_HOST={{ queue_redis_host }}
+      - QUEUE_BULL_REDIS_PORT={{ queue_redis_port }}
+      - QUEUE_HEALTH_CHECK_ACTIVE=true
+      {% if metrics_enabled -%}
+      - N8N_METRICS_INCLUDE_QUEUE_METRICS=true
+      {% endif -%}
+      {% endif -%}
+    volumes:
+      - /etc/localtime:/etc/localtime:ro
+      - data:/home/node/.n8n
+    {% if network_mode == 'bridge' -%}
+    networks:
+      {% if traefik_enabled -%}
+      - {{ traefik_network }}
+      {% endif -%}
+      - {{ network_name }}
+    {% else -%}
+    {% endif -%}
+    {% if queue_enabled and not queue_redis_external -%}
+    depends_on:
+      redis:
+        condition: service_healthy
+    {% endif -%}
+    {% if traefik_enabled -%}
+    labels:
+      - traefik.enable=true
+      {% if network_mode == 'bridge' -%}
+      - traefik.docker.network={{ traefik_network }}
+      {% endif -%}
+      {% if traefik_webhook_host -%}
+      - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`) || Host(`{{ traefik_webhook_host }}`)
+      {% else -%}
+      - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      {% endif -%}
+      {% if traefik_tls_enabled -%}
+      - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}.tls=true
+      - traefik.http.routers.{{ service_name }}.tls.certresolver={{ traefik_tls_certresolver }}
+      {% else -%}
+      - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_entrypoint }}
+      {% endif -%}
+      - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=5678
+      - traefik.http.routers.{{ service_name }}.service={{ service_name }}-web
+    {% endif -%}
+    {% if not swarm_enabled -%}
+    restart: {{ restart_policy }}
+    {% endif -%}
+    {% if swarm_enabled -%}
+    deploy:
+      replicas: {{ swarm_replicas }}
+      {% if swarm_placement_host -%}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      {% endif -%}
+      {% if traefik_enabled -%}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        {% if traefik_webhook_host -%}
+        - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`) || Host(`{{ traefik_webhook_host }}`)
+        {% else -%}
+        - traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        {% endif -%}
+        {% if traefik_tls_enabled -%}
+        - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}.tls=true
+        - traefik.http.routers.{{ service_name }}.tls.certresolver={{ traefik_tls_certresolver }}
+        {% else -%}
+        - traefik.http.routers.{{ service_name }}.entrypoints={{ traefik_entrypoint }}
+        {% endif -%}
+        - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=5678
+        - traefik.http.routers.{{ service_name }}.service={{ service_name }}-web
+      {% endif -%}
+    secrets:
+      - {{ encryption_key_secret_name }}
+      {% if database_enabled -%}
+      - {{ database_password_secret_name }}
+      {% endif -%}
+    {% endif -%}
+{% if queue_enabled and queue_embedded_worker -%}
+
+  {{ service_name }}-worker:
+    image: n8nio/n8n:1.118.1
+    command: worker
+    {% if not swarm_enabled -%}
+    container_name: {{ container_name }}-worker
+    {% endif -%}
+    environment:
+      - N8N_LOG_LEVEL={{ container_loglevel }}
+      - GENERIC_TIMEZONE={{ container_timezone }}
+      - TZ={{ container_timezone }}
+      {% if database_enabled -%}
+      {% if database_type == 'postgres' -%}
+      - DB_TYPE=postgresdb
+      - DB_POSTGRESDB_HOST={{ database_host }}
+      - DB_POSTGRESDB_PORT={{ database_port }}
+      - DB_POSTGRESDB_DATABASE={{ database_name }}
+      - DB_POSTGRESDB_USER={{ database_user }}
+      {% if swarm_enabled -%}
+      - DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
+      {% else -%}
+      - DB_POSTGRESDB_PASSWORD={{ database_password }}
+      {% endif -%}
+      {% elif database_type == 'mysql' -%}
+      - DB_TYPE=mysqldb
+      - DB_MYSQLDB_HOST={{ database_host }}
+      - DB_MYSQLDB_PORT={{ database_port }}
+      - DB_MYSQLDB_DATABASE={{ database_name }}
+      - DB_MYSQLDB_USER={{ database_user }}
+      {% if swarm_enabled -%}
+      - DB_MYSQLDB_PASSWORD_FILE=/run/secrets/{{ database_password_secret_name }}
+      {% else -%}
+      - DB_MYSQLDB_PASSWORD={{ database_password }}
+      {% endif -%}
+      {% endif -%}
+      {% endif -%}
+      {% if swarm_enabled -%}
+      - N8N_ENCRYPTION_KEY_FILE=/run/secrets/{{ encryption_key_secret_name }}
+      {% else -%}
+      - N8N_ENCRYPTION_KEY={{ encryption_key }}
+      {% endif -%}
+      - EXECUTIONS_MODE=queue
+      - QUEUE_BULL_REDIS_HOST={{ queue_redis_host }}
+      - QUEUE_BULL_REDIS_PORT={{ queue_redis_port }}
+      - QUEUE_HEALTH_CHECK_ACTIVE=true
+      {% if metrics_enabled -%}
+      - N8N_METRICS=true
+      {% if metrics_detailed -%}
+      - N8N_METRICS_INCLUDE_WORKFLOW_ID_LABELS=true
+      - N8N_METRICS_INCLUDE_NODE_TYPE_LABEL=true
+      {% endif -%}
+      {% endif -%}
+    volumes:
+      - /etc/localtime:/etc/localtime:ro
+      - data:/home/node/.n8n
+    {% if network_mode == 'bridge' -%}
+    networks:
+      - {{ network_name }}
+    {% else -%}
+    {% endif -%}
+    {% if not queue_redis_external -%}
+    depends_on:
+      redis:
+        condition: service_healthy
+    {% endif -%}
+    {% if not swarm_enabled -%}
+    restart: {{ restart_policy }}
+    {% endif -%}
+    {% if swarm_enabled -%}
+    deploy:
+      replicas: 1
+      {% if swarm_placement_host -%}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      {% endif -%}
+    secrets:
+      - {{ encryption_key_secret_name }}
+      {% if database_enabled -%}
+      - {{ database_password_secret_name }}
+      {% endif -%}
+    {% endif -%}
+{% endif -%}
+
+volumes:
+  data:
+    driver: local
+{% if queue_enabled and not queue_redis_external -%}
+  redis_data:
+    driver: local
+{% endif -%}
+
+{% if network_mode == 'bridge' -%}
+networks:
+  {{ network_name }}:
+  {% if network_external -%}
+    external: true
+  {% else -%}
+    driver: bridge
+  {% endif -%}
+  {% if traefik_enabled -%}
+  {{ traefik_network }}:
+  {% if traefik_network_external -%}
+    external: true
+  {% else -%}
+    driver: bridge
+  {% endif -%}
+  {% endif -%}
+{% endif -%}
+
+{% if swarm_enabled -%}
+secrets:
+  {{ encryption_key_secret_name }}:
+    external: true
+  {% if database_enabled -%}
+  {{ database_password_secret_name }}:
+    external: true
+  {% endif -%}
+{% endif -%}

+ 2 - 12
library/compose/n8n/template.yaml

@@ -35,9 +35,9 @@ metadata:
   next_steps: |
     {% if swarm_enabled -%}
     1. Create required secrets:
-       echo "your-encryption-key" | docker secret create {{ encryption_key_secret_name }} -
+       echo "your-encryption-key" | docker secret create {{ service_name }}_encryption_key -
        {% if database_enabled and database_type == 'postgres' -%}
-       echo "your-db-password" | docker secret create {{ database_password_secret_name }} -
+       echo "your-db-password" | docker secret create {{ service_name }}_database_password -
        {%- endif %}
     2. Deploy to Docker Swarm:
        docker stack deploy -c compose.yaml {{ service_name }}
@@ -229,13 +229,3 @@ spec:
         default: 1
         needs: "swarm_enabled"
         extra: "For HA, set > 1 (requires queue mode)"
-      encryption_key_secret_name:
-        type: str
-        description: "Docker Swarm secret name for encryption key"
-        default: "n8n_encryption_key"
-        needs: "swarm_enabled"
-      database_password_secret_name:
-        type: str
-        description: "Docker Swarm secret name for database password"
-        default: "n8n_database_password"
-        needs: ["swarm_enabled", "database_enabled"]

+ 1 - 0
library/compose/netbox/.env.secret.database_password.j2

@@ -0,0 +1 @@
+{{ database_password }}

+ 1 - 0
library/compose/netbox/.env.secret.email_password.j2

@@ -0,0 +1 @@
+{% if email_enabled %}{{ email_password }}{% endif %}

+ 1 - 0
library/compose/netbox/.env.secret.netbox_secret_key.j2

@@ -0,0 +1 @@
+{{ netbox_secret_key }}

+ 1 - 0
library/compose/netbox/.env.secret.redis_password.j2

@@ -0,0 +1 @@
+{{ redis_password }}

+ 18 - 0
library/compose/netbox/common/networks.yaml.j2

@@ -0,0 +1,18 @@
+{#
+  Network definitions:
+  - Backend network: Internal communication between NetBox services
+  - Traefik network: External access via reverse proxy (always external)
+  Note: In Swarm mode, backend network uses overlay driver for multi-host communication
+#}
+networks:
+  {{ service_name }}_backend:
+    {% if swarm_enabled %}
+    driver: overlay
+    attachable: true
+    {% else %}
+    driver: bridge
+    {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}

+ 15 - 0
library/compose/netbox/common/secrets.yaml.j2

@@ -0,0 +1,15 @@
+{#
+  Docker Swarm secrets definitions
+  Secrets are stored in separate files and referenced in service environment variables
+#}
+secrets:
+  {{ service_name }}_database_password:
+    file: ./.env.secret.database_password
+  {{ service_name }}_redis_password:
+    file: ./.env.secret.redis_password
+  {{ service_name }}_secret_key:
+    file: ./.env.secret.netbox_secret_key
+  {% if email_enabled %}
+  {{ service_name }}_email_password:
+    file: ./.env.secret.email_password
+  {% endif %}

+ 64 - 0
library/compose/netbox/common/volumes.yaml.j2

@@ -0,0 +1,64 @@
+{#
+  Volume definitions:
+  - When volume_mode is 'local' (default): use docker-managed local volumes
+  - When volume_mode is 'nfs': configure NFS-backed volumes
+  - When volume_mode is 'mount': no volume definition needed (bind mounts used directly)
+  Note: Postgres volumes only defined in Compose mode (Swarm uses external database)
+#}
+{% if volume_mode == 'local' %}
+volumes:
+  {% if not database_external and not swarm_enabled %}
+  {{ service_name }}-postgres:
+    driver: local
+  {% endif %}
+  {{ service_name }}-redis:
+    driver: local
+  {{ service_name }}-redis-cache:
+    driver: local
+  {{ service_name }}-media:
+    driver: local
+  {{ service_name }}-reports:
+    driver: local
+  {{ service_name }}-scripts:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {% if not database_external and not swarm_enabled %}
+  {{ service_name }}-postgres:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},nfsvers=4,{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/postgres"
+  {% endif %}
+  {{ service_name }}-redis:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},nfsvers=4,{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/redis"
+  {{ service_name }}-redis-cache:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},nfsvers=4,{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/redis-cache"
+  {{ service_name }}-media:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},nfsvers=4,{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/media"
+  {{ service_name }}-reports:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},nfsvers=4,{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/reports"
+  {{ service_name }}-scripts:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},nfsvers=4,{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/scripts"
+{% endif %}

+ 23 - 282
library/compose/netbox/compose.yaml.j2

@@ -1,284 +1,25 @@
-services:
-  {{ service_name }}:
-    image: docker.io/netboxcommunity/netbox:v4.2.3
-    restart: {{ restart_policy }}
-    {% if container_name %}
-    container_name: {{ container_name }}
-    {% endif %}
-    {% if container_hostname %}
-    hostname: {{ container_hostname }}
-    {% endif %}
-    depends_on:
-      {% if not database_external %}
-      - {{ service_name }}-postgres
-      {% endif %}
-      - {{ service_name }}-redis
-      - {{ service_name }}-redis-cache
-    environment:
-      {% if container_timezone %}
-      - TZ={{ container_timezone }}
-      {% endif %}
-      {% if traefik_enabled %}
-      - ALLOWED_HOSTS={{ traefik_host }}.{{ traefik_domain }}
-      {% else %}
-      - ALLOWED_HOSTS=*
-      {% endif %}
-      {% if database_external %}
-      - DB_HOST={{ database_host }}
-      {% else %}
-      - DB_HOST={{ service_name }}-postgres
-      {% endif %}
-      - DB_NAME={{ database_name }}
-      - DB_USER={{ database_user }}
-      - DB_PASSWORD=${DATABASE_PASSWORD}
-      - REDIS_HOST={{ service_name }}-redis
-      - REDIS_PASSWORD=${REDIS_PASSWORD}
-      - REDIS_CACHE_HOST={{ service_name }}-redis-cache
-      - REDIS_CACHE_PASSWORD=${REDIS_PASSWORD}
-      - SECRET_KEY=${NETBOX_SECRET_KEY}
-      {% if netbox_metrics_enabled %}
-      - METRICS_ENABLED=true
-      {% endif %}
-      {% if email_enabled %}
-      - EMAIL_SERVER={{ email_server }}
-      - EMAIL_PORT={{ email_port }}
-      - EMAIL_FROM={{ email_from }}
-      - EMAIL_USERNAME={{ email_username }}
-      - EMAIL_PASSWORD=${EMAIL_PASSWORD}
-      {% if email_encryption == "ssl" %}
-      - EMAIL_USE_SSL=True
-      {% elif email_encryption == "starttls" %}
-      - EMAIL_USE_TLS=True
-      {% endif %}
-      {% endif %}
-    networks:
-      {% if traefik_enabled %}
-      - {{ traefik_network }}
-      {% endif %}
-      - {{ service_name }}_backend
-    {% if not traefik_enabled %}
-    ports:
-      - "{{ ports_http }}:8080"
-    {% endif %}
-    volumes:
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/media:/opt/netbox/netbox/media
-      - {{ volume_mount_path }}/reports:/opt/netbox/netbox/reports
-      - {{ volume_mount_path }}/scripts:/opt/netbox/netbox/scripts
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-media:/opt/netbox/netbox/media
-      - {{ service_name }}-reports:/opt/netbox/netbox/reports
-      - {{ service_name }}-scripts:/opt/netbox/netbox/scripts
-      {% endif %}
-    {% if traefik_enabled %}
-    labels:
-      - traefik.enable=true
-      - traefik.docker.network={{ traefik_network }}
-      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=8080
-      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
-      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
-      {% if traefik_tls_enabled %}
-      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
-      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
-      - traefik.http.routers.{{ service_name }}-https.tls=true
-      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
-      {% endif %}
-    {% endif %}
-
-  {{ service_name }}-worker:
-    image: docker.io/netboxcommunity/netbox:v4.2.3
-    restart: {{ restart_policy }}
-    {% if container_name %}
-    container_name: {{ container_name }}-worker
-    {% endif %}
-    {% if container_hostname %}
-    hostname: {{ container_hostname }}-worker
-    {% endif %}
-    command:
-      - /opt/netbox/venv/bin/python
-      - /opt/netbox/netbox/manage.py
-      - rqworker
-    depends_on:
-      {% if not database_external %}
-      - {{ service_name }}-postgres
-      {% endif %}
-      - {{ service_name }}
-      - {{ service_name }}-redis
-      - {{ service_name }}-redis-cache
-    environment:
-      - TZ={{ container_timezone }}
-      {% if database_external %}
-      - DB_HOST={{ database_host }}
-      {% else %}
-      - DB_HOST={{ service_name }}-postgres
-      {% endif %}
-      - DB_NAME={{ database_name }}
-      - DB_USER={{ database_user }}
-      - DB_PASSWORD=${DATABASE_PASSWORD}
-      - REDIS_HOST={{ service_name }}-redis
-      - REDIS_PASSWORD=${REDIS_PASSWORD}
-      - REDIS_CACHE_HOST={{ service_name }}-redis-cache
-      - REDIS_CACHE_PASSWORD=${REDIS_PASSWORD}
-      - SECRET_KEY=${NETBOX_SECRET_KEY}
-      {% if email_enabled %}
-      - EMAIL_SERVER={{ email_server }}
-      - EMAIL_PORT={{ email_port }}
-      - EMAIL_FROM={{ email_from }}
-      - EMAIL_USERNAME={{ email_username }}
-      - EMAIL_PASSWORD=${EMAIL_PASSWORD}
-      - EMAIL_USE_SSL={{ email_use_ssl }}
-      - EMAIL_USE_TLS={{ email_use_tls }}
-      {% endif %}
-    networks:
-      - {{ service_name }}_backend
-    volumes:
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/media:/opt/netbox/netbox/media
-      - {{ volume_mount_path }}/reports:/opt/netbox/netbox/reports
-      - {{ volume_mount_path }}/scripts:/opt/netbox/netbox/scripts
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-media:/opt/netbox/netbox/media
-      - {{ service_name }}-reports:/opt/netbox/netbox/reports
-      - {{ service_name }}-scripts:/opt/netbox/netbox/scripts
-      {% endif %}
-
-  {{ service_name }}-redis:
-    image: docker.io/library/redis:8.4.0-alpine
-    restart: {{ restart_policy }}
-    {% if container_name %}
-    container_name: {{ container_name }}-redis
-    {% endif %}
-    {% if container_hostname %}
-    hostname: {{ container_hostname }}-redis
-    {% endif %}
-    command:
-      - sh
-      - -c
-      - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD
-    environment:
-      - REDIS_PASSWORD=${REDIS_PASSWORD}
-    networks:
-      - {{ service_name }}_backend
-    volumes:
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/redis:/data
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-redis:/data
-      {% endif %}
-
-  {{ service_name }}-redis-cache:
-    image: docker.io/library/redis:8.4.0-alpine
-    restart: {{ restart_policy }}
-    {% if container_name %}
-    container_name: {{ container_name }}-redis-cache
-    {% endif %}
-    {% if container_hostname %}
-    hostname: {{ container_hostname }}-redis-cache
-    {% endif %}
-    command:
-      - sh
-      - -c
-      - redis-server --requirepass $$REDIS_PASSWORD
-    environment:
-      - REDIS_PASSWORD=${REDIS_PASSWORD}
-    networks:
-      - {{ service_name }}_backend
-    volumes:
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/redis-cache:/data
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-redis-cache:/data
-      {% endif %}
-
-  {% if not database_external %}
-  {{ service_name }}-postgres:
-    image: docker.io/library/postgres:17.2-alpine
-    restart: {{ restart_policy }}
-    {% if container_name %}
-    container_name: {{ container_name }}-postgres
-    {% endif %}
-    {% if container_hostname %}
-    hostname: {{ container_hostname }}-postgres
-    {% endif %}
-    environment:
-      - TZ={{ container_timezone }}
-      - POSTGRES_USER={{ database_user }}
-      - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
-      - POSTGRES_DB={{ database_name }}
-    networks:
-      - {{ service_name }}_backend
-    volumes:
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/postgres:/var/lib/postgresql/data
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-postgres:/var/lib/postgresql/data
-      {% endif %}
+{#
+  NetBox Docker Compose Configuration
+  
+  This is the main orchestration file that includes all service definitions.
+  Services are split into separate files for better maintainability:
+  - services/netbox.yaml: Main NetBox web application
+  - services/worker.yaml: Background task worker
+  - services/redis.yaml: Redis cache and queue services
+  - services/postgres.yaml: PostgreSQL database (Compose mode only)
+  - common/networks.yaml: Network definitions
+  - common/volumes.yaml: Volume definitions
+  - common/secrets.yaml: Secrets (Swarm mode only)
+#}
+include:
+  - services/netbox.yaml
+  - services/worker.yaml
+  - services/redis.yaml
+  {% if not database_external and not swarm_enabled %}
+  - services/postgres.yaml
   {% endif %}
-
-{% if volume_mode == 'local' %}
-volumes:
-  {% if not database_external %}
-  {{ service_name }}-postgres:
-    driver: local
-  {% endif %}
-  {{ service_name }}-redis:
-    driver: local
-  {{ service_name }}-redis-cache:
-    driver: local
-  {{ service_name }}-media:
-    driver: local
-  {{ service_name }}-reports:
-    driver: local
-  {{ service_name }}-scripts:
-    driver: local
-{% elif volume_mode == 'nfs' %}
-volumes:
-  {% if not database_external %}
-  {{ service_name }}-postgres:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/postgres"
-  {% endif %}
-  {{ service_name }}-redis:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/redis"
-  {{ service_name }}-redis-cache:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/redis-cache"
-  {{ service_name }}-media:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/media"
-  {{ service_name }}-reports:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/reports"
-  {{ service_name }}-scripts:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/scripts"
-{% endif %}
-
-networks:
-  {{ service_name }}_backend:
-    driver: bridge
-  {% if traefik_enabled %}
-  {{ traefik_network }}:
-    external: true
+  - common/networks.yaml
+  - common/volumes.yaml
+  {% if swarm_enabled %}
+  - common/secrets.yaml
   {% endif %}

+ 205 - 0
library/compose/netbox/services/netbox.yaml.j2

@@ -0,0 +1,205 @@
+{#
+  NetBox main web application service
+  Handles web UI, API, and application logic
+#}
+services:
+  {{ service_name }}:
+    image: docker.io/netboxcommunity/netbox:v4.2.3
+    {#
+      If not in swarm mode, apply restart policy and optional container name/hostname
+      Swarm mode handles restarts via deploy.restart_policy
+    #}
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    {% if container_name %}
+    container_name: {{ container_name }}
+    {% endif %}
+    {% endif %}
+    {#
+      Set container hostname if specified
+    #}
+    {% if container_hostname %}
+    hostname: {{ container_hostname }}
+    {% endif %}
+    {#
+      Service dependencies:
+      - Postgres: Only when using bundled database (Compose mode)
+      - Redis: Required for caching and task queue
+      - Redis-cache: Required for session caching
+    #}
+    {% if not swarm_enabled %}
+    depends_on:
+      {% if not database_external %}
+      - {{ service_name }}-postgres
+      {% endif %}
+      - {{ service_name }}-redis
+      - {{ service_name }}-redis-cache
+    {% endif %}
+    {#
+      Environment variables for NetBox configuration:
+      - TZ: Container timezone
+      - ALLOWED_HOSTS: Restrict access to specific hostnames
+      - DB_*: Database connection settings (external or bundled)
+      - REDIS_*: Redis connection settings for caching and task queue
+      - SECRET_KEY: Cryptographic signing key
+      - METRICS_ENABLED: Prometheus metrics (optional)
+      - EMAIL_*: Email configuration (optional)
+      Note: In Swarm mode, passwords are loaded from Docker secrets
+    #}
+    environment:
+      {% if container_timezone %}
+      - TZ={{ container_timezone }}
+      {% endif %}
+      {% if traefik_enabled %}
+      - ALLOWED_HOSTS={{ traefik_host }}.{{ traefik_domain }}
+      {% else %}
+      - ALLOWED_HOSTS=*
+      {% endif %}
+      - DB_HOST={{ database_host }}
+      - DB_NAME={{ database_name }}
+      - DB_USER={{ database_user }}
+      {% if swarm_enabled %}
+      - DB_PASSWORD_FILE=/run/secrets/{{ service_name }}_database_password
+      {% else %}
+      - DB_PASSWORD=${DATABASE_PASSWORD}
+      {% endif %}
+      - REDIS_HOST={{ service_name }}-redis
+      {% if swarm_enabled %}
+      - REDIS_PASSWORD_FILE=/run/secrets/{{ service_name }}_redis_password
+      {% else %}
+      - REDIS_PASSWORD=${REDIS_PASSWORD}
+      {% endif %}
+      - REDIS_CACHE_HOST={{ service_name }}-redis-cache
+      {% if swarm_enabled %}
+      - REDIS_CACHE_PASSWORD_FILE=/run/secrets/{{ service_name }}_redis_password
+      {% else %}
+      - REDIS_CACHE_PASSWORD=${REDIS_PASSWORD}
+      {% endif %}
+      {% if swarm_enabled %}
+      - SECRET_KEY_FILE=/run/secrets/{{ service_name }}_secret_key
+      {% else %}
+      - SECRET_KEY=${NETBOX_SECRET_KEY}
+      {% endif %}
+      {% if netbox_metrics_enabled %}
+      - METRICS_ENABLED=true
+      {% endif %}
+      {% if email_enabled %}
+      - EMAIL_SERVER={{ email_server }}
+      - EMAIL_PORT={{ email_port }}
+      - EMAIL_FROM={{ email_from }}
+      - EMAIL_USERNAME={{ email_username }}
+      {% if swarm_enabled %}
+      - EMAIL_PASSWORD_FILE=/run/secrets/{{ service_name }}_email_password
+      {% else %}
+      - EMAIL_PASSWORD=${EMAIL_PASSWORD}
+      {% endif %}
+      {% if email_encryption == "ssl" %}
+      - EMAIL_USE_SSL=True
+      {% elif email_encryption == "starttls" %}
+      - EMAIL_USE_TLS=True
+      {% endif %}
+      {% endif %}
+    {#
+      Network configuration:
+      - Traefik network: For external access via reverse proxy
+      - Backend network: Internal communication between NetBox services
+    #}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {{ service_name }}_backend:
+    {#
+      Port mappings when Traefik is disabled:
+      - HTTP: Web interface access
+      Note: Swarm mode uses 'host' mode for port publishing
+    #}
+    {% if not traefik_enabled %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 8080
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:8080"
+      {% endif %}
+    {% endif %}
+    {#
+      Volume configuration for persistent data:
+      - media: Uploaded files and images
+      - reports: Custom reports
+      - scripts: Custom scripts
+    #}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/media:/opt/netbox/netbox/media
+      - {{ volume_mount_path }}/reports:/opt/netbox/netbox/reports
+      - {{ volume_mount_path }}/scripts:/opt/netbox/netbox/scripts
+      {% elif volume_mode == 'local' or volume_mode == 'nfs' %}
+      - {{ service_name }}-media:/opt/netbox/netbox/media
+      - {{ service_name }}-reports:/opt/netbox/netbox/reports
+      - {{ service_name }}-scripts:/opt/netbox/netbox/scripts
+      {% endif %}
+    {#
+      Docker Swarm secrets for sensitive data
+    #}
+    {% if swarm_enabled %}
+    secrets:
+      - {{ service_name }}_database_password
+      - {{ service_name }}_redis_password
+      - {{ service_name }}_secret_key
+      {% if email_enabled %}
+      - {{ service_name }}_email_password
+      {% endif %}
+    {% endif %}
+    {#
+      Deploy configuration for Swarm mode:
+      - Can run on any node (stateless with external database)
+      - Traefik: Labels for reverse proxy integration
+    #}
+    {% if swarm_enabled %}
+    deploy:
+      mode: replicated
+      replicas: 1
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      restart_policy:
+        condition: on-failure
+      {% if traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=8080
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {#
+      Traefik labels for Compose mode
+    #}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=8080
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}

+ 28 - 0
library/compose/netbox/services/postgres.yaml.j2

@@ -0,0 +1,28 @@
+{#
+  PostgreSQL database service
+  Only deployed in Compose mode with internal database
+  Swarm mode requires external PostgreSQL database
+#}
+services:
+  {{ service_name }}-postgres:
+    image: docker.io/library/postgres:17.2-alpine
+    restart: {{ restart_policy }}
+    {% if container_name %}
+    container_name: {{ container_name }}-postgres
+    {% endif %}
+    {% if container_hostname %}
+    hostname: {{ container_hostname }}-postgres
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      - POSTGRES_USER={{ database_user }}
+      - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
+      - POSTGRES_DB={{ database_name }}
+    networks:
+      - {{ service_name }}_backend
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/postgres:/var/lib/postgresql/data
+      {% elif volume_mode == 'local' or volume_mode == 'nfs' %}
+      - {{ service_name }}-postgres:/var/lib/postgresql/data
+      {% endif %}

+ 88 - 0
library/compose/netbox/services/redis.yaml.j2

@@ -0,0 +1,88 @@
+{#
+  Redis services for NetBox
+  - redis: Task queue and caching backend
+  - redis-cache: Session caching
+  Both must be pinned to specific node in Swarm mode (persistent data)
+#}
+services:
+  {{ service_name }}-redis:
+    image: docker.io/library/redis:8.4.0-alpine
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    {% if container_name %}
+    container_name: {{ container_name }}-redis
+    {% endif %}
+    {% endif %}
+    {% if container_hostname %}
+    hostname: {{ container_hostname }}-redis
+    {% endif %}
+    command:
+      - sh
+      - -c
+      - {% if swarm_enabled %}redis-server --appendonly yes --requirepass $$(cat /run/secrets/{{ service_name }}_redis_password){% else %}redis-server --appendonly yes --requirepass $$REDIS_PASSWORD{% endif %}
+
+    {% if not swarm_enabled %}
+    environment:
+      - REDIS_PASSWORD=${REDIS_PASSWORD}
+    {% endif %}
+    networks:
+      {{ service_name }}_backend:
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/redis:/data
+      {% elif volume_mode == 'local' or volume_mode == 'nfs' %}
+      - {{ service_name }}-redis:/data
+      {% endif %}
+    {% if swarm_enabled %}
+    secrets:
+      - {{ service_name }}_redis_password
+    deploy:
+      mode: replicated
+      replicas: 1
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      restart_policy:
+        condition: on-failure
+    {% endif %}
+
+  {{ service_name }}-redis-cache:
+    image: docker.io/library/redis:8.4.0-alpine
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    {% if container_name %}
+    container_name: {{ container_name }}-redis-cache
+    {% endif %}
+    {% endif %}
+    {% if container_hostname %}
+    hostname: {{ container_hostname }}-redis-cache
+    {% endif %}
+    command:
+      - sh
+      - -c
+      - {% if swarm_enabled %}redis-server --requirepass $$(cat /run/secrets/{{ service_name }}_redis_password){% else %}redis-server --requirepass $$REDIS_PASSWORD{% endif %}
+
+    {% if not swarm_enabled %}
+    environment:
+      - REDIS_PASSWORD=${REDIS_PASSWORD}
+    {% endif %}
+    networks:
+      {{ service_name }}_backend:
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/redis-cache:/data
+      {% elif volume_mode == 'local' or volume_mode == 'nfs' %}
+      - {{ service_name }}-redis-cache:/data
+      {% endif %}
+    {% if swarm_enabled %}
+    secrets:
+      - {{ service_name }}_redis_password
+    deploy:
+      mode: replicated
+      replicas: 1
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      restart_policy:
+        condition: on-failure
+    {% endif %}

+ 101 - 0
library/compose/netbox/services/worker.yaml.j2

@@ -0,0 +1,101 @@
+{#
+  NetBox worker service
+  Processes background tasks from Redis queue
+  Can scale horizontally in Swarm mode for better queue processing
+#}
+services:
+  {{ service_name }}-worker:
+    image: docker.io/netboxcommunity/netbox:v4.2.3
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    {% if container_name %}
+    container_name: {{ container_name }}-worker
+    {% endif %}
+    {% endif %}
+    {% if container_hostname %}
+    hostname: {{ container_hostname }}-worker
+    {% endif %}
+    command:
+      - /opt/netbox/venv/bin/python
+      - /opt/netbox/netbox/manage.py
+      - rqworker
+    {% if not swarm_enabled %}
+    depends_on:
+      - {{ service_name }}
+      - {{ service_name }}-redis
+      - {{ service_name }}-redis-cache
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      - DB_HOST={{ database_host }}
+      - DB_NAME={{ database_name }}
+      - DB_USER={{ database_user }}
+      {% if swarm_enabled %}
+      - DB_PASSWORD_FILE=/run/secrets/{{ service_name }}_database_password
+      {% else %}
+      - DB_PASSWORD=${DATABASE_PASSWORD}
+      {% endif %}
+      - REDIS_HOST={{ service_name }}-redis
+      {% if swarm_enabled %}
+      - REDIS_PASSWORD_FILE=/run/secrets/{{ service_name }}_redis_password
+      {% else %}
+      - REDIS_PASSWORD=${REDIS_PASSWORD}
+      {% endif %}
+      - REDIS_CACHE_HOST={{ service_name }}-redis-cache
+      {% if swarm_enabled %}
+      - REDIS_CACHE_PASSWORD_FILE=/run/secrets/{{ service_name }}_redis_password
+      {% else %}
+      - REDIS_CACHE_PASSWORD=${REDIS_PASSWORD}
+      {% endif %}
+      {% if swarm_enabled %}
+      - SECRET_KEY_FILE=/run/secrets/{{ service_name }}_secret_key
+      {% else %}
+      - SECRET_KEY=${NETBOX_SECRET_KEY}
+      {% endif %}
+      {% if email_enabled %}
+      - EMAIL_SERVER={{ email_server }}
+      - EMAIL_PORT={{ email_port }}
+      - EMAIL_FROM={{ email_from }}
+      - EMAIL_USERNAME={{ email_username }}
+      {% if swarm_enabled %}
+      - EMAIL_PASSWORD_FILE=/run/secrets/{{ service_name }}_email_password
+      {% else %}
+      - EMAIL_PASSWORD=${EMAIL_PASSWORD}
+      {% endif %}
+      {% if email_encryption == "ssl" %}
+      - EMAIL_USE_SSL=True
+      {% elif email_encryption == "starttls" %}
+      - EMAIL_USE_TLS=True
+      {% endif %}
+      {% endif %}
+    networks:
+      {{ service_name }}_backend:
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/media:/opt/netbox/netbox/media
+      - {{ volume_mount_path }}/reports:/opt/netbox/netbox/reports
+      - {{ volume_mount_path }}/scripts:/opt/netbox/netbox/scripts
+      {% elif volume_mode == 'local' or volume_mode == 'nfs' %}
+      - {{ service_name }}-media:/opt/netbox/netbox/media
+      - {{ service_name }}-reports:/opt/netbox/netbox/reports
+      - {{ service_name }}-scripts:/opt/netbox/netbox/scripts
+      {% endif %}
+    {% if swarm_enabled %}
+    secrets:
+      - {{ service_name }}_database_password
+      - {{ service_name }}_redis_password
+      - {{ service_name }}_secret_key
+      {% if email_enabled %}
+      - {{ service_name }}_email_password
+      {% endif %}
+    deploy:
+      mode: {{ swarm_worker_placement_mode }}
+      {% if swarm_worker_placement_mode == 'replicated' %}
+      replicas: {{ swarm_worker_replicas }}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_worker_placement_host }}
+      {% endif %}
+      restart_policy:
+        condition: on-failure
+    {% endif %}

+ 102 - 99
library/compose/netbox/template.yaml

@@ -1,102 +1,105 @@
----
 kind: compose
-schema: "1.2"
 metadata:
-  name: NetBox
-  description: |
-    Network infrastructure management (IPAM/DCIM) and network automation source of truth.
-    Provides comprehensive API for managing IP addresses, circuits, devices, racks, cables,
-    and other network infrastructure components with powerful automation capabilities.
-    ## References
-    * **Project:** https://netbox.dev/
-    * **Documentation:** https://docs.netbox.dev/
-    * **GitHub:** https://github.com/netbox-community/netbox
-  version: 4.2.3
-  author: Christian Lempa
-  date: '2025-11-13'
-  tags:
-    - traefik
-    - netbox
-    - ipam
-    - dcim
-  next_steps: |
-    ### 1. Start NetBox
-    ```bash
-    docker compose up -d
-    ```
-    ### 2. Wait for Initialization (2-5 minutes)
-    ```bash
-    docker compose logs -f {{ service_name }}
-    ```
-    ### 3. Create Superuser Account
-    ```bash
-    docker compose exec {{ service_name }} /opt/netbox/netbox/manage.py createsuperuser
-    ```
-    ### 4. Access the Web Interface
-    {% if traefik_enabled -%}
-    * Navigate to: **https://{{ traefik_host }}.{{ traefik_domain }}**
-    {% else -%}
-    * Navigate to: **http://localhost:{{ ports_http }}**
-    {% endif -%}
-    ### 5. Initial Configuration
-    * Complete the setup wizard
-    * Configure your network infrastructure (sites, racks, devices)
-    * Set up IPAM (IP ranges, VLANs, prefixes)
-    * Create user accounts and assign permissions
-    ### 6. Security Recommendations
-    * Enable two-factor authentication for admin accounts
-    * Configure LDAP/SSO for user authentication (if needed)
-    * Set up regular database backups
-    * Review and customize user permissions
-    * Configure change logging and webhooks
+    name: NetBox
+    description: |-
+        Network infrastructure management (IPAM/DCIM) and network automation source of truth. Provides comprehensive API for managing IP addresses, circuits, devices, racks, cables, and other network infrastructure components with powerful automation capabilities.
+        ## :warning: Swarm Deployment
+        NetBox in Swarm mode requires an external PostgreSQL database (`database_external=true`).
+        ## References
+        * **Project:** https://netbox.dev/
+        * **Documentation:** https://docs.netbox.dev/
+        * **GitHub:** https://github.com/netbox-community/netbox
+    version: 4.2.3
+    author: Christian Lempa
+    date: "2025-11-13"
+    tags:
+        - traefik
+        - swarm
+        - volume_modes
+    draft: false
+schema: "1.2"
 spec:
-  general:
-    vars:
-      service_name:
-        default: netbox
-  database:
-    vars:
-      database_name:
-        default: netbox
-      database_user:
-        default: netbox
-      redis_password:
-        description: Redis password for authentication
-        type: str
-        sensitive: true
-        autogenerated: true
-        required: true
-  ports:
-    vars:
-      ports_http:
-        description: Host port for HTTP
-        type: int
-        default: 8000
-  traefik:
-    vars:
-      traefik_host:
-        default: netbox
-  netbox:
-    title: NetBox Configuration
-    description: Configure NetBox application settings
-    vars:
-      netbox_secret_key:
-        description: Secret Key
-        extra: Used for cryptographic signing and session management
-        type: str
-        sensitive: true
-        autogenerated: true
-        autogenerated_length: 50
-        required: true
-      netbox_metrics_enabled:
-        description: Enable Prometheus metrics endpoint
-        type: bool
-        default: false
-  email:
-    vars:
-      email_server:
-        default: localhost
-      email_port:
-        default: 25
-      email_from:
-        default: netbox@example.com
+    database:
+        key: database
+        title: ""
+        vars:
+            database_name:
+                default: netbox
+            database_user:
+                default: netbox
+            redis_password:
+                description: Redis password for authentication
+                type: str
+                sensitive: true
+                autogenerated: true
+                required: true
+    email:
+        key: email
+        title: ""
+        vars:
+            email_from:
+                default: netbox@example.com
+            email_server:
+                type: str
+                default: localhost
+    general:
+        key: general
+        title: ""
+        vars:
+            service_name:
+                default: netbox
+    netbox:
+        key: netbox
+        title: NetBox Configuration
+        description: Configure NetBox application settings
+        vars:
+            netbox_metrics_enabled:
+                description: Enable Prometheus metrics endpoint
+                type: bool
+                default: false
+            netbox_secret_key:
+                description: Secret Key
+                type: str
+                sensitive: true
+                autogenerated: true
+                required: true
+                extra: Used for cryptographic signing and session management
+    ports:
+        key: ports
+        title: ""
+        vars:
+            ports_http:
+                description: Host port for HTTP
+                default: 8000
+    swarm:
+        key: swarm
+        title: ""
+        description: |
+            Docker Swarm deployment requires an external PostgreSQL database. Enable 'database_external' in the Database section first.
+        needs:
+            - database_external=true
+        vars:
+            swarm_placement_host:
+                description: Target hostname for stateful services (NetBox, Redis)
+            swarm_worker_placement_host:
+                description: Target hostname for worker service (replicated mode only)
+                type: str
+                needs: [swarm_worker_placement_mode=replicated]
+            swarm_worker_placement_mode:
+                description: Deployment mode for worker service
+                type: enum
+                default: replicated
+                options: [replicated, global]
+                required: true
+            swarm_worker_replicas:
+                description: Number of worker replicas for queue processing
+                type: int
+                default: 1
+                required: true
+                needs: [swarm_worker_placement_mode=replicated]
+    traefik:
+        key: traefik
+        title: ""
+        vars:
+            traefik_host:
+                default: netbox

+ 1 - 46
library/compose/nextcloud/compose.yaml.j2

@@ -18,21 +18,8 @@ services:
       - POSTGRES_USER={{ database_user }}
       - POSTGRES_HOST={{ service_name }}-db
       {% endif %}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if traefik_enabled %}
-      {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
+    {% if not traefik_enabled %}
     ports:
       {% if swarm_enabled %}
       - target: 80
@@ -100,16 +87,6 @@ services:
       - MYSQL_PASSWORD=${MYSQL_PASSWORD}
       - MYSQL_DATABASE={{ database_name }}
       - MYSQL_USER={{ database_user }}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address_db }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
     volumes:
       - nextcloud-db:/var/lib/mysql
@@ -124,16 +101,6 @@ services:
       - POSTGRES_USER={{ database_user }}
       - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
       - POSTGRES_DB={{ database_name }}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address_db }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
     volumes:
       - nextcloud-db:/var/lib/postgresql/data
@@ -154,16 +121,4 @@ networks:
   {{ traefik_network }}:
     external: true
   {% endif %}
-  {% if network_mode == 'macvlan' %}
-  {{ network_name }}:
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-  {% elif network_mode == 'bridge' %}
-  {{ network_name }}:
-    driver: {% if swarm_enabled %}overlay{% else %}bridge{% endif %}
   {% endif %}

+ 169 - 0
library/compose/nextcloud/compose.yaml.j2.bak3

@@ -0,0 +1,169 @@
+services:
+  {{ service_name }}-app:
+    image: docker.io/library/nextcloud:31.0.10-apache
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}-app
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      {% if database_type == 'mysql' %}
+      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
+      - MYSQL_DATABASE={{ database_name }}
+      - MYSQL_USER={{ database_user }}
+      - MYSQL_HOST={{ service_name }}-db
+      {% elif database_type == 'postgres' %}
+      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
+      - POSTGRES_DB={{ database_name }}
+      - POSTGRES_USER={{ database_user }}
+      - POSTGRES_HOST={{ service_name }}-db
+      {% endif %}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if not traefik_enabled and network_mode == 'bridge' %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 80
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:80"
+      {% endif %}
+    {% endif %}
+    volumes:
+      - nextcloud-data:/var/www/html
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=80
+      - traefik.http.routers.{{ service_name }}-web-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-web-http.entrypoints={{ traefik_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-web-http.service={{ service_name }}-web
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-web-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-web-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-web-https.tls=true
+      - traefik.http.routers.{{ service_name }}-web-https.tls.certresolver={{ traefik_tls_certresolver }}
+      - traefik.http.routers.{{ service_name }}-web-https.service={{ service_name }}-web
+      {% endif %}
+    {% endif %}
+    depends_on:
+      - {{ service_name }}-db
+    {% if swarm_enabled %}
+    deploy:
+      replicas: {{ swarm_replicas }}
+      {% if traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=80
+        - traefik.http.routers.{{ service_name }}-web-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-web-http.entrypoints={{ traefik_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-web-http.service={{ service_name }}-web
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-web-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-web-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-web-https.tls=true
+        - traefik.http.routers.{{ service_name }}-web-https.tls.certresolver={{ traefik_tls_certresolver }}
+        - traefik.http.routers.{{ service_name }}-web-https.service={{ service_name }}-web
+        {% endif %}
+      {% endif %}
+    {% endif %}
+
+  {{ service_name }}-db:
+    {% if database_type == 'mysql' %}
+    # See compatibility matrix for Nextcloud 31
+    # https://docs.nextcloud.com/server/31/admin_manual/installation/system_requirements.html
+    image: docker.io/library/mariadb:10.11.14
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-db
+    {% endif %}
+    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
+    environment:
+      - TZ={{ container_timezone }}
+      - MYSQL_RANDOM_ROOT_PASSWORD=true
+      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
+      - MYSQL_DATABASE={{ database_name }}
+      - MYSQL_USER={{ database_user }}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address_db }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    volumes:
+      - nextcloud-db:/var/lib/mysql
+    {% elif database_type == 'postgres' %}
+    image: docker.io/library/postgres:17.6
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-db
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      - POSTGRES_USER={{ database_user }}
+      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
+      - POSTGRES_DB={{ database_name }}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address_db }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    volumes:
+      - nextcloud-db:/var/lib/postgresql/data
+    {% endif %}
+    {% if swarm_enabled %}
+    deploy:
+      replicas: 1
+    {% endif %}
+
+volumes:
+  nextcloud-data:
+    driver: local
+  nextcloud-db:
+    driver: local
+
+networks:
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+  {% if network_mode == 'macvlan' %}
+  {{ network_name }}:
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+  {% elif network_mode == 'bridge' %}
+  {{ network_name }}:
+    driver: {% if swarm_enabled %}overlay{% else %}bridge{% endif %}
+  {% endif %}

+ 142 - 0
library/compose/nextcloud/compose.yaml.j2.final

@@ -0,0 +1,142 @@
+services:
+  {{ service_name }}-app:
+    image: docker.io/library/nextcloud:31.0.10-apache
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}-app
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      {% if database_type == 'mysql' %}
+      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
+      - MYSQL_DATABASE={{ database_name }}
+      - MYSQL_USER={{ database_user }}
+      - MYSQL_HOST={{ service_name }}-db
+      {% elif database_type == 'postgres' %}
+      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
+      - POSTGRES_DB={{ database_name }}
+      - POSTGRES_USER={{ database_user }}
+      - POSTGRES_HOST={{ service_name }}-db
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if not traefik_enabled and network_mode == 'bridge' %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 80
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:80"
+      {% endif %}
+    {% endif %}
+    volumes:
+      - nextcloud-data:/var/www/html
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=80
+      - traefik.http.routers.{{ service_name }}-web-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-web-http.entrypoints={{ traefik_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-web-http.service={{ service_name }}-web
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-web-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-web-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-web-https.tls=true
+      - traefik.http.routers.{{ service_name }}-web-https.tls.certresolver={{ traefik_tls_certresolver }}
+      - traefik.http.routers.{{ service_name }}-web-https.service={{ service_name }}-web
+      {% endif %}
+    {% endif %}
+    depends_on:
+      - {{ service_name }}-db
+    {% if swarm_enabled %}
+    deploy:
+      replicas: {{ swarm_replicas }}
+      {% if traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=80
+        - traefik.http.routers.{{ service_name }}-web-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-web-http.entrypoints={{ traefik_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-web-http.service={{ service_name }}-web
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-web-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-web-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-web-https.tls=true
+        - traefik.http.routers.{{ service_name }}-web-https.tls.certresolver={{ traefik_tls_certresolver }}
+        - traefik.http.routers.{{ service_name }}-web-https.service={{ service_name }}-web
+        {% endif %}
+      {% endif %}
+    {% endif %}
+
+  {{ service_name }}-db:
+    {% if database_type == 'mysql' %}
+    # See compatibility matrix for Nextcloud 31
+    # https://docs.nextcloud.com/server/31/admin_manual/installation/system_requirements.html
+    image: docker.io/library/mariadb:10.11.14
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-db
+    {% endif %}
+    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
+    environment:
+      - TZ={{ container_timezone }}
+      - MYSQL_RANDOM_ROOT_PASSWORD=true
+      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
+      - MYSQL_DATABASE={{ database_name }}
+      - MYSQL_USER={{ database_user }}
+    {% endif %}
+    volumes:
+      - nextcloud-db:/var/lib/mysql
+    {% elif database_type == 'postgres' %}
+    image: docker.io/library/postgres:17.6
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-db
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      - POSTGRES_USER={{ database_user }}
+      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
+      - POSTGRES_DB={{ database_name }}
+    {% endif %}
+    volumes:
+      - nextcloud-db:/var/lib/postgresql/data
+    {% endif %}
+    {% if swarm_enabled %}
+    deploy:
+      replicas: 1
+    {% endif %}
+
+volumes:
+  nextcloud-data:
+    driver: local
+  nextcloud-db:
+    driver: local
+
+networks:
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+  {% if network_mode == 'macvlan' %}
+  {{ network_name }}:
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+  {% elif network_mode == 'bridge' %}
+  {{ network_name }}:
+    driver: {% if swarm_enabled %}overlay{% else %}bridge{% endif %}
+  {% endif %}

+ 124 - 0
library/compose/nextcloud/compose.yaml.j2.portfix

@@ -0,0 +1,124 @@
+services:
+  {{ service_name }}-app:
+    image: docker.io/library/nextcloud:31.0.10-apache
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}-app
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      {% if database_type == 'mysql' %}
+      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
+      - MYSQL_DATABASE={{ database_name }}
+      - MYSQL_USER={{ database_user }}
+      - MYSQL_HOST={{ service_name }}-db
+      {% elif database_type == 'postgres' %}
+      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
+      - POSTGRES_DB={{ database_name }}
+      - POSTGRES_USER={{ database_user }}
+      - POSTGRES_HOST={{ service_name }}-db
+      {% endif %}
+    {% endif %}
+    {% if not traefik_enabled and network_mode == 'bridge' %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 80
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:80"
+      {% endif %}
+    {% endif %}
+    volumes:
+      - nextcloud-data:/var/www/html
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=80
+      - traefik.http.routers.{{ service_name }}-web-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-web-http.entrypoints={{ traefik_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-web-http.service={{ service_name }}-web
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-web-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-web-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-web-https.tls=true
+      - traefik.http.routers.{{ service_name }}-web-https.tls.certresolver={{ traefik_tls_certresolver }}
+      - traefik.http.routers.{{ service_name }}-web-https.service={{ service_name }}-web
+      {% endif %}
+    {% endif %}
+    depends_on:
+      - {{ service_name }}-db
+    {% if swarm_enabled %}
+    deploy:
+      replicas: {{ swarm_replicas }}
+      {% if traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadbalancer.server.port=80
+        - traefik.http.routers.{{ service_name }}-web-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-web-http.entrypoints={{ traefik_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-web-http.service={{ service_name }}-web
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-web-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-web-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-web-https.tls=true
+        - traefik.http.routers.{{ service_name }}-web-https.tls.certresolver={{ traefik_tls_certresolver }}
+        - traefik.http.routers.{{ service_name }}-web-https.service={{ service_name }}-web
+        {% endif %}
+      {% endif %}
+    {% endif %}
+
+  {{ service_name }}-db:
+    {% if database_type == 'mysql' %}
+    # See compatibility matrix for Nextcloud 31
+    # https://docs.nextcloud.com/server/31/admin_manual/installation/system_requirements.html
+    image: docker.io/library/mariadb:10.11.14
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-db
+    {% endif %}
+    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
+    environment:
+      - TZ={{ container_timezone }}
+      - MYSQL_RANDOM_ROOT_PASSWORD=true
+      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
+      - MYSQL_DATABASE={{ database_name }}
+      - MYSQL_USER={{ database_user }}
+    {% endif %}
+    volumes:
+      - nextcloud-db:/var/lib/mysql
+    {% elif database_type == 'postgres' %}
+    image: docker.io/library/postgres:17.6
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-db
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      - POSTGRES_USER={{ database_user }}
+      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
+      - POSTGRES_DB={{ database_name }}
+    {% endif %}
+    volumes:
+      - nextcloud-db:/var/lib/postgresql/data
+    {% endif %}
+    {% if swarm_enabled %}
+    deploy:
+      replicas: 1
+    {% endif %}
+
+volumes:
+  nextcloud-data:
+    driver: local
+  nextcloud-db:
+    driver: local
+
+networks:
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+  {% endif %}

+ 36 - 27
library/compose/nginx/compose.yaml.j2

@@ -1,25 +1,25 @@
 services:
   {{ service_name }}:
     image: docker.io/library/nginx:1.28.0-alpine
+    {#
+      If not in swarm mode, apply restart policy and container name
+    #}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     container_name: {{ container_name }}
     {% endif %}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
     networks:
-      {% if traefik_enabled %}
       {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
+    {#
+      Port mappings for HTTP and HTTPS (only when Traefik is disabled)
+      Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
+    #}
+    {% if not traefik_enabled %}
     ports:
       {% if swarm_enabled %}
       - target: 80
@@ -35,9 +35,18 @@ services:
       - "{{ ports_https }}:443"
       {% endif %}
     {% endif %}
+    {#
+      Volume configuration (commented out - uncomment and customize as needed):
+      - config: Nginx configuration files
+      - data: Static content to serve
+    #}
     # volumes:
     #   - ./config/default.conf:/etc/nginx/conf.d/default.conf:ro
     #   - ./data:/usr/share/nginx/html:ro
+    {#
+      When traefik_enabled is set, and not running in swarm mode, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
     {% if traefik_enabled and not swarm_enabled %}
     labels:
       - traefik.enable=true
@@ -54,9 +63,18 @@ services:
       - traefik.http.routers.{{ service_name }}-web-https.service={{ service_name }}-web
       {% endif %}
     {% endif %}
+    {#
+      Deploy configuration for Swarm mode:
+      - Configure replicas
+      - Traefik: Labels for reverse proxy integration (Swarm mode)
+    #}
     {% if swarm_enabled %}
     deploy:
       replicas: {{ swarm_replicas }}
+      {#
+        When traefik_enabled is set in swarm mode, add traefik labels
+        (optionally enable TLS if traefik_tls_enabled is set)
+      #}
       {% if traefik_enabled %}
       labels:
         - traefik.enable=true
@@ -75,21 +93,12 @@ services:
       {% endif %}
     {% endif %}
 
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
-  {% if network_mode == 'macvlan' %}
-  {{ network_name }}:
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-  {% elif network_mode == 'bridge' %}
-  {{ network_name }}:
-    driver: {% if swarm_enabled %}overlay{% else %}bridge{% endif %}
-  {% endif %}
+{% endif %}

+ 1 - 0
library/compose/nginx/template.yaml

@@ -18,6 +18,7 @@ metadata:
   author: Christian Lempa
   tags:
     - traefik
+    - swarm
 spec:
   ports:
     vars:

+ 0 - 40
library/compose/nginxproxymanager/compose.yaml.j2

@@ -40,16 +40,6 @@ services:
         volume:
           nocopy: true
       {% endif %}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
     depends_on:
       - {{ service_name }}-db
@@ -105,15 +95,6 @@ services:
         volume:
           nocopy: true
       {% endif %}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
     {% if swarm_enabled %}
     secrets:
@@ -131,27 +112,6 @@ services:
           memory: {{ resources_memory_reservation }}
       {% endif %}
     {% endif %}
-{% if network_mode != 'host' %}
-networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
     {% endif %}
 {% endif %}
 volumes:

+ 188 - 0
library/compose/nginxproxymanager/compose.yaml.j2.bak3

@@ -0,0 +1,188 @@
+services:
+  {{ service_name }}:
+    image: docker.io/jc21/nginx-proxy-manager:2.12.6
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    hostname: {{ container_hostname }}
+    environment:
+      - TZ={{ container_timezone }}
+      - DB_MYSQL_HOST={{ service_name }}-db
+      - DB_MYSQL_PORT={{ database_port }}
+      - DB_MYSQL_USER={{ database_user }}
+      {% if swarm_enabled %}
+      - DB_MYSQL_PASSWORD=/run/secrets/database_password
+      {% else %}
+      - DB_MYSQL_PASSWORD={{ database_password }}
+      {% endif %}
+      - DB_MYSQL_NAME={{ database_name }}
+    ports:
+      - {{ ports_http }}:80
+      - 81:81
+      - {{ ports_https }}:443
+    volumes:
+      {% if volume_mode == 'local' %}
+      - {{ service_name }}_data:/data
+      - {{ service_name }}_ssl:/etc/letsencrypt
+      {% elif volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/{{ service_name }}/data:/data
+      - {{ volume_mount_path }}/{{ service_name }}/ssl:/etc/letsencrypt
+      {% elif volume_mode == 'nfs' %}
+      - type: volume
+        source: {{ service_name }}_data
+        target: /data
+        volume:
+          nocopy: true
+      - type: volume
+        source: {{ service_name }}_ssl
+        target: /etc/letsencrypt
+        volume:
+          nocopy: true
+      {% endif %}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    depends_on:
+      - {{ service_name }}-db
+    {% if swarm_enabled %}
+    secrets:
+      - database_password
+    deploy:
+      {% if swarm_placement_mode == 'replicated' %}
+      replicas: {{ swarm_replicas }}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      {% else %}
+      mode: global
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+      {% endif %}
+    {% endif %}
+  {{ service_name }}-db:
+    image: docker.io/jc21/mariadb-aria:10.11.5
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}-db
+    {% endif %}
+    hostname: {{ container_hostname }}-db
+    environment:
+      - TZ={{ container_timezone }}
+      {% if swarm_enabled %}
+      - MYSQL_ROOT_PASSWORD=/run/secrets/database_root_password
+      - MYSQL_PASSWORD=/run/secrets/database_password
+      {% else %}
+      - MYSQL_ROOT_PASSWORD={{ database_root_password }}
+      - MYSQL_PASSWORD={{ database_password }}
+      {% endif %}
+      - MYSQL_DATABASE={{ database_name }}
+      - MYSQL_USER={{ database_user }}
+    volumes:
+      {% if volume_mode == 'local' %}
+      - {{ service_name }}_db:/var/lib/mysql
+      {% elif volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/{{ service_name }}/db:/var/lib/mysql
+      {% elif volume_mode == 'nfs' %}
+      - type: volume
+        source: {{ service_name }}_db
+        target: /var/lib/mysql
+        volume:
+          nocopy: true
+      {% endif %}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if swarm_enabled %}
+    secrets:
+      - database_root_password
+      - database_password
+    deploy:
+      replicas: 1
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+      {% endif %}
+    {% endif %}
+{% if network_mode != 'host' %}
+networks:
+  {{ network_name }}:
+    {% if network_external %}
+    external: true
+    {% else %}
+    {% if network_mode == 'macvlan' %}
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+    name: {{ network_name }}
+    {% elif swarm_enabled %}
+    driver: overlay
+    attachable: true
+    {% else %}
+    driver: bridge
+    {% endif %}
+    {% endif %}
+{% endif %}
+volumes:
+  {{ service_name }}_data:
+    {% if volume_mode == 'nfs' %}
+    driver: local
+    driver_opts:
+      type: nfs
+      o: {{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/{{ service_name }}/data"
+    {% endif %}
+  {{ service_name }}_ssl:
+    {% if volume_mode == 'nfs' %}
+    driver: local
+    driver_opts:
+      type: nfs
+      o: {{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/{{ service_name }}/ssl"
+    {% endif %}
+  {{ service_name }}_db:
+    {% if volume_mode == 'nfs' %}
+    driver: local
+    driver_opts:
+      type: nfs
+      o: {{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/{{ service_name }}/db"
+    {% endif %}
+{% if swarm_enabled %}
+secrets:
+  database_root_password:
+    external: true
+  database_password:
+    external: true
+{% endif %}

+ 32 - 32
library/compose/openwebui/compose.yaml.j2

@@ -2,7 +2,15 @@ services:
   {{ service_name }}:
     image: ghcr.io/open-webui/open-webui:v{{ openwebui_version }}
     container_name: {{ container_name }}
+    {#
+      Set container hostname for identification
+    #}
     hostname: {{ container_hostname }}
+    {#
+      Environment variables for OpenWebUI configuration:
+      - Ollama API URL
+      - OAuth/OpenID configuration (if Authentik is enabled)
+    #}
     environment:
       - TZ={{ container_timezone }}
       - OLLAMA_BASE_URL={{ ollama_base_url }}
@@ -16,26 +24,29 @@ services:
       - OAUTH_SCOPES={{ oauth_scopes }}
       - OPENID_REDIRECT_URI={{ openid_redirect_uri }}
       {% endif %}
+    {#
+      Volume configuration for persistent data
+    #}
     volumes:
       - data:/app/backend/data:rw
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
     networks:
-      {% if traefik_enabled %}
       {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
     {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
+    {#
+      Port mappings for web interface (only when Traefik is disabled)
+    #}
+    {% if not traefik_enabled %}
     ports:
       - "{{ ports_http }}:8080"
     {% endif %}
+    {#
+      When traefik_enabled is set, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
     {% if traefik_enabled %}
     labels:
       - traefik.enable=true
@@ -54,31 +65,20 @@ services:
     {% endif %}
     restart: {{ restart_policy }}
 
+{#
+  Volume definitions:
+  - data: Persistent storage for OpenWebUI data
+#}
 volumes:
   data:
     driver: local
 
-{% if network_mode != 'host' %}
+{#
+  Network definitions (only when Traefik is enabled):
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if traefik_enabled %}
 networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-    name: {{ network_name }}
-  {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
-  {% endif %}
 {% endif %}

+ 1 - 0
library/compose/openwebui/template.yaml

@@ -20,6 +20,7 @@ metadata:
   date: '2025-11-07'
   tags:
     - traefik
+    - authentik
 spec:
   general:
     vars:

+ 137 - 0
library/compose/pangolin/compose.yaml.j2.final

@@ -0,0 +1,137 @@
+---
+services:
+  {{ service_name }}:
+    image: docker.io/fosrl/pangolin:latest
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    hostname: {{ container_hostname }}
+    {#
+      When traefik is enabled, add traefik network for reverse proxy access
+    #}
+    {% if traefik_enabled %}
+    networks:
+      {{ traefik_network }}:
+    {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if network_mode == 'bridge' and not traefik_enabled %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 8080
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:8080/tcp"
+      {% endif %}
+    {% endif %}
+    {% if environment_enabled or postgres_enabled %}
+    environment:
+      {% if postgres_enabled %}
+      POSTGRES_CONNECTION_STRING: "{{ postgres_connection_string }}"
+      {% endif %}
+      {% if environment_enabled %}
+      {% if environment_log_level %}
+      LOG_LEVEL: "{{ environment_log_level }}"
+      {% endif %}
+      {% if environment_crowdsec_enabled %}
+      CROWDSEC_ENABLED: "true"
+      {% endif %}
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/data:/app/data:rw
+      - {{ volume_mount_path }}/config:/app/config:rw
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-data:/app/data
+      - {{ service_name }}-config:/app/config
+      {% endif %}
+    {% if swarm_enabled or resources_enabled %}
+    deploy:
+      {% if swarm_enabled %}
+      mode: replicated
+      replicas: 1
+      restart_policy:
+        condition: on-failure
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        {% if swarm_enabled %}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+        {% endif %}
+      {% endif %}
+      {% if swarm_enabled and traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=8080
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=8080
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}
+
+{% if volume_mode == 'local' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+  {{ service_name }}-config:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/data"
+  {{ service_name }}-config:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/config"
+{% endif %}
+
+    {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}

+ 113 - 0
library/compose/pangolin/template.yaml.backup

@@ -0,0 +1,113 @@
+---
+kind: compose
+schema: "1.2"
+metadata:
+  name: Pangolin
+  description: |
+    Self-hosted reverse proxy server that securely exposes private resources on distributed networks through
+    encrypted WireGuard tunnels. Pangolin enables access from anywhere without opening ports, using a custom
+    user-space WireGuard client (Newt) for secure connectivity. Features include automatic tunnel management,
+    integrated CrowdSec security, and support for both PostgreSQL and SQLite databases.
+    ## References
+    * **Project:** https://github.com/fosrl/pangolin
+    * **Documentation:** https://github.com/fosrl/pangolin/blob/main/README.md
+    * **Docker Hub:** https://hub.docker.com/r/fosrl/pangolin
+  version: latest
+  author: Christian Lempa
+  date: '2025-11-13'
+  tags:
+    - traefik
+    - swarm
+    - proxy
+    - wireguard
+  next_steps: |
+    ### 1. Configure Database
+    {% if postgres_enabled -%}
+    Make sure PostgreSQL is running and accessible at:
+    * Connection string: {{ postgres_connection_string }}
+    {% else -%}
+    Pangolin will use SQLite database stored in the data volume.
+    {% endif -%}
+    ### 2. Deploy the Service
+    {% if swarm_enabled -%}
+    Deploy to Docker Swarm:
+    ```bash
+    docker stack deploy -c compose.yaml pangolin
+    ```
+    {% else -%}
+    Start Pangolin using Docker Compose:
+    ```bash
+    docker compose up -d
+    ```
+    {% endif -%}
+    ### 3. Access the Web Interface
+    {% if traefik_enabled -%}
+    * Navigate to: **https://{{ traefik_host }}.{{ traefik_domain }}**
+    {% else -%}
+    * Navigate to: **http://localhost:{{ ports_http }}**
+    {% endif -%}
+    ### 4. Configure WireGuard Clients
+    * Use the Pangolin web interface to create and manage WireGuard tunnels
+    * Deploy Newt client on remote machines to establish secure connections
+spec:
+  general:
+    vars:
+      service_name:
+        default: "pangolin"
+      container_name:
+        default: "pangolin"
+      container_hostname:
+        default: "pangolin"
+  traefik:
+    vars:
+      traefik_host:
+        default: "pangolin"
+  network:
+    vars:
+      network_name:
+        default: "pangolin_network"
+  ports:
+    vars:
+      ports_http:
+        description: "External HTTP port (web interface)"
+        type: int
+        default: 8080
+        needs: ["traefik_enabled=false", "network_mode=bridge"]
+  volume:
+    vars:
+      volume_mount_path:
+        default: "/mnt/storage/pangolin"
+  postgres:
+    title: "PostgreSQL Configuration"
+    toggle: postgres_enabled
+    needs: null
+    vars:
+      postgres_enabled:
+        type: bool
+        default: false
+        description: "Use PostgreSQL database (SQLite is default)"
+      postgres_connection_string:
+        type: str
+        default: "postgresql://postgres:postgres@localhost:5432"
+        description: "PostgreSQL connection string"
+        needs: "postgres_enabled=true"
+  environment:
+    title: "Environment Variables"
+    toggle: environment_enabled
+    needs: null
+    vars:
+      environment_enabled:
+        type: bool
+        default: false
+        description: "Configure additional environment variables"
+      environment_crowdsec_enabled:
+        type: bool
+        default: false
+        description: "Enable CrowdSec integration"
+        needs: "environment_enabled=true"
+      environment_log_level:
+        type: enum
+        default: "info"
+        options: ["debug", "info", "warn", "error"]
+        description: "Log level"
+        needs: "environment_enabled=true"

+ 1 - 0
library/compose/pihole/.env.secret.webpassword.j2

@@ -0,0 +1 @@
+{{ webpassword }}

+ 38 - 0
library/compose/pihole/common/networks.yaml.j2

@@ -0,0 +1,38 @@
+{#
+  Network definitions (only when needed):
+  - When network_mode is empty: no definition needed (uses Docker's default bridge)
+  - When network_mode is 'bridge': define custom bridge network
+  - When network_mode is 'macvlan': configure macvlan with static IP (for Compose mode)
+    Note: In Swarm mode, macvlan networks must be created manually with config-only networks on each node
+  - When swarm_enabled: use overlay network for multi-host communication
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %}
+networks:
+  {% if network_mode == 'bridge' or network_mode == 'macvlan'%}
+  {{ network_name }}:
+    {% if network_external %}
+    external: true
+    {% else %}
+    {% if network_mode == 'macvlan' %}
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+    name: {{ network_name }}
+    {% elif swarm_enabled %}
+    driver: overlay
+    attachable: true
+    {% else %}
+    driver: bridge
+    {% endif %}
+    {% endif %}
+  {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}

+ 3 - 0
library/compose/pihole/common/secrets.yaml.j2

@@ -0,0 +1,3 @@
+secrets:
+  {{ service_name }}_webpassword:
+    file: ./.env.secret.webpassword

+ 21 - 0
library/compose/pihole/common/volumes.yaml.j2

@@ -0,0 +1,21 @@
+{% if volume_mode == 'local' %}
+volumes:
+  {{ service_name }}-dnsmasq:
+    driver: local
+  {{ service_name }}-pihole:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-dnsmasq:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/dnsmasq"
+  {{ service_name }}-pihole:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/pihole"
+{% endif %}

+ 11 - 196
library/compose/pihole/compose.yaml.j2

@@ -1,197 +1,12 @@
----
-services:
-  {{ service_name }}:
-    image: docker.io/pihole/pihole:2025.11.0
-    {% if not swarm_enabled %}
-    restart: {{ restart_policy }}
-    container_name: {{ container_name }}
-    {% endif %}
-    hostname: {{ container_hostname }}
-    environment:
-      - TZ={{ container_timezone }}
-      - PIHOLE_UID={{ user_uid }}
-      - PIHOLE_GID={{ user_gid }}
-      {% if swarm_enabled %}
-      - WEBPASSWORD_FILE={{ service_name }}_webpassword
-      {% else %}
-      - FTLCONF_webserver_api_password=${WEBPASSWORD}
-      {% endif %}
-      {% if network_mode == 'bridge' %}
-      - FTLCONF_dns_listeningMode=all
-      {% endif %}
-    {% if network_mode == 'host' %}
-    network_mode: host
-    {% else %}
-    networks:
-      {% if traefik_enabled %}
-      {{ traefik_network }}:
-      {% endif %}
-      {% if network_mode == 'macvlan' %}
-      {{ network_name }}:
-        ipv4_address: {{ network_macvlan_ipv4_address }}
-      {% elif network_mode == 'bridge' %}
-      {{ network_name }}:
-      {% endif %}
-    {% endif %}
-    {% if not traefik_enabled and network_mode == 'bridge' %}
-    ports:
-      {% if not traefik_enabled %}
-      {% if swarm_enabled %}
-      - target: 80
-        published: {{ ports_http }}
-        protocol: tcp
-        mode: host
-      - target: 443
-        published: {{ ports_https }}
-        protocol: tcp
-        mode: host
-      {% else %}
-      - "{{ ports_http }}:80/tcp"
-      - "{{ ports_https }}:443/tcp"
-      {% endif %}
-      {% endif %}
-      {% if swarm_enabled %}
-      - target: 53
-        published: {{ ports_dns }}
-        protocol: tcp
-        mode: host
-      - target: 53
-        published: {{ ports_dns }}
-        protocol: udp
-        mode: host
-      - target: 123
-        published: {{ ports_ntp }}
-        protocol: udp
-        mode: host
-      {% else %}
-      - "{{ ports_dns }}:53/tcp"
-      - "{{ ports_dns }}:53/udp"
-      - "{{ ports_ntp }}:123/udp"
-      {% endif %}
-    {% endif %}
-    volumes:
-      {% if volume_mode == 'mount' %}
-      - {{ volume_mount_path }}/dnsmasq:/etc/dnsmasq.d:rw
-      - {{ volume_mount_path }}/pihole:/etc/pihole:rw
-      {% elif volume_mode in ['local', 'nfs'] %}
-      - {{ service_name }}-dnsmasq:/etc/dnsmasq.d
-      - {{ service_name }}-pihole:/etc/pihole
-      {% endif %}
-    cap_add:
-      - NET_ADMIN
-      - SYS_TIME
-    {% if swarm_enabled %}
-    secrets:
-      - {{ service_name }}_webpassword
-    {% endif %}
-    {% if swarm_enabled or resources_enabled %}
-    deploy:
-      {% if swarm_enabled %}
-      mode: replicated
-      replicas: 1
-      placement:
-        constraints:
-          - node.hostname == {{ swarm_placement_host }}
-      restart_policy:
-        condition: on-failure
-      {% endif %}
-      {% if resources_enabled %}
-      resources:
-        limits:
-          cpus: '{{ resources_cpu_limit }}'
-          memory: {{ resources_memory_limit }}
-        {% if swarm_enabled %}
-        reservations:
-          cpus: '{{ resources_cpu_reservation }}'
-          memory: {{ resources_memory_reservation }}
-        {% endif %}
-      {% endif %}
-      {% if swarm_enabled and traefik_enabled %}
-      labels:
-        - traefik.enable=true
-        - traefik.docker.network={{ traefik_network }}
-        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
-        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
-        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
-        {% if traefik_tls_enabled %}
-        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
-        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
-        - traefik.http.routers.{{ service_name }}-https.tls=true
-        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
-        {% endif %}
-      {% endif %}
-    {% endif %}
-    {% if traefik_enabled and not swarm_enabled %}
-    labels:
-      - traefik.enable=true
-      - traefik.docker.network={{ traefik_network }}
-      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
-      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
-      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
-      {% if traefik_tls_enabled %}
-      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
-      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
-      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
-      - traefik.http.routers.{{ service_name }}-https.tls=true
-      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
-      {% endif %}
-    {% endif %}
-
-{% if swarm_enabled %}
-secrets:
-  {{ service_name }}_webpassword:
-    file: ./.env.secret.webpassword
-{% endif %}
-
-{% if volume_mode == 'local' %}
-volumes:
-  {{ service_name }}-dnsmasq:
-    driver: local
-  {{ service_name }}-pihole:
-    driver: local
-{% elif volume_mode == 'nfs' %}
-volumes:
-  {{ service_name }}-dnsmasq:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/dnsmasq"
-  {{ service_name }}-pihole:
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
-      device: ":{{ volume_nfs_path }}/pihole"
-{% endif %}
-
-{% if network_mode != 'host' %}
-networks:
-  {{ network_name }}:
-    {% if network_external %}
-    external: true
-    {% else %}
-    {% if network_mode == 'macvlan' %}
-    driver: macvlan
-    driver_opts:
-      parent: {{ network_macvlan_parent_interface }}
-    ipam:
-      config:
-        - subnet: {{ network_macvlan_subnet }}
-          gateway: {{ network_macvlan_gateway }}
-    name: {{ network_name }}
-    {% elif swarm_enabled %}
-    driver: overlay
-    attachable: true
-    {% else %}
-    driver: bridge
-    {% endif %}
-    {% endif %}
-  {% if traefik_enabled %}
-  {{ traefik_network }}:
-    external: true
+{#
+  Pi-hole Docker Compose Configuration
+#}
+include:
+  - services/pihole.yaml
+  {% if network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %}
+  - common/networks.yaml
+  {% endif %}
+  - common/volumes.yaml
+  {% if swarm_enabled %}
+  - common/secrets.yaml
   {% endif %}
-{% endif %}

+ 266 - 0
library/compose/pihole/compose.yaml.j2.backup

@@ -0,0 +1,266 @@
+---
+services:
+  {{ service_name }}:
+    image: docker.io/pihole/pihole:2025.11.0
+    {#
+      If not in swarm mode, check whether container_name is set and apply restart policy,
+      else swarm mode handles restarts via deploy.restart_policy
+    #}
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    {% if container_name %}
+    container_name: {{ container_name }}
+    {% endif %}
+    {% endif %}
+    {#
+      Set container hostname (Pi-hole requires this for proper operation)
+    #}
+    {% if container_hostname %}
+    hostname: {{ container_hostname }}
+    {% endif %}
+    {#
+      Environment variables for Pi-hole configuration
+      - TZ: Timezone for proper log rotation
+      - PIHOLE_UID/GID: User/group for file permissions
+      - WEBPASSWORD: Admin password (from env file in compose mode, from secret in swarm mode)
+      - FTLCONF_dns_listeningMode: In bridge mode, listen on all interfaces
+    #}
+    environment:
+      - TZ={{ container_timezone }}
+      - PIHOLE_UID={{ user_uid }}
+      - PIHOLE_GID={{ user_gid }}
+      {% if swarm_enabled %}
+      - WEBPASSWORD_FILE={{ service_name }}_webpassword
+      {% else %}
+      - FTLCONF_webserver_api_password=${WEBPASSWORD}
+      {% endif %}
+      {% if network_mode == 'bridge' %}
+      - FTLCONF_dns_listeningMode=all
+      {% endif %}
+    {#
+      Network configuration based on network_mode:
+      - 'host': Use host networking (direct access to host network stack, not supported in Swarm)
+      - 'bridge': Custom bridge network (default Docker networking)
+      - 'macvlan': Container gets its own MAC/IP on physical network (requires external macvlan network setup in Swarm)
+      - '' (empty): Uses Docker's default bridge network
+      When traefik is enabled, always add traefik network
+    #}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% elif network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {#
+      Port mappings when in bridge mode (or empty, which defaults to bridge)
+      - HTTP/HTTPS: Web interface (only if Traefik is disabled)
+      - DNS: Port 53 TCP/UDP (always exposed, even with Traefik, since DNS can't be proxied)
+      - NTP: Port 123 UDP (network time protocol, always exposed)
+      Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
+      Note: Host and macvlan modes don't need port mappings (direct network access)
+    #}
+    {% if network_mode == '' or network_mode == 'bridge' or traefik_enabled %}
+    ports:
+      {% if not traefik_enabled %}
+      {% if swarm_enabled %}
+      - target: 80
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      - target: 443
+        published: {{ ports_https }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:80/tcp"
+      - "{{ ports_https }}:443/tcp"
+      {% endif %}
+      {% endif %}
+      {% if swarm_enabled %}
+      - target: 53
+        published: {{ ports_dns }}
+        protocol: tcp
+        mode: host
+      - target: 53
+        published: {{ ports_dns }}
+        protocol: udp
+        mode: host
+      - target: 123
+        published: {{ ports_ntp }}
+        protocol: udp
+        mode: host
+      {% else %}
+      - "{{ ports_dns }}:53/tcp"
+      - "{{ ports_dns }}:53/udp"
+      - "{{ ports_ntp }}:123/udp"
+      {% endif %}
+    {% endif %}
+    {#
+      Volume configuration for persistent data
+      - When volume_mode is 'mount': bind mount from host path
+      - When volume_mode is 'local', 'nfs', or empty: use docker-managed volumes
+    #}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/dnsmasq:/etc/dnsmasq.d:rw
+      - {{ volume_mount_path }}/pihole:/etc/pihole:rw
+      {% else %}
+      - {{ service_name }}-dnsmasq:/etc/dnsmasq.d
+      - {{ service_name }}-pihole:/etc/pihole
+      {% endif %}
+    {#
+      Required capabilities:
+      - NET_ADMIN: For DHCP and routing table operations
+      - SYS_TIME: For NTP functionality
+    #}
+    cap_add:
+      - NET_ADMIN
+      - SYS_TIME
+    {#
+      When swarm_enabled is set, use Docker secrets for admin password
+    #}
+    {% if swarm_enabled %}
+    secrets:
+      - {{ service_name }}_webpassword
+    {% endif %}
+    {#
+      Deploy configuration for Swarm mode:
+      - Single replica with node placement constraint (Pi-hole doesn't support multi-replica)
+      - Multiple instances would conflict with DNS/DHCP services
+      - Traefik: Labels for reverse proxy integration (Swarm mode)
+      Note: For macvlan in Swarm, create config-only networks on each node first, then use --scope swarm
+    #}
+    {% if swarm_enabled %}
+    deploy:
+      mode: replicated
+      replicas: 1
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      restart_policy:
+        condition: on-failure
+      {#
+        When traefik_enabled is set in swarm mode, add traefik labels
+        (optionally enable TLS if traefik_tls_enabled is set)
+      #}
+      {% if traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {#
+      When traefik_enabled is set, and not running in swarm mode, add traefik labels
+      (optionally enable TLS if traefik_tls_enabled is set)
+    #}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}
+
+{#
+  When swarm_enabled is set, define Docker secret for admin password
+#}
+{% if swarm_enabled %}
+secrets:
+  {{ service_name }}_webpassword:
+    file: ./.env.secret.webpassword
+{% endif %}
+
+{#
+  Volume definitions:
+  - When volume_mode is 'local' (default): use docker-managed local volumes
+  - When volume_mode is 'nfs': configure NFS-backed volumes
+  - When volume_mode is 'mount': no volume definition needed (bind mounts used directly)
+#}
+{% if volume_mode == 'local' %}
+volumes:
+  {{ service_name }}-dnsmasq:
+    driver: local
+  {{ service_name }}-pihole:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-dnsmasq:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/dnsmasq"
+  {{ service_name }}-pihole:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/pihole"
+{% endif %}
+
+{#
+  Network definitions (only when needed):
+  - When network_mode is empty: no definition needed (uses Docker's default bridge)
+  - When network_mode is 'bridge': define custom bridge network
+  - When network_mode is 'macvlan': configure macvlan with static IP (for Compose mode)
+    Note: In Swarm mode, macvlan networks must be created manually with config-only networks on each node
+  - When swarm_enabled: use overlay network for multi-host communication
+  - Traefik network: always external (managed by Traefik)
+#}
+{% if network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %}
+networks:
+  {% if network_mode == 'bridge' or network_mode == 'macvlan' %}
+  {{ network_name }}:
+    {% if network_external %}
+    external: true
+    {% else %}
+    {% if network_mode == 'macvlan' %}
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+    name: {{ network_name }}
+    {% elif swarm_enabled %}
+    driver: overlay
+    attachable: true
+    {% else %}
+    driver: bridge
+    {% endif %}
+    {% endif %}
+  {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}

+ 133 - 0
library/compose/pihole/services/pihole.yaml.j2

@@ -0,0 +1,133 @@
+{#
+  Pi-hole: Network-wide ad blocking and DNS privacy
+  Provides DNS, DHCP, and ad blocking services
+#}
+services:
+  {{ service_name }}:
+    image: docker.io/pihole/pihole:2025.11.0
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    {% if container_name %}
+    container_name: {{ container_name }}
+    {% endif %}
+    {% endif %}
+    {% if container_hostname %}
+    hostname: {{ container_hostname }}
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      - PIHOLE_UID={{ user_uid }}
+      - PIHOLE_GID={{ user_gid }}
+      {% if swarm_enabled %}
+      - WEBPASSWORD_FILE={{ service_name }}_webpassword
+      {% else %}
+      - FTLCONF_webserver_api_password=${WEBPASSWORD}
+      {% endif %}
+      {% if network_mode == 'bridge' %}
+      - FTLCONF_dns_listeningMode=all
+      {% endif %}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% elif network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if network_mode == '' or network_mode == 'bridge' or traefik_enabled %}
+    ports:
+      {% if not traefik_enabled %}
+      {% if swarm_enabled %}
+      - target: 80
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      - target: 443
+        published: {{ ports_https }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:80/tcp"
+      - "{{ ports_https }}:443/tcp"
+      {% endif %}
+      {% endif %}
+      {% if swarm_enabled %}
+      - target: 53
+        published: {{ ports_dns }}
+        protocol: tcp
+        mode: host
+      - target: 53
+        published: {{ ports_dns }}
+        protocol: udp
+        mode: host
+      - target: 123
+        published: {{ ports_ntp }}
+        protocol: udp
+        mode: host
+      {% else %}
+      - "{{ ports_dns }}:53/tcp"
+      - "{{ ports_dns }}:53/udp"
+      - "{{ ports_ntp }}:123/udp"
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/dnsmasq:/etc/dnsmasq.d:rw
+      - {{ volume_mount_path }}/pihole:/etc/pihole:rw
+      {% else %}
+      - {{ service_name }}-dnsmasq:/etc/dnsmasq.d
+      - {{ service_name }}-pihole:/etc/pihole
+      {% endif %}
+    cap_add:
+      - NET_ADMIN
+      - SYS_TIME
+    {% if swarm_enabled %}
+    secrets:
+      - {{ service_name }}_webpassword
+    deploy:
+      mode: replicated
+      replicas: 1
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      restart_policy:
+        condition: on-failure
+      {% if traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}

+ 3 - 26
library/compose/pihole/template.yaml

@@ -20,6 +20,8 @@ metadata:
   tags:
     - traefik
     - swarm
+    - network_modes
+    - volume_modes
   next_steps: |
     ### 1. Deploy the Service
     {% if swarm_enabled -%}
@@ -45,8 +47,6 @@ spec:
     vars:
       service_name:
         default: "pihole"
-      container_name:
-        default: "pihole"
   admin_settings:
     description: "Admin Pi-hole Settings"
     required: true
@@ -59,8 +59,6 @@ spec:
         autogenerated: true
   traefik:
     vars:
-      traefik_enabled:
-        needs: "network_mode=bridge"
       traefik_host:
         default: "pihole.home.arpa"
   network:
@@ -73,35 +71,14 @@ spec:
         default: "pihole_network"
   ports:
     vars:
-      ports_http:
-        description: "External HTTP port"
-        type: int
-        default: 8080
-        needs: ["traefik_enabled=false", "network_mode=bridge"]
-      ports_https:
-        description: "External HTTPS port"
-        type: int
-        default: 8443
-        needs: ["traefik_enabled=false", "network_mode=bridge"]
-      ports_dns:
-        description: "External DNS port"
-        type: int
-        default: 53
-        needs: "network_mode=bridge"
       ports_ntp:
         description: "External NTP port"
         type: int
         default: 123
-        needs: "network_mode=bridge"
+        required: true
   swarm:
     vars:
-      swarm_enabled:
-        needs: "network_mode=bridge"
       swarm_placement_host:
         required: true
         optional: false
         needs: null
-      webpassword_secret_name:
-        description: "Docker Swarm secret name for admin password"
-        type: str
-        default: "pihole_webpassword"

Некоторые файлы не были показаны из-за большого количества измененных файлов