Unreleased documentation. Choose your installed release in
the version menu. Features described here may be absent from that release.
Generate configuration and files
Use these examples when writing your own configuration or auxiliary-file
templates. To add one setting to the bundled configuration, start with
Write your first template instead.
The examples run in your browser. They omit installation-specific credentials
and pod selectors, so use them as template examples rather than applying their
HAProxyTemplateConfig objects directly to a cluster. With Helm, put template
settings under controller.config; see the configuration reference
for a complete standalone object.
What you can template
| Template Type |
Use When |
haproxyConfig |
Main HAProxy configuration (frontends, backends, global settings) |
maps |
HAProxy lookup tables for host/path routing decisions |
files |
Auxiliary files like custom error pages |
sslCertificates |
TLS certificate files assembled from Kubernetes Secrets |
HAProxy Configuration
The main haproxyConfig template generates the complete HAProxy configuration file. This example loops over the sample Ingresses and emits a backend for each. Open Resources to add or edit an Ingress, then check the output. The backends have no servers yet; this example teaches template rendering, not a working route.
apiVersion: haproxy-haptic.org/v1alpha1
kind: HAProxyTemplateConfig
metadata:
name: haproxy-config-demo
spec:
watchedResources:
ingresses:
apiVersion: networking.k8s.io/v1
resources: ingresses
indexBy:
- metadata.name
maps:
host.map:
template: |
{%- for _, ingress := range resources.ingresses.List() %}
{%- for _, rule := range ingress.spec.rules %}
{{ rule.host }} {{ ingress.metadata.name }}
{%- end %}
{%- end %}
haproxyConfig:
template: |
global
log stdout len 4096 local0 info
daemon
maxconn 4096
defaults
mode http
timeout connect 5s
timeout client 50s
timeout server 50s
frontend http
bind *:80
use_backend %[req.hdr(host),lower,map({{ pathResolver.GetPath("host.map", "map") }})]
{%- for _, ingress := range resources.ingresses.List() %}
backend {{ ingress.metadata.name }}
balance roundrobin
{%- end %}
apiVersion: v1
kind: List
items:
- apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
spec:
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shop
port:
number: 80
- apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog
spec:
rules:
- host: blog.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: blog
port:
number: 80
Important
Whenever your HAProxy config references a map file, error file, certificate, or crt-list, use pathResolver.GetPath(filename, type) instead of a hard-coded path. The controller deploys these files to a configurable directory (set in spec.dataplane.mapsDir, sslCertsDir, generalStorageDir) and pathResolver knows where they live, so the path stays correct even if you reconfigure those directories.
HAProxy's http-response set-header directive adds a response header. For
example, http-response set-header X-Team storefront adds X-Team: storefront.
Try it in the frontend web section below.
Add a line to frontend web that sets the response header X-Example to hello. Find the new directive in the output.
apiVersion: haproxy-haptic.org/v1alpha1
kind: HAProxyTemplateConfig
metadata:
name: hsts-demo
spec:
haproxyConfig:
template: |
global
log stdout format raw local0
daemon
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend web
bind *:80
# TODO(you): add a line so every response carries X-Example: hello
default_backend app
backend app
server s1 127.0.0.1:8080 check
Peek at the solution
apiVersion: haproxy-haptic.org/v1alpha1
kind: HAProxyTemplateConfig
metadata:
name: hsts-demo
spec:
haproxyConfig:
template: |
global
log stdout format raw local0
daemon
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend web
bind *:80
http-response set-header X-Example hello
default_backend app
backend app
server s1 127.0.0.1:8080 check
Named and multiple defaults sections
Templates can emit multiple named defaults sections. A frontend, backend, or listen section selects one with from <name>. The bundled base library uses named profiles, including haptic-base; see reload-free routing before changing profiles used by dynamic backends.
Map files
Each maps entry renders one HAProxy lookup table. They're written to spec.dataplane.mapsDir (default /etc/haproxy/maps/) on the HAProxy pod. This template turns each Ingress host into a backend-name entry — switch to the maps tab to read the generated host.map.
apiVersion: haproxy-haptic.org/v1alpha1
kind: HAProxyTemplateConfig
metadata:
name: map-demo
spec:
watchedResources:
ingresses:
apiVersion: networking.k8s.io/v1
resources: ingresses
indexBy:
- metadata.name
maps:
host.map:
template: |
{%- for _, ingress := range resources.ingresses.List() %}
{%- for _, rule := range ingress.spec.rules %}
{%- if len(rule.http.paths) > 0 %}
{{ rule.host }} ing_{{ ingress.metadata.name }}
{%- end %}
{%- end %}
{%- end %}
haproxyConfig:
template: |
global
log stdout format raw local0
daemon
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http
bind *:80
use_backend %[req.hdr(host),lower,map({{ pathResolver.GetPath("host.map", "map") }})]
apiVersion: v1
kind: List
items:
- apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
spec:
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shop
port:
number: 80
- apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog
spec:
rules:
- host: blog.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: blog
port:
number: 80
General files
Auxiliary files like custom error pages. Written to spec.dataplane.generalStorageDir (default /etc/haproxy/general/). The errorfile directive points HAProxy at the rendered file — open the files tab to see 503.http.
apiVersion: haproxy-haptic.org/v1alpha1
kind: HAProxyTemplateConfig
metadata:
name: files-demo
spec:
files:
503.http:
template: |
HTTP/1.0 503 Service Unavailable
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>503 Service Unavailable</h1></body></html>
haproxyConfig:
template: |
global
log stdout format raw local0
daemon
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http
bind *:80
errorfile 503 {{ pathResolver.GetPath("503.http", "file") }}
default_backend web
backend web
server s1 10.0.0.1:8080 check
General-file changes trigger a reload by default. For a file consumed only by a
sidecar that reloads its own configuration, set reloadOnPush: false to update
the file without reloading HAProxy:
spec:
files:
vector.yaml:
reloadOnPush: false
template: |
sources: {}
Registering the file at render time takes the same flag as a fourth argument:
{%- var _, err = fileRegistry.Register("file", "spoa-hub-config.toml", content, false) %}
{% if err != nil %}{{ fail(err.Error()) }}{% end %}
SSL certificates
SSL/TLS certificate files are assembled from Kubernetes Secrets. Written to spec.dataplane.sslCertsDir (default /etc/haproxy/ssl/). This reads a TLS Secret and concatenates its certificate and key into one PEM — the certs tab shows the result.
apiVersion: haproxy-haptic.org/v1alpha1
kind: HAProxyTemplateConfig
metadata:
name: cert-demo
spec:
watchedResources:
secrets:
apiVersion: v1
resources: secrets
indexBy:
- metadata.namespace
- metadata.name
sslCertificates:
example-com.pem:
template: |
{%- var secret = resources.secrets.GetSingle("default", "example-com-tls") %}
{%- if secret != nil %}
{{ secret.data["tls.crt"] | b64decode() }}
{{ secret.data["tls.key"] | b64decode() }}
{%- end %}
haproxyConfig:
template: |
global
log stdout format raw local0
daemon
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend web
bind *:443 ssl crt {{ pathResolver.GetPath("example-com.pem", "cert") }}
default_backend app
backend app
server s1 10.0.0.1:8080 check
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
name: example-com-tls
namespace: default
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURUekNDQWplZ0F3SUJBZ0lVV3ZyRGg3bVB5ck5rclB2N1FjeWQ1cXBZVFZFd0RRWUpLb1pJaHZjTkFRRUwKQlFBd056RVVNQklHQTFVRUF3d0xaWGhoYlhCc1pTNWpiMjB4SHpBZEJnTlZCQW9NRmtoQlVGUkpReUJRYkdGNQpaM0p2ZFc1a0lFUmxiVzh3SGhjTk1qWXdOekE1TWpNd056QTJXaGNOTXpZd056QTJNak13TnpBMldqQTNNUlF3CkVnWURWUVFEREF0bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUNnd1dTRUZRVkVsRElGQnNZWGxuY205MWJtUWcKUkdWdGJ6Q0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUxtYXBnQlZTNERmQ29jcApNUk1ocnIxeG42M1RCL3plL2kxT3hQV1k5eUhmc0hOelZPakRUT054elE1SERVMVFBUFZXb2I0YmlKemZWbDF6Cm5qVCs4MkordXVUZWVCbWUxcFJhRUhyNjgvbWxCelAvM3V0NDBDNlJ1Y0xSbzVWYlVvd3d2WnpOVHJGbW1Jdk4KcDdXdVNsWDFhTFBSSENvRE0zYUtndU94MS9MdHl6TGw3eGtPdkRBa0ZoYmNWc0tVSUFzb01KaWliREYrdzBYZApXenJDUmZOSDdzMjNldTBDRDBnZk1lT0lTV3R5MU40SWRUT2NBcGU4aWpMNi80SkJYOG51NmFhOXMwd3JmMXhpCm9yeEhEV2dDMFpva21EMGlvZ0NYaWptNXFJUGZySnZ5NkMyNzgrRnErK2I3ZzR0dzlFdjlmS1YyeGJYUjdNVTQKTTNRaUZpVUNBd0VBQWFOVE1GRXdIUVlEVlIwT0JCWUVGSUQzOG51WmszaklHQVRVZWMzV3pwMi9tNmpxTUI4RwpBMVVkSXdRWU1CYUFGSUQzOG51WmszaklHQVRVZWMzV3pwMi9tNmpxTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3CkRRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFHQmFYa1JhcTRReEoxTDl2WHdnemlyWjR1dzltRzBWL1gzVkNtUDUKVXhicnJrQ3JiZzZEYURYRWpUTEk5bm92VVFmK2NaMWhPRDI0TDN4d1dvUHZ2Z25BNlBlR240c2F1Q0Z0WFNrSwp5RzZOemFrWmdjdHY0OHUzQnNLUDRJenZmTVRhZENNWmlyb2xMV0MrWWlDc1doSVRSR1RSd3JnVXlwN3JiTVgzCk9uNXpEYlU3MjU4RXhiN01NYlBvMlpJRWZZcUErKzIzVlZ6alBQamR4Yy81NjhLZTFPZUhKenR3SG5ENmk3WVAKM3NaTyt0dC83OU5TQlBUNk5TcUg2eWdGWUpCMWpYOWhYKzA1VHJzb010UnVUMmFsU1duY2VVOHJRd2dYalFLVQpiZnUrVE4xdnBrVjk0ZFZERnVKRFhhWFIyQ0ptUmVTM1prWDlJYWxNc1cvTHpwWT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzVtcVlBVlV1QTN3cUgKS1RFVElhNjljWit0MHdmODN2NHRUc1QxbVBjaDM3QnpjMVRvdzB6amNjME9SdzFOVUFEMVZxRytHNGljMzFaZApjNTQwL3ZOaWZycmszbmdabnRhVVdoQjYrdlA1cFFjei85N3JlTkF1a2JuQzBhT1ZXMUtNTUwyY3pVNnhacGlMCnphZTFya3BWOVdpejBSd3FBek4yaW9ManNkZnk3Y3N5NWU4WkRyd3dKQllXM0ZiQ2xDQUxLRENZb213eGZzTkYKM1ZzNndrWHpSKzdOdDNydEFnOUlIekhqaUVscmN0VGVDSFV6bkFLWHZJb3krditDUVYvSjd1bW12Yk5NSzM5YwpZcUs4Uncxb0F0R2FKSmc5SXFJQWw0bzV1YWlEMzZ5Yjh1Z3R1L1BoYXZ2bSs0T0xjUFJML1h5bGRzVzEwZXpGCk9ETjBJaFlsQWdNQkFBRUNnZ0VBRW4zcmN4WU1ienNKbi96RkpHeFRMaEcvZ0lDSmg3S3A3VmF2UGU3dkZHTm0KZjZJcWdBUlJTVW5oemIzYmYrdnNKSVZzbVBYQ1R5cmJQblZSK21LNldnSlpXWXNtdVJxL3Mwa2o0alRWa1BaVgp1T01SMFRFWXdNTUpHSFZ0a0dob1dZcFRvZWM4bzJVZTVyTG5OaTAydjhpekZWTk10SXpjR0QvbG1ZenpBSU53CkV0UFJRRHdsMks1NDFFckdZTjA1c2RyQmFWNkFFdjRFWHh4cldzVXJCK3k2cW1XQ1kvUDdSUHkwNzFCVHJnTmUKSkhYUnk5NnJOSE9DUHZYK1kzQWRYSGw4T01yMTV0M3IyMVVlMmpqVlltY29UT1pSTTVMSjN2emRRSEFESFV4ZQoyZUFORXJkWGNNdVgyUi9wK0IvNnBtUE1LVTJLT2JJeWlOK1p0Zm9ya3dLQmdRRHFLZ083Z1BqY0RIVGc4bEdaCk14Z282emErL1VaOUN2K2JMTzk2RzBzWlpkUEJpYjR0cStvMXRnSXlqWjZ5SHBzbTBpanRSZHhjZEtuQXlIcUcKNmRwU3pJbXlUQU9DV3JsbkFFY05XQitIeTR1cTVuMUY3M0VrSitiYi9saDRUbm94SmFSeEIweDM3QjJlRVhBcQppUkhjeGdyKzljOTU3ajVuSk5RWnJ2eE1id0tCZ1FESzZXZW9jcEdSeFoxM1ZUYUVrWERFL3ZQaVBpWVJBWEZjCmVQUmVrNnhZbVAxdmxDVUdpK2VPNGgyTW9ycEoxWVBlbDBzcHNDTCs2bk5ZV0Z2K3cyUjlsb0RqY1BOSnY0WGQKdkdGeFRzS0Zkdlp0ZkxodVpqeXljM01FeWRpckt3dmpuK2lieHo2NWZOdWtWcjFhSlExQnUvN2wycmJTSEsxbwpzSERiOENsNHF3S0JnQVhMb0dnRm15TW5FOFYxZWR1R3pqUkZEZ2ZRRU95TFZ5UXFDb3RGSGFpMVFuWnB5RkV0CkRoRGlQayt0L1oxKzhHd1hpM2ZENE41UTdOcWVtNW0zTS9ZVXBkdkowZFJxRm1pY015WDdabHhnQjBibGlYZ3YKb3VjNExaaUlSUHhGUlBUdWI1RjBrc250Q0JhZmE5MUJveldKbVVBU0tWNWxMUm8wYVNOeGwwRDFBb0dCQU1hVgpWV0J5OStwdE42WFJYTEN6VW1WSmkwL1JPUm9OaW05UTVRQW1rRmFKTEFkbU9qSkUrOU5Ia2xuUDdIZFVJbUhYCk9iVkw3NFFCMmU4TlVzTnJZTTdVVzhHOENpNFQ1YVJUdUIzWFVlS2l3WnYzb3R4UTdIaE5LclQyQWpuS3dERCsKai96ZEs1TUhFa0tzclZZcXl1V1pZbVo3L2M1MlNIUWJzZWhlQzRoUEFvR0FDNW9zY2NqQlpiK2xMOW9lMnp1WgpZQ0pDMjNzQnB2bnc2cmFBdXMzZXBFdDVXQnBxL0t0cmhEVjBvL1FaVU1JUEtOM3d3dUxyd01pM0VsMHNLand2CmtHNGxhRThhU1BGek16TjBVdTRXbEhCY01xT2N3UVpVUzIwM2o4eTl3SjVtdVllNU9FMzRUdndOQ3dtVFZXNkcKK3RkNElYaHgvMGpEbXZaSzNjRDd5V3M9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
Use b64decode to decode certificate data from Secrets.
Template snippets
Reusable template fragments are included via {{ render "snippet-name" }} — or {{ render_glob "pattern" }} to pull in every match at once. This config keeps each backend in its own snippet and stitches them into the config with render_glob, which renders matches in alphabetical order.
apiVersion: haproxy-haptic.org/v1alpha1
kind: HAProxyTemplateConfig
metadata:
name: snippet-demo
spec:
templateSnippets:
backend-api:
template: |
backend api
server s1 10.0.1.5:9000 check
backend-web:
template: |
backend web
server s1 10.0.0.1:8080 check
haproxyConfig:
template: |
global
log stdout format raw local0
daemon
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http
bind *:80
default_backend web
{{ render_glob "backend-*" }}
Include a single snippet in a template:
{{ render "backend-name" }}
Include all snippets matching a glob pattern (rendered in alphabetical order):
{{ render_glob "backend-*" }}
Pass local variables to rendered snippets with inherit_context:
{%- var service_name = "my-service" %}
{{ render "backend-servers" inherit_context }}
Use an incremental snippet when each
watched object can produce an independently cached fragment. The reference
describes bindings, tracked inputs, shared groups, and effects.
Post-processing
The haproxyConfig section supports a postProcessing list that transforms the rendered output before deployment. Post-processors run sequentially on the rendered configuration.
Available types:
| Type |
Description |
regex_replace |
Line-by-line regex find/replace (pattern and replace params) |
template |
Template transformation with access to the rendered output via the input variable (source param) |
The config below renders a __REGION__ marker, then runs two post-processors in order: a template step rewrites the marker to a value, and a regex_replace step renames the header. The haproxy.cfg tab shows the final, post-processed output.
apiVersion: haproxy-haptic.org/v1alpha1
kind: HAProxyTemplateConfig
metadata:
name: postproc-demo
spec:
haproxyConfig:
template: |
global
log stdout format raw local0
daemon
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http
bind *:80
http-response set-header X-Region __REGION__
default_backend web
backend web
server s1 10.0.0.1:8080 check
postProcessing:
- type: template
params:
source: |
{%- if strings_contains(input, "__REGION__") -%}
{{ replace(input, "__REGION__", "eu-west-1") }}
{%- else -%}
{{ input }}
{%- end -%}
- type: regex_replace
params:
pattern: "X-Region"
replace: "X-Deployment-Region"
The template post-processor receives the fully rendered output as the input variable and has access to the standard template functions (regexp, replace, len, tostring, etc.). Its output becomes the new rendered content.
Next steps
Read resource fields in your templates and add
validation tests for the generated output.