HAProxy running in Docker with dynamic configuration is a game-changer, and the most surprising thing is how little state it actually needs to maintain locally.
Let’s see it in action. Imagine you have a few backend services, say api-service running on ports 8001, 8002, and 8003. We want HAProxy to load balance across them, and we want to be able to add or remove these backends without restarting HAProxy.
First, we need a HAProxy configuration file. This isn’t the dynamic part yet, but it sets up the basics and points to where the dynamic configuration will live.
global
log stdout local0
daemon
defaults
mode http
timeout connect 5s
timeout client 50s
timeout server 50s
frontend http_frontend
bind *:80
http-request use-service http if { path -i /api }
default_backend api_backend
backend api_backend
balance roundrobin
# This is where the magic happens: load configuration from a file
# that can be updated without reloading HAProxy.
server-template api 10 maxconn 32 check
# We'll populate this with actual server entries dynamically.
Now, the dynamic part. HAProxy can be instructed to watch a directory for configuration snippets. When a file in that directory changes, HAProxy will pick up the changes.
Let’s create a directory for these dynamic configuration snippets: /usr/local/etc/haproxy/conf.d/.
Inside this directory, we’ll create a file for our backend servers. Let’s call it api.cfg.
# /usr/local/etc/haproxy/conf.d/api.cfg
server api-server-1 192.168.1.10:8001 check
server api-server-2 192.168.1.10:8002 check
server api-server-3 192.168.1.10:8003 check
To make HAProxy watch this directory, we modify the api_backend section in our main haproxy.cfg:
backend api_backend
balance roundrobin
server-template api 10 maxconn 32 check
# Add this line to enable dynamic configuration loading
config-dir /usr/local/etc/haproxy.conf.d
When HAProxy starts, it will read the main config file, then it will scan the config-dir. It finds api.cfg and parses the server entries within it. If we later add a new server, say 192.168.1.10:8004, we just add this line to api.cfg:
server api-server-4 192.168.1.10:8004 check
HAProxy, by default, checks for changes every 5 seconds. So, after a few seconds, it will detect the new file content, parse it, and seamlessly add api-server-4 to the api_backend pool. No reload, no downtime.
Here’s how you’d run this in Docker. We’ll use a Dockerfile to build our HAProxy image.
FROM haproxy:2.8
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
COPY conf.d/ /usr/local/etc/haproxy/conf.d/
EXPOSE 80
CMD ["haproxy", "-f", "/usr/local/etc/haproxy/haproxy.cfg"]
And the docker-compose.yml to orchestrate it:
version: '3.8'
services:
haproxy:
build: .
ports:
- "80:80"
volumes:
# Mount a volume for dynamic configuration so we can change it from the host
- ./conf.d:/usr/local/etc/haproxy/conf.d:ro # Read-only mount is sufficient if changes are external
networks:
- app-network
networks:
app-network:
driver: bridge
With this setup, you can edit conf.d/api.cfg on your host machine, and HAProxy inside the container will automatically pick up the changes. This is incredibly powerful for managing ephemeral services or scaling backends without service interruption. The server-template directive is key here; it tells HAProxy to expect a certain number of servers and then dynamically populates them from the config-dir. HAProxy doesn’t need to know the exact list of servers at startup if they are managed dynamically.
The real magic is how HAProxy handles configuration updates. It doesn’t re-parse the entire configuration file. Instead, it specifically watches the config-dir for changes to the files it references. When a change is detected, it only processes the modified or new files, merging them into the running configuration. This is why it’s so fast and doesn’t cause downtime. It’s designed to be resilient to external configuration management.
A common pitfall is forgetting to make the config-dir accessible to HAProxy. If you’re mounting it via Docker volumes, ensure the path inside the container matches what’s in your haproxy.cfg. Also, HAProxy typically checks for changes every 5 seconds by default, but this interval can be adjusted.
The next step in mastering HAProxy is understanding how to leverage its built-in statistics socket for real-time monitoring and control of your running configuration.