Sign inSign up

epicsoft/haproxy

By epicsoft

•Updated about 4 hours ago

Official HAProxy image with automatic hitless reload on config change and a reload-aware healthcheck

Image
Networking
Web servers
1

100K+

epicsoft/haproxy repository overview

⁠HAProxy

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.

⁠What this image adds

official imagethis image
Configuration changednothing happens until you send a SIGUSR2reloaded automatically, seamless - the old workers finish their connections
Broken configurationthe reload fails, HAProxy keeps the old configuration and nothing outside shows itthe reload is refused with HAProxy's own error messages, and the container turns unhealthy
Reload from a scriptdocker kill -s USR2 - no answer whether it workeddocker exec <container> haproxy-reload - HAProxy's messages and an exit code
Healthchecknonemaster 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.

⁠Versions

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.

TagBuilt on
latest, alpinehaproxy:alpine - the newest HAProxy line
3.4haproxy:3.4-alpine
3.2haproxy: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

⁠Quick start

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⁠.

⁠Reloading

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.

⁠Healthcheck

The image checks every 30 seconds:

  • the master CLI answers,
  • no reload has failed since the last successful one,
  • a current worker runs.

The reason for an unhealthy container is in docker inspect:

docker inspect --format '{{json .State.Health}}' haproxy

⁠Metrics

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.

⁠Environment variables

VariableDefaultMeaning
HAPROXY_CONFIG/usr/local/etc/haproxy/haproxy.cfgconfiguration file, or a directory - HAProxy then loads all its files in alphabetical order
HAPROXY_WATCH/usr/local/etc/haproxyfiles and directories, separated by spaces, whose changes trigger a reload; empty turns the automatic reload off
HAPROXY_WATCH_INTERVAL5seconds between the checksum compares that catch changes without inotify events
HAPROXY_MASTER_SOCKET/var/run/haproxy/master.sockmaster CLI socket for reload and healthcheck
HAPROXY_RELOAD_WEBHOOK_SUCCESS_URLemptyURL called (GET) after a successful reload by haproxy-reload
HAPROXY_RELOAD_WEBHOOK_FAILURE_URLemptyURL called (GET) after a refused or failed reload

⁠Migrating from the earlier image

Until 2026-10 this image restarted HAProxy on a change, logged through syslogd and ran as root. That is gone:

  • Logging: replace log /dev/log local0 ... with log stdout format raw local0 - there is no syslog socket any more.
  • User: HAProxy runs as 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.
  • Removed: 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_*).
  • A change is reloaded seamlessly now, without the restart that dropped connections.

⁠Reporting problems

Issues are welcome on GitLab⁠ and GitHub⁠. Please report a security problem as a confidential issue on GitLab instead of a public one.

⁠License

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.

Tag summary

Content type

Image

Digest

sha256:82c790073…

Size

22.1 MB

Last updated

about 4 hours ago

docker pull epicsoft/haproxy