Official HAProxy image with automatic hitless reload on config change and a reload-aware healthcheck
100K+
This Docker image is managed and kept up to date by epicsoft LLC.
The official HAProxy image with what it leaves to you: the configuration is
reloaded automatically and without dropping connections when it changes, haproxy-reload tells you whether a
reload worked, and the healthcheck turns unhealthy when a reload failed - instead of HAProxy silently serving the
old configuration.
alpine variants), platforms linux/amd64 and linux/arm64| official image | this image | |
|---|---|---|
| Configuration changed | nothing happens until you send a SIGUSR2 | reloaded automatically, seamless - the old workers finish their connections |
| Broken configuration | the reload fails, HAProxy keeps the old configuration and nothing outside shows it | the reload is refused with HAProxy's own error messages, and the container turns unhealthy |
| Reload from a script | docker kill -s USR2 - no answer whether it worked | docker exec <container> haproxy-reload - HAProxy's messages and an exit code |
| Healthcheck | none | master answers, a current worker runs, no failed reload |
Everything else is the official image as it is: HAProxy and its features (HTTP/3, ACME, Prometheus exporter, Lua),
master-worker mode, running as user haproxy. Logging goes straight to the container log with
log stdout format raw local0.
The tags follow the HAProxy lines of the official image and are rebuilt weekly, so each one keeps getting the newest HAProxy release of its line.
| Tag | Built on |
|---|---|
latest, alpine | haproxy:alpine - the newest HAProxy line |
3.4 | haproxy:3.4-alpine |
3.2 | haproxy:3.2-alpine - long-term support |
The HAProxy version an image really holds is written into it:
docker image inspect --format '{{index .Config.Labels "image.haproxy.version"}}' epicsoft/haproxy:3.2
Mount the directory with the configuration, not the file itself: editors and deployment tools usually replace a file instead of rewriting it, and a single-file mount keeps pointing at the old one.
docker run -d --name haproxy \
-p 80:80 \
-v /srv/haproxy:/usr/local/etc/haproxy:ro \
epicsoft/haproxy:3.2
/srv/haproxy/haproxy.cfg, the minimum with logging to the container log:
global
log stdout format raw local0
defaults
log global
mode http
option httplog
timeout connect 5s
timeout client 30s
timeout server 30s
frontend web
bind :80
default_backend app
backend app
server app1 app1:8080 check
A complete Compose example with three web servers and the statistics page: example/compose.yml.
Automatically: every change below /usr/local/etc/haproxy - the default of HAPROXY_WATCH - is checked with
haproxy -c and then reloaded through the master CLI. inotify reports a change right away; where no events arrive,
for example with a bind mount from Docker Desktop, a checksum compare every HAPROXY_WATCH_INTERVAL seconds catches
it.
[haproxy-watch] 2026-10-18T12:16:24+00:00 change found, reloading
Success=1
--
[NOTICE] (1) : Initializing new worker (55)
[NOTICE] (1) : Loading success.
[NOTICE] (1) : haproxy version is 3.4.7-8872a26
[WARNING] (1) : Former worker (16) exited with code 0 (Exit)
[haproxy-reload] 2026-10-18T12:16:24+00:00 reload done
By hand or from a deployment: haproxy-reload does the same and exits with 0 only when the new configuration
is running - an Ansible handler or a CI job can rely on it.
docker exec haproxy haproxy-reload
By signal: docker kill -s USR2 haproxy still works as in the official image. A failed reload sent this way is
caught by the healthcheck as well.
A broken configuration never takes HAProxy down: the reload is refused, HAProxy keeps serving the previous configuration, and the container is unhealthy until a reload succeeds.
The image checks every 30 seconds:
The reason for an unhealthy container is in docker inspect:
docker inspect --format '{{json .State.Health}}' haproxy
HAProxy brings its own Prometheus exporter; a frontend makes it reachable:
frontend metrics
bind :8405
http-request use-service prometheus-exporter if { path /metrics }
haproxy_process_start_time_seconds is the time the running configuration was loaded: every successful reload
starts a new worker, so the value moves with it - and stays where it is when a reload fails. The age of the running
configuration in PromQL:
time() - haproxy_process_start_time_seconds
A failed reload is not part of HAProxy's metrics; the healthcheck reports it.
| Variable | Default | Meaning |
|---|---|---|
HAPROXY_CONFIG | /usr/local/etc/haproxy/haproxy.cfg | configuration file, or a directory - HAProxy then loads all its files in alphabetical order |
HAPROXY_WATCH | /usr/local/etc/haproxy | files and directories, separated by spaces, whose changes trigger a reload; empty turns the automatic reload off |
HAPROXY_WATCH_INTERVAL | 5 | seconds between the checksum compares that catch changes without inotify events |
HAPROXY_MASTER_SOCKET | /var/run/haproxy/master.sock | master CLI socket for reload and healthcheck |
HAPROXY_RELOAD_WEBHOOK_SUCCESS_URL | empty | URL called (GET) after a successful reload by haproxy-reload |
HAPROXY_RELOAD_WEBHOOK_FAILURE_URL | empty | URL called (GET) after a refused or failed reload |
Until 2026-10 this image restarted HAProxy on a change, logged through syslogd and ran as root. That is gone:
log /dev/log local0 ... with log stdout format raw local0 - there is no syslog socket any more.haproxy, like in the official image. Files it reads must be readable for it; ports below
1024 work on current Docker, which allows them for unprivileged users in containers.OBSERVE_FILES, OBSERVE_FILES_CREATE, OBSERVE_COMMAND, OBSERVE_INTERVAL, HAPROXY_CONFIG_FILE
(now HAPROXY_CONFIG), HAPROXY_PID_FILE, HAPROXY_STARTUP_ARGS, HAPROXY_VALIDATE_ARGS and the Check_MK
integration (CHECK_MK_*).Issues are welcome on GitLab and GitHub. Please report a security problem as a confidential issue on GitLab instead of a public one.
MIT — Copyright (c) 2018-2026 epicsoft LLC / Alexander Schwarz
The MIT license applies only to files created by epicsoft LLC. Third-party software in the image — HAProxy, the
Alpine packages and the official haproxy image — remains under its own licenses.
Content type
Image
Digest
sha256:82c790073…
Size
22.1 MB
Last updated
about 4 hours ago
docker pull epicsoft/haproxy