Kubernetes Deployment
Deploying OpenLDAP on Kubernetes is technically possible — but it's a decision that deserves careful thought. LDAP is the backbone of authentication for most organizations. Putting it on Kubernetes introduces risks that don't exist with simpler deployment models.The Hard Truth About LDAP on Kubernetes
LDAP is not a typical stateless microservice. It's a stateful, persistent, security-critical service that every other application depends on. This creates a fundamental tension with Kubernetes.Why Teams Hesitate
LDAP is the foundation of authentication. If LDAP goes down, nobody can log in — not to Kubernetes itself, not to any application that authenticates against it. Deploying the service that controls access to your infrastructure inside that infrastructure creates a circular dependency. Stateful workloads on Kubernetes are hard. Despite StatefulSets, Kubernetes was built for stateless services. Pod rescheduling, node failures, and storage reattachment can cause data corruption if not handled carefully. The MDB database that OpenLDAP uses requires consistent storage — not storage that might disappear during a node drain. The blast radius is massive. A misconfigured Kubernetes upgrade, a storage class issue, or a pod eviction at the wrong time doesn't just break one service — it breaks authentication for everything. Multi-master replication adds complexity. OpenLDAP's syncRepl protocol expects stable network identities and persistent connections. Kubernetes pod IPs change on restart. DNS resolution can be delayed. This can cause replication conflicts or split-brain scenarios.What the Industry Does
Most organizations that run LDAP in production keep it outside Kubernetes:- Dedicated VMs or bare metal with Docker / Docker Compose
- Managed directory services (AWS Directory Service, Azure AD DS)
- Standalone containers on long-lived hosts
When Kubernetes Makes Sense
There are legitimate reasons to run LDAP on Kubernetes:- All-in-one clusters where everything runs on K8s and no external infrastructure is available
- Development and staging environments that mirror production topology
- Edge deployments where a single K8s cluster is the only compute available
- Teams with strong K8s expertise who understand StatefulSet operations deeply
Helm Chart
Everything below is hand-written YAML, and it is worth reading once so you know what the chart is doing for you. For an actual install, use the published chart instead:
helm repo add vibhuvioio https://VibhuviOiO.github.io/openldap-helmchart
helm repo update
helm install openldap vibhuvioio/openldap \
--namespace directory --create-namespace \
--set auth.adminPassword='...' \
--set auth.configPassword='...' \
--set auth.replicationPassword='...'
It is on Artifact Hub, signed with GPG provenance, and it encodes the failure modes the rest of this page warns about:
| This page explains | The chart already does |
|---|---|
| StatefulSet with stable pod names | replicaCount providers, headless service |
| Provider-to-provider replication | replication.enabled over cn=config |
| PodDisruptionBudget | podDisruptionBudget.maxUnavailable: 1 by default |
| Pod anti-affinity | spreadAcrossNodes: true |
| Required passwords | fail at render if auth.adminPassword and auth.existingSecret are both unset |
| TLS | tls.enabled, tls.verifyClient, replication over StartTLS |
values.schema.json ships with the chart, so a mistyped enum fails the install rather than
starting a misconfigured directory.
Note: the chart refuses to render a cluster with no password rather than deploying one
with a default. That is deliberate - an OpenLDAP server with a predictable admin password is
worse than one that failed to start.
StatefulSet Deployment
Namespace
apiVersion: v1
kind: Namespace
metadata:
name: ldap
Secret
apiVersion: v1
kind: Secret
metadata:
name: openldap-credentials
namespace: ldap
type: Opaque
stringData:
LDAP_ADMIN_PASSWORD: "changeme"
LDAP_CONFIG_PASSWORD: "changeme"
Security: Use a secrets manager (Vault, Sealed Secrets, External Secrets Operator) instead of plain manifests in production.
StatefulSet — Single Node
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: openldap
namespace: ldap
spec:
serviceName: openldap
replicas: 1
selector:
matchLabels:
app: openldap
template:
metadata:
labels:
app: openldap
spec:
containers:
- name: openldap
image: vibhuvioio/openldap:latest
ports:
- containerPort: 389
name: ldap
- containerPort: 636
name: ldaps
env:
- name: LDAP_DOMAIN
value: "company.com"
- name: LDAP_ORGANIZATION
value: "Company Inc"
- name: INCLUDE_SCHEMAS
value: "cosine,inetorgperson,nis"
- name: ENABLE_MONITORING
value: "true"
envFrom:
- secretRef:
name: openldap-credentials
volumeMounts:
- name: ldap-data
mountPath: /var/lib/ldap
- name: ldap-config
mountPath: /etc/openldap/slapd.d
livenessProbe:
exec:
command:
- ldapsearch
- "-x"
- "-H"
- "ldap://localhost:389"
- "-b"
- ""
- "-s"
- "base"
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
readinessProbe:
exec:
command:
- ldapsearch
- "-x"
- "-H"
- "ldap://localhost:389"
- "-b"
- ""
- "-s"
- "base"
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1000m"
volumeClaimTemplates:
- metadata:
name: ldap-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 1Gi
- metadata:
name: ldap-config
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 100Mi
Service
apiVersion: v1
kind: Service
metadata:
name: openldap
namespace: ldap
spec:
type: ClusterIP
selector:
app: openldap
ports:
- name: ldap
port: 389
targetPort: 389
- name: ldaps
port: 636
targetPort: 636
Applications inside the cluster connect to openldap.ldap.svc.cluster.local:389.
External Access (Optional)
To expose LDAP outside the cluster:
apiVersion: v1
kind: Service
metadata:
name: openldap-external
namespace: ldap
spec:
type: NodePort
selector:
app: openldap
ports:
- name: ldap
port: 389
targetPort: 389
nodePort: 30389
Warning: Exposing LDAP externally without TLS sends passwords in plaintext. Always enable TLS for external access.
Multi-Master on Kubernetes
Running multi-master replication on Kubernetes requires a headless service for stable DNS names:
apiVersion: v1
kind: Service
metadata:
name: openldap-headless
namespace: ldap
spec:
clusterIP: None
selector:
app: openldap-cluster
ports:
- name: ldap
port: 389
Each pod gets a stable DNS name: openldap-cluster-0.openldap-headless.ldap.svc.cluster.local.
SERVER_ID and peers. This typically requires an init container or startup script that derives the server ID from the pod ordinal:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: openldap-cluster
namespace: ldap
spec:
serviceName: openldap-headless
replicas: 3
selector:
matchLabels:
app: openldap-cluster
template:
metadata:
labels:
app: openldap-cluster
spec:
containers:
- name: openldap
image: vibhuvioio/openldap:latest
ports:
- containerPort: 389
env:
- name: LDAP_DOMAIN
value: "company.com"
- name: LDAP_ORGANIZATION
value: "Company Inc"
- name: INCLUDE_SCHEMAS
value: "cosine,inetorgperson,nis"
- name: ENABLE_REPLICATION
value: "true"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
envFrom:
- secretRef:
name: openldap-credentials
command:
- /bin/sh
- -c
- |
# Derive SERVER_ID from pod ordinal (openldap-cluster-0 → 1)
ORDINAL=${POD_NAME##*-}
export SERVER_ID=$((ORDINAL + 1))
# Build peer list excluding self
PEERS=""
for i in 0 1 2; do
if [ "$i" != "$ORDINAL" ]; then
PEER="openldap-cluster-${i}.openldap-headless.ldap.svc.cluster.local"
PEERS="${PEERS:+${PEERS},}${PEER}"
fi
done
export REPLICATION_PEERS="$PEERS"
# Start OpenLDAP
exec /docker-entrypoint.sh
volumeMounts:
- name: ldap-data
mountPath: /var/lib/ldap
- name: ldap-config
mountPath: /etc/openldap/slapd.d
livenessProbe:
exec:
command: ["ldapsearch", "-x", "-H", "ldap://localhost:389", "-b", "", "-s", "base"]
initialDelaySeconds: 45
periodSeconds: 30
readinessProbe:
exec:
command: ["ldapsearch", "-x", "-H", "ldap://localhost:389", "-b", "", "-s", "base"]
initialDelaySeconds: 30
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1000m"
volumeClaimTemplates:
- metadata:
name: ldap-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 1Gi
- metadata:
name: ldap-config
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 100Mi
Known Risks on Kubernetes
| Risk | Impact | Mitigation |
|---|---|---|
| Pod eviction | LDAP unavailable during reschedule | Use PodDisruptionBudget with minAvailable: 2 |
| Storage loss | Data corruption or loss | Use reliable StorageClass with reclaimPolicy: Retain |
| DNS delay | Replication peers can't find each other | Use headless service, increase retry intervals |
| Node drain | All LDAP pods may land on same node | Use podAntiAffinity to spread across nodes |
| Circular dependency | K8s can't authenticate if LDAP is down | Consider external LDAP or bootstrap credentials |
Pod Disruption Budget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: openldap-pdb
namespace: ldap
spec:
minAvailable: 2
selector:
matchLabels:
app: openldap-cluster
Pod Anti-Affinity
Add to the StatefulSet pod spec to spread pods across nodes:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: openldap-cluster
topologyKey: kubernetes.io/hostname
The Recommended Pattern
For most organizations, the safest approach is:- 1 Run LDAP outside Kubernetes — Docker Compose on dedicated hosts
- 2 Create an ExternalName Service in Kubernetes pointing to the LDAP host
- 3 Applications inside K8s connect via the service name
apiVersion: v1
kind: Service
metadata:
name: openldap
namespace: ldap
spec:
type: ExternalName
externalName: ldap.internal.company.com
Or use Endpoints for IP-based external services:
apiVersion: v1
kind: Service
metadata:
name: openldap
namespace: ldap
spec:
ports:
- port: 389
---
apiVersion: v1
kind: Endpoints
metadata:
name: openldap
namespace: ldap
subsets:
- addresses:
- ip: 10.0.1.50
ports:
- port: 389
This gives Kubernetes workloads a stable service name (openldap.ldap.svc.cluster.local) while keeping LDAP safely outside the cluster.
Next Steps
- Single Node Deployment — simpler Docker Compose deployment
- Multi-Master Cluster — Docker Compose cluster
- Security — TLS and ACL hardening
- Monitoring — health checks and alerting