Concepts
This section explains how EasyHAProxy works under the hood — the mental models that help you understand why things are configured the way they are.
Service Discovery
EasyHAProxy runs a polling loop every N seconds (default 10, configurable via EASYHAPROXY_REFRESH_CONF). On each tick it:
- Queries your runtime — Docker API for containers/services, Kubernetes API for Ingress objects, or reads the static YAML file.
- Filters by label/annotation prefix — only resources that carry the
easyhaproxyprefix (or your customEASYHAPROXY_LABEL_PREFIX) are considered. - Builds an intermediate structure — an in-memory list of (host, port, backend) tuples called
easymapping. - Runs plugins — global plugins run once; domain plugins run once per host entry.
- Renders
haproxy.cfgfrom a Jinja2 template using theeasymappingdata. - Reloads HAProxy if the rendered config differs from the previous one (zero-downtime reload).
Discovery mode comparison
| Mode | Source polled | Auth required |
|---|---|---|
docker | Docker socket /var/run/docker.sock | Socket access |
swarm | Docker socket (Swarm API) | Socket access |
kubernetes | Kubernetes API server | RBAC (get/list on Ingress, Secret) |
static | File on disk | None |
Configuration Pipeline
Labels / Annotations / YAML file
↓
Discovery (Docker / Swarm / K8s / Static)
↓
parsed_object { IP → label map }
↓
[GLOBAL PLUGINS] ← executed once
↓
easymapping [ list of domain configs ]
↓
For each domain:
[DOMAIN PLUGINS] ← executed per domain
↓
PluginResult snippets collected
↓
Jinja2 template render → haproxy.cfg
↓
HAProxy reload (if config changed)
The key insight: you never write haproxy.cfg by hand. You express intent through labels/annotations/YAML, and EasyHAProxy translates that into valid HAProxy configuration on every discovery cycle.
Plugin Execution Model
Plugins are Python classes that implement PluginInterface. They are discovered automatically from:
/src/plugins/builtin/— bundled plugins/etc/easyhaproxy/plugins/— your custom plugins
Two execution types:
GLOBAL plugins
- Run once per discovery cycle, before any domain processing.
- Receive the full
parsed_object(all discovered services). - Use cases: cleanup tasks, global monitoring, DNS updates.
- Example:
cleanupplugin.
DOMAIN plugins
- Run once per discovered domain/host.
- Receive domain-specific context: domain name, port, label/annotation map.
- Return
PluginResultcontaining HAProxy config snippets to inject into the backend section. - Use cases: IP whitelisting, JWT validation, Cloudflare IP restoration, path blocking.
- Examples:
cloudflare,deny_pages,jwt_validator,ip_whitelist,fastcgi.
Configuration precedence (highest to lowest):
- Container labels / Ingress annotations (per-domain)
- Static YAML
plugins.configblock - Environment variables
EASYHAPROXY_PLUGIN_<NAME>_<KEY>
See the Plugin Developer Guide for how to build your own plugin.
SSL/TLS Termination Model
SSL termination happens at HAProxy, not in your backend containers.
Internet → HAProxy (TLS decryption) → Backend container (plain HTTP)
Your containers should only serve HTTP on their internal port. HAProxy handles all TLS on ports 80/443.
Certificate sources (evaluated in this order for each domain):
- ACME/Certbot — if
certbot=truelabel is set, EasyHAProxy runs Certbot HTTP-01 challenge automatically. - Volume-mounted PEM — files in
/etc/easyhaproxy/certs/haproxy/{domain}.pem. - Label-embedded PEM — base64 certificate in the
sslcertlabel.
Both ACME and manual certificates can coexist. Per domain, whichever source is configured takes precedence as shown above.
See SSL setup and ACME/Let's Encrypt for step-by-step configuration.