Overview
- In lab, we have 3 nodes – 1 for manager and 2 for worker node(s).
- You can follow the Docker Swarm Mode setup from here if not yet.
- In case, we will deploy haproxy service on worker-01 and worker-02 node.
Prerequisites
1. Rebuild HAProxy image
- HAProxy official image has no required directory “/var/lib/haproxy” so we cannot start HAProxy container yet.
- In case we do not have our own registry, so we need to rebuild haproxy image for each node(s) of Docker Swarm Cluster.
- Connect via SSH to each worker node(s) and rebuild haproxy image as the following command:
cat <<'EOF' | sudo tee Dockerfile
FROM haproxy:alpine
RUN mkdir /var/lib/haproxy
EOF
docker build -t my_haproxy .
- If we do not have public certificate, connect to manager node and we should start using with self-signed as the following:
mkdir -p /srv/haproxy-load-balancer/certs
cd /srv/haproxy-load-balancer/certs/
# Create DHPARAM file
openssl dhparam -out dhparam.pem 2048
# My domain name
mydomain=lab.local
# Generate a unique private key (KEY)
sudo openssl genrsa -out $mydomain.key 2048
# Generating a Certificate Signing Request (CSR)
sudo openssl req -new -key $mydomain.key -out $mydomain.csr
# Creating a Self-Signed Certificate (CRT)
openssl x509 -req -days 365 -in $mydomain.csr -signkey $mydomain.key -out $mydomain.crt
# Append KEY and CRT to mydomain.pem
cat $mydomain.key $mydomain.crt >> $mydomain.pem
chmod 400 *.{key,pem,csr,crt}
chmod 600 ../certs
- Create docker secret to store those self-signed certificates (.key, .pem)
docker secret create dhparam-pem /srv/haproxy-load-balancer/certs/dhparam.pem
docker secret create haproxy-certificate-pem /srv/haproxy-load-balancer/certs/lab.local.pem
docker secret ls
Deploy HAProxy
- Create HAProxy configuration file named “haproxy.cfg”
cd /srv/haproxy-load-balancer/
cat <<'EOF' | sudo tee haproxy.cfg
global
log fd@2 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 40000
user haproxy
group haproxy
stats socket /var/lib/haproxy/stats expose-fd listeners
master-worker
nbproc 1
nbthread 4
cpu-map auto:1/1-4 0-3
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dhparam.pem
resolvers docker
nameserver dns1 127.0.0.11:53
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold other 10s
hold refused 10s
hold nx 10s
hold timeout 10s
hold valid 10s
hold obsolete 10s
defaults
timeout connect 10s
log global
mode http
option httplog
option http-server-close
option forwardfor
option dontlognull
option redispatch
option contstats
frontend default
mode http
bind :80
redirect scheme https code 301 if !{ ssl_fc }
bind :443 ssl crt /etc/haproxy/haproxy-certificate.pem alpn h2,http/1.1
http-response set-header Strict-Transport-Security max-age=63072000
acl url_is_stats req.hdr(Host) -i stats.lab.local
use_backend haproxy if url_is_stats
backend haproxy
stats enable
stats uri /
stats refresh 15s
stats show-legends
stats show-node
stats auth admin:changeme
stats admin if TRUE
EOF
- Create HAProxy deployment YAML file named “docker-compose-haproxy.yml”
cd /srv/haproxy-load-balancer/
cat <<'EOF' | sudo tee docker-compose-haproxy.yml
version: '3.7'
networks:
backend-net:
external: true
configs:
haproxy_cfg:
file: ./haproxy.cfg
secrets:
dhparam-pem:
external: true
haproxy-certificate-pem:
external: true
services:
proxy:
image: my_haproxy
networks:
- backend-net
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
configs:
- source: haproxy_cfg
target: /usr/local/etc/haproxy/haproxy.cfg
mode: 0440
secrets:
- source: dhparam-pem
target: /etc/haproxy/dhparam.pem
mode: 0400
- source: haproxy-certificate-pem
target: /etc/haproxy/haproxy-certificate.pem
mode: 0400
dns:
- 127.0.0.11
deploy:
mode: replicated
replicas: ${HAPROXY_REPLICA:-2}
placement:
constraints:
- node.role == worker
EOF
- Create overlay network backend application
docker network create --attachable --driver overlay backend-net
- Start deploy with stack named “haproxy”
cd /srv/haproxy-load-balancer/
docker stack deploy -c docker-compose-haproxy.yml haproxy
- To verify the deployment status
docker stack ls
docker service ls
docker service logs -ft haproxy_proxy
- You should be able to access HAProxy stats by url: https://stats.lab.local that stats.lab.local point to worker-01 & worker-02 IP address

Demonstration with Whoami
- Create deployment YMAL file named “docker-compose-whoami.yml”
mkdir /srv/whoami
cd /srv/whoami/
cat <<'EOF' | sudo tee docker-compose-whoami.yml
version: '3.7'
networks:
backend-net:
external: true
services:
app:
image: containous/whoami:latest
networks:
- backend-net
deploy:
mode: replicated
replicas: 4
endpoint_mode: dnsrr
placement:
constraints:
- node.role == worker
EOF
- Start deploying with stack named “whoami”
docker stack deploy -c docker-compose-whoami.yml whoami
- Update “haproxy.cfg” configuration for Whoami
cd /srv/haproxy-load-balancer/
vim haproxy.cfg
--> Add new ACL, USE_BACKEND and BACKEND name
frontend default
-----
acl url_is_whoami req.hdr(Host) -i whoammi.lab.local
use_backend whoami if url_is_whoami
-----
backend whoami
balance roundrobin
server-template whoami_app- 4 whoami_app:80 check resolvers docker init-addr libc,none
- After save and exit, then need to redeploy HAProxy again as the following:
cd /srv/haproxy-load-balancer/
docker stack rm haproxy
docker stack deploy -c docker-compose-haproxy.yml haproxy
- Now you should be able to access whoami application by URL: https://whoami.lab.local which this name will resolve into worker-01 & worker-02 IP address. Here is the result

- Here is the HAProxy stats look like:
