compose.yaml.j2 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. ---
  2. services:
  3. {{ service_name }}:
  4. image: docker.io/pihole/pihole:2025.11.1
  5. {#
  6. If not in swarm mode, check whether container_name is set and apply restart policy,
  7. else swarm mode handles restarts via deploy.restart_policy
  8. #}
  9. {% if not swarm_enabled %}
  10. restart: {{ restart_policy }}
  11. {% if container_name %}
  12. container_name: {{ container_name }}
  13. {% endif %}
  14. {% endif %}
  15. {#
  16. Set container hostname (Pi-hole requires this for proper operation)
  17. #}
  18. {% if container_hostname %}
  19. hostname: {{ container_hostname }}
  20. {% endif %}
  21. {#
  22. Environment variables for Pi-hole configuration
  23. - TZ: Timezone for proper log rotation
  24. - PIHOLE_UID/GID: User/group for file permissions
  25. - WEBPASSWORD: Admin password (from env file in compose mode, from secret in swarm mode)
  26. - FTLCONF_dns_listeningMode: In bridge mode, listen on all interfaces
  27. #}
  28. environment:
  29. - TZ={{ container_timezone }}
  30. - PIHOLE_UID={{ user_uid }}
  31. - PIHOLE_GID={{ user_gid }}
  32. {% if swarm_enabled %}
  33. - WEBPASSWORD_FILE={{ service_name }}_webpassword
  34. {% else %}
  35. - FTLCONF_webserver_api_password=${WEBPASSWORD}
  36. {% endif %}
  37. {% if network_mode == 'bridge' %}
  38. - FTLCONF_dns_listeningMode=all
  39. {% endif %}
  40. {#
  41. Network configuration based on network_mode:
  42. - 'host': Use host networking (direct access to host network stack, not supported in Swarm)
  43. - 'bridge': Custom bridge network (default Docker networking)
  44. - 'macvlan': Container gets its own MAC/IP on physical network (requires external macvlan network setup in Swarm)
  45. - '' (empty): Uses Docker's default bridge network
  46. When traefik is enabled, always add traefik network
  47. #}
  48. {% if network_mode == 'host' %}
  49. network_mode: host
  50. {% elif network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %}
  51. networks:
  52. {% if traefik_enabled %}
  53. {{ traefik_network }}:
  54. {% endif %}
  55. {% if network_mode == 'macvlan' %}
  56. {{ network_name }}:
  57. ipv4_address: {{ network_macvlan_ipv4_address }}
  58. {% elif network_mode == 'bridge' %}
  59. {{ network_name }}:
  60. {% endif %}
  61. {% endif %}
  62. {#
  63. Port mappings when in bridge mode (or empty, which defaults to bridge)
  64. - HTTP/HTTPS: Web interface (only if Traefik is disabled)
  65. - DNS: Port 53 TCP/UDP (always exposed, even with Traefik, since DNS can't be proxied)
  66. - NTP: Port 123 UDP (network time protocol, always exposed)
  67. Note: Swarm mode uses 'host' mode for port publishing to avoid port conflicts
  68. Note: Host and macvlan modes don't need port mappings (direct network access)
  69. #}
  70. {% if network_mode == '' or network_mode == 'bridge' or traefik_enabled %}
  71. ports:
  72. {% if not traefik_enabled %}
  73. {% if swarm_enabled %}
  74. - target: 80
  75. published: {{ ports_http }}
  76. protocol: tcp
  77. mode: host
  78. - target: 443
  79. published: {{ ports_https }}
  80. protocol: tcp
  81. mode: host
  82. {% else %}
  83. - "{{ ports_http }}:80/tcp"
  84. - "{{ ports_https }}:443/tcp"
  85. {% endif %}
  86. {% endif %}
  87. {% if swarm_enabled %}
  88. - target: 53
  89. published: {{ ports_dns }}
  90. protocol: tcp
  91. mode: host
  92. - target: 53
  93. published: {{ ports_dns }}
  94. protocol: udp
  95. mode: host
  96. - target: 123
  97. published: {{ ports_ntp }}
  98. protocol: udp
  99. mode: host
  100. {% else %}
  101. - "{{ ports_dns }}:53/tcp"
  102. - "{{ ports_dns }}:53/udp"
  103. - "{{ ports_ntp }}:123/udp"
  104. {% endif %}
  105. {% endif %}
  106. {#
  107. Volume configuration for persistent data
  108. - When volume_mode is 'mount': bind mount from host path
  109. - When volume_mode is 'local', 'nfs', or empty: use docker-managed volumes
  110. #}
  111. volumes:
  112. {% if volume_mode == 'mount' %}
  113. - {{ volume_mount_path }}/dnsmasq:/etc/dnsmasq.d:rw
  114. - {{ volume_mount_path }}/pihole:/etc/pihole:rw
  115. {% else %}
  116. - {{ service_name }}-dnsmasq:/etc/dnsmasq.d
  117. - {{ service_name }}-pihole:/etc/pihole
  118. {% endif %}
  119. {#
  120. Required capabilities:
  121. - NET_ADMIN: For DHCP and routing table operations
  122. - SYS_TIME: For NTP functionality
  123. #}
  124. cap_add:
  125. - NET_ADMIN
  126. - SYS_TIME
  127. {#
  128. When swarm_enabled is set, use Docker secrets for admin password
  129. #}
  130. {% if swarm_enabled %}
  131. secrets:
  132. - {{ service_name }}_webpassword
  133. {% endif %}
  134. {#
  135. Deploy configuration for Swarm mode:
  136. - Single replica with node placement constraint (Pi-hole doesn't support multi-replica)
  137. - Multiple instances would conflict with DNS/DHCP services
  138. - Traefik: Labels for reverse proxy integration (Swarm mode)
  139. Note: For macvlan in Swarm, create config-only networks on each node first, then use --scope swarm
  140. #}
  141. {% if swarm_enabled %}
  142. deploy:
  143. mode: replicated
  144. replicas: 1
  145. placement:
  146. constraints:
  147. - node.hostname == {{ swarm_placement_host }}
  148. restart_policy:
  149. condition: on-failure
  150. {#
  151. When traefik_enabled is set in swarm mode, add traefik labels
  152. (optionally enable TLS if traefik_tls_enabled is set)
  153. #}
  154. {% if traefik_enabled %}
  155. labels:
  156. - traefik.enable=true
  157. - traefik.docker.network={{ traefik_network }}
  158. - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
  159. - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
  160. - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
  161. - traefik.http.routers.{{ service_name }}-http.entrypoints=web
  162. {% if traefik_tls_enabled %}
  163. - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
  164. - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
  165. - traefik.http.routers.{{ service_name }}-https.entrypoints=websecure
  166. - traefik.http.routers.{{ service_name }}-https.tls=true
  167. - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
  168. {% endif %}
  169. {% endif %}
  170. {% endif %}
  171. {#
  172. When traefik_enabled is set, and not running in swarm mode, add traefik labels
  173. (optionally enable TLS if traefik_tls_enabled is set)
  174. #}
  175. {% if traefik_enabled and not swarm_enabled %}
  176. labels:
  177. - traefik.enable=true
  178. - traefik.docker.network={{ traefik_network }}
  179. - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
  180. - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
  181. - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
  182. - traefik.http.routers.{{ service_name }}-http.entrypoints=web
  183. {% if traefik_tls_enabled %}
  184. - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
  185. - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
  186. - traefik.http.routers.{{ service_name }}-https.entrypoints=websecure
  187. - traefik.http.routers.{{ service_name }}-https.tls=true
  188. - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
  189. {% endif %}
  190. {% endif %}
  191. {#
  192. When swarm_enabled is set, define Docker secret for admin password
  193. #}
  194. {% if swarm_enabled %}
  195. secrets:
  196. {{ service_name }}_webpassword:
  197. file: ./.env.secret.webpassword
  198. {% endif %}
  199. {#
  200. Volume definitions:
  201. - When volume_mode is 'local' (default): use docker-managed local volumes
  202. - When volume_mode is 'nfs': configure NFS-backed volumes
  203. - When volume_mode is 'mount': no volume definition needed (bind mounts used directly)
  204. #}
  205. {% if volume_mode == 'local' %}
  206. volumes:
  207. {{ service_name }}-dnsmasq:
  208. driver: local
  209. {{ service_name }}-pihole:
  210. driver: local
  211. {% elif volume_mode == 'nfs' %}
  212. volumes:
  213. {{ service_name }}-dnsmasq:
  214. driver: local
  215. driver_opts:
  216. type: nfs
  217. o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
  218. device: ":{{ volume_nfs_path }}/dnsmasq"
  219. {{ service_name }}-pihole:
  220. driver: local
  221. driver_opts:
  222. type: nfs
  223. o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
  224. device: ":{{ volume_nfs_path }}/pihole"
  225. {% endif %}
  226. {#
  227. Network definitions (only when needed):
  228. - When network_mode is empty: no definition needed (uses Docker's default bridge)
  229. - When network_mode is 'bridge': define custom bridge network
  230. - When network_mode is 'macvlan': configure macvlan with static IP (for Compose mode)
  231. Note: In Swarm mode, macvlan networks must be created manually with config-only networks on each node
  232. - When swarm_enabled: use overlay network for multi-host communication
  233. - Traefik network: always external (managed by Traefik)
  234. #}
  235. {% if network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %}
  236. networks:
  237. {% if network_mode == 'bridge' or network_mode == 'macvlan' %}
  238. {{ network_name }}:
  239. {% if network_external %}
  240. external: true
  241. {% else %}
  242. {% if network_mode == 'macvlan' %}
  243. driver: macvlan
  244. driver_opts:
  245. parent: {{ network_macvlan_parent_interface }}
  246. ipam:
  247. config:
  248. - subnet: {{ network_macvlan_subnet }}
  249. gateway: {{ network_macvlan_gateway }}
  250. name: {{ network_name }}
  251. {% elif swarm_enabled %}
  252. driver: overlay
  253. attachable: true
  254. {% else %}
  255. driver: bridge
  256. {% endif %}
  257. {% endif %}
  258. {% endif %}
  259. {% if traefik_enabled %}
  260. {{ traefik_network }}:
  261. external: true
  262. {% endif %}
  263. {% endif %}