Kubernetes operator that reconciles HAProxy configuration via the Dataplane API.
Designed for a GitOps workflow where infrastructure operators commit haproxy.cfg changes to a private GitHub repository. FluxCD watches the repo and syncs the config into a Kubernetes Secret. The operator detects changes, validates the configuration, and applies it to a production HAProxy load balancer — fully automated, with accept/reject feedback via Kubernetes Events.
┌─────────────────────────┐ poll ┌──────────────────────┐
│ GitHub Repo │◄────────────────────│ FluxCD │
│ (haproxy.cfg) │ GitRepository │ (K8s cluster) │
└─────────────────────────┘ │ Kustomization │
│ → K8s Secret │
└──────────┬───────────┘
│ watch
┌──────────▼───────────┐
│ haproxy-operator │
│ 1. Detect change │
│ 2. Validate config │
│ 3. Apply via DPAPI │
│ 4. Emit K8s Event │
└──────────┬───────────┘
│ mTLS (SPIRE)
┌──────────▼───────────┐
│ HAProxy LB │
│ (Dataplane API) │
└──────────────────────┘
- GitOps-native: FluxCD polls Git and syncs haproxy.cfg into a Kubernetes Secret
- One-way architecture: Clusters are not publicly accessible — Flux pulls from GitHub; no direct cluster-to-external communication is required
- Config validation: Pre-validates
haproxy.cfgvia the Dataplane APIonly_validateendpoint before applying - SPIFFE/SPIRE mTLS: Automatic workload identity and certificate rotation between K8s and the bare-metal load balancer
- Static TLS: Alternative to SPIRE — mount cert files from a pre-provisioned Secret
- Environment promotion:
latest(dev) →stable(production) using Flux Kustomize overlays - Kubernetes Events: Accept/reject status emitted as Events on the config Secret for observability
- Leader election: Safe multi-replica deployment with controller-runtime leader election
| Mode | Source | Use Case |
|---|---|---|
k8s (default) |
Kubernetes Secret | Production — Flux syncs config from Git into a Secret |
local |
File on disk | Development — watches a local haproxy.cfg file |
docker compose up --buildThis starts HAProxy with the Dataplane API, the operator in local mode, and sample backend services. Edit dev/external-haproxy-service/haproxy.cfg and the operator automatically validates and applies changes.
Verify:
curl http://localhost:8080 # Traffic through HAProxy
curl http://localhost:8404 # HAProxy stats| Flag / Env Var | Default | Description |
|---|---|---|
--mode / MODE |
k8s |
Run mode: k8s or local |
--namespace / WATCH_NAMESPACE |
haproxy-operator |
K8s namespace to watch |
--secret-name / SECRET_NAME |
haproxy-config |
Secret containing haproxy.cfg |
--secret-key / SECRET_KEY |
haproxy.cfg |
Key within the Secret |
--dataplane-url / DATAPLANE_URL |
https://haproxy:5555/v3 |
Dataplane API base URL |
--spire-socket / SPIRE_AGENT_SOCKET |
— | SPIRE Workload API socket |
--leader-elect / LEADER_ELECT |
false |
Enable leader election |
See charts/haproxy-operator/values.yaml for the full set of Helm values.
When SPIRE is enabled (spire.enabled=true), the operator obtains X.509 SVIDs from the local SPIRE Agent for mTLS with the Dataplane API. No manual certificate distribution required — both the operator pod and the HAProxy host authenticate via their SPIFFE identities.
When SPIRE is not enabled, the operator mounts TLS certificates from a Kubernetes Secret (haproxy-operator-tls). Pre-provision this Secret with ca.crt, tls.crt, and tls.key for mTLS communication with the Dataplane API.
├── main.go # Entrypoint
├── internal/
│ ├── config/ # Config loader, hasher, validator
│ ├── controller/ # K8s Secret reconciler
│ ├── haproxy/ # Dataplane API client (mTLS, basic auth)
│ ├── local/ # Local file-watching runner
│ ├── spire/ # SPIFFE/SPIRE Workload API integration
│ └── status/ # K8s Event reporter
├── charts/haproxy-operator/ # Helm chart (OCI → GHCR)
├── dev/ # Local development assets
├── .github/workflows/ # CI/CD (test, build, sign, scan, publish)
└── docker-compose.yaml # Local dev environment
- Go tests +
go veton every PR - Helm lint + kubeconform schema validation
- Docker image built, pushed to GHCR, signed with Cosign (keyless), scanned with Trivy
- Helm chart packaged as OCI, pushed to GHCR, signed with Cosign
- Release Please for automated semver and changelog