Creates a docker swarm cluster. With portainer and haproxy
Go to file
Andrea Dell'Amico 019e66b120
haproxy template: add support for JWT auth/authz, CORS, robots.txt, bots mitigation.
2026-08-18 17:33:59 +02:00
.vscode Run portainer without haproxy. 2023-05-26 16:27:39 +02:00
defaults haproxy template: add support for JWT auth/authz, CORS, robots.txt, bots mitigation. 2026-08-18 17:33:59 +02:00
handlers Remove the handler for cAdvisor. 2023-05-29 14:17:57 +02:00
meta Run portainer without haproxy. 2023-05-26 16:27:39 +02:00
tasks cadvisor moved into the 'docker' role. 2023-05-29 13:39:12 +02:00
templates haproxy template: add support for JWT auth/authz, CORS, robots.txt, bots mitigation. 2026-08-18 17:33:59 +02:00
tests Initial commit 2020-09-24 16:01:51 +02:00
vars Support plain http haproxy listener. 2023-10-06 16:08:01 +02:00
.gitignore Initial commit 2020-09-24 16:01:51 +02:00
LICENSE Initial commit 2020-09-24 16:01:51 +02:00
README.md haproxy template: add support for JWT auth/authz, CORS, robots.txt, bots mitigation. 2026-08-18 17:33:59 +02:00

README.md

Role Name

A role that configures a docker swarm cluster, adding Portainer CE and HAPROXY

Role Variables

The most important variables are listed below:

docker_swarm_cluster_haproxy_install: true
# mesh, keepalive, global
docker_swarm_haproxy_installation_type: 'keepalive'
docker_swarm_haproxy_networks:
  - '{{ docker_swarm_portainer_network }}'
#
docker_swarm_expose_api_via_haproxy: false
docker_swarm_expose_api_hostname: 'swarm.example.com'
# Portainer
docker_swarm_cluster_portainer_install: true
docker_swarm_portainer_hostname: 'portainer-swarm.example.com'
docker_swarm_portainer_additional_constraints: []
#  - 'node.hostname != docker01'
docker_swarm_portainer_nfs_volume: false
docker_swarm_portainer_nfs_type: 'nfs4'
docker_swarm_portainer_nfs_o: 'nfsvers=4,addr=127.0.0.1,rw,nolock,soft'
docker_swarm_portainer_nfs_device: ':/tmp'

docker_swarm_portainer_network: 'agent_network'
docker_swarm_portainer_http_port: '9000'
docker_swarm_portainer_service_port: '8000'

docker_swarm_haproxy_additional_services: []
#  - { acl_name: 'service', acl_rule: 'hdr_dom(host) -i service.example.com', service_name: 'service-', service_replica_num: '1', service_port: '9999', service_overlay_network: 'service-network', http_redirect_to_https: True }

HAPROXY configuration

docker_swarm_haproxy_plain_http_global_acl_rules allows the definition of arbitrary acl with the corresponding http-request rules, for the plain http listener docker_swarm_haproxy_global_acl_rules allows the definition of arbitrary acl with the corresponding http-request rules, for the https listener

haproxy_docker_swarm_additional_services is the dictionary that contains the backends definitions and their acls

A plain http listener can be defined, using the following variables. The syntax of haproxy_docker_swarm_plain_http_services is identical to the haproxy_docker_swarm_additional_services one.

haproxy_docker_swarm_plain_http_listener: true
haproxy_docker_swarm_plain_http_port: 8080
haproxy_docker_swarm_plain_http_services: []

Optional HAProxy features

The generated haproxy.cfg can serve robots.txt, deny blacklisted user agents, apply a per-source flood control with a browser-validation challenge, handle CORS and enforce OIDC/JWT authentication. Everything is off by default; see defaults/main.yml for the variables and the per-service opt-in fields.

These features need files, and on Swarm HAProxy runs in a container. The role only writes the configuration: getting the files in place is a deployment decision, and there are three ways to do it.

  1. Through the directory that is already mounted. With haproxy_docker_mount_conf_file: true the haproxy role bind-mounts /etc/haproxy on the manager node to /usr/local/etc/haproxy inside the container. Anything rendered under /etc/haproxy/static/ and /etc/haproxy/lua/ is therefore visible to HAProxy with no additional volume, and an in-place reload picks it up without recreating the service. The path defaults in this role assume exactly this layout. It covers robots.txt, the user-agent blacklists and cors.lua.

  2. Docker configs / secrets. Cleaner distribution (the manager pushes them, nothing to manage per node) but config objects are immutable: changing one means creating a new object and updating the service, i.e. a rolling restart instead of a reload. Reasonable for files that never change, awkward for blacklists. Use a secret for the stats password if the config file is moved to a config object, since config objects are not secret; HAProxy expands ${VARIABLE} from its environment, so a small entrypoint wrapper that exports the secret files content is enough.

  3. A custom image. Required — not merely convenient — for the OIDC scripts: they require cjson, socket, ssl and ltn12, and the stock haproxytech/haproxy-debian image ships HAProxy built with Lua support but not a single Lua module, which cannot be installed at run time. The build itself is trivial, since that image is Debian based and the Debian/Ubuntu module packages (lua-cjson, lua-socket, lua-sec) install for every Lua ABI from 5.1 to 5.4, so they cover whatever the image links. What it costs is owning an image build and publish pipeline tied to HAProxy version bumps. cors.lua needs no modules and works in the stock image.

Recommended split: option 1 for the static files and CORS, and either a custom image or a separate oauth2-proxy service for OIDC. oauth2-proxy avoids the image pipeline altogether and does not depend on the Lua code path, which has never run in production.

Dependencies

  • docker
  • keepalived, when haproxy is installed in HA host mode

License

EUPL-1.2

Author Information

Andrea DellAmico, andrea.dellamico@isti.cnr.it