Why Run Consul on Kubernetes?
Kubernetes has its own basic service discovery (via DNS and Services), but it lacks several capabilities that Consul provides out of the box: cross-cluster service discovery, mTLS service mesh, health-check driven routing, and integration with non-Kubernetes services. Running Consul on Kubernetes lets you bridge K8s workloads with VMs, bare metal, or other clusters in a unified control plane.
Prerequisites
- A running Kubernetes cluster (v1.25+)
kubectlconfigured and pointing at your cluster- Helm v3 installed
- At least 3 nodes for server pods (to maintain quorum)
Installing Consul via Helm
Add the HashiCorp Helm Repository
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
Create a values.yaml File
Start with a minimal but production-oriented configuration:
global:
name: consul
datacenter: dc1
tls:
enabled: true
acls:
manageSystemACLs: true
server:
replicas: 3
storage: 10Gi
client:
enabled: true
connectInject:
enabled: true
ui:
enabled: true
service:
type: LoadBalancer
Install the Chart
helm install consul hashicorp/consul \
--namespace consul \
--create-namespace \
-f values.yaml
Wait for all pods to reach Running status:
kubectl get pods -n consul -w
How the Helm Chart Works
The chart deploys:
- Server StatefulSet — 3 (or more) Consul server pods with persistent volumes
- Client DaemonSet — one Consul client pod per K8s node, handling health checks and local service registration
- Connect Inject Controller — a mutating admission webhook that automatically injects Envoy sidecar containers into annotated pods
- Controller — syncs Consul CRDs (custom resource definitions) to Consul config entries
Automatically Injecting Sidecars
To enable the Consul service mesh for a Kubernetes deployment, add a single annotation:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
template:
metadata:
annotations:
consul.hashicorp.com/connect-inject: "true"
spec:
containers:
- name: web
image: myapp/web:latest
ports:
- containerPort: 8080
The webhook injects an Envoy sidecar and an init container that configures the proxy — your application container doesn't need any modification.
Service Sync: Bridging K8s and Non-K8s Services
Enable syncCatalog in your Helm values to bidirectionally sync services between Kubernetes and Consul:
syncCatalog:
enabled: true
toConsul: true
toK8S: true
With this enabled:
- Kubernetes Services appear in Consul's catalog automatically
- Services registered in Consul appear as Kubernetes Services, making them accessible via
kube-dns
This is the key to connecting Kubernetes workloads with legacy VMs or external databases registered in Consul.
Using CRDs for Config Entries
The Consul Helm chart installs CRDs that let you manage Consul config entries as native Kubernetes resources:
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceIntentions
metadata:
name: web-to-api
spec:
destination:
name: api
sources:
- name: web
action: allow
Apply this with kubectl apply -f intentions.yaml and Consul will enforce it across your mesh.
Integrating Consul with Vault on Kubernetes
For secrets management, pair Consul with HashiCorp Vault using the Vault Agent Injector or the Vault Secrets Operator. Vault can act as Consul's certificate authority, rotating mTLS certificates automatically and storing ACL bootstrap tokens securely.
Troubleshooting Tips
- Sidecar not injecting? Check that the namespace has the
consul.hashicorp.com/connect-injectlabel and that the pod annotation is correct. - Services not syncing? Verify the sync catalog RBAC permissions and check the controller logs:
kubectl logs -n consul -l component=sync-catalog. - TLS errors? Ensure your Consul clients and servers all use certificates from the same CA. Re-run
consul tls cert createif needed.
Summary
The Consul Helm chart makes it straightforward to deploy a production-grade Consul cluster on Kubernetes with TLS, ACLs, and Connect injection enabled from day one. The real power comes from the bidirectional service sync and CRD-based management, which let you treat Consul configuration as code alongside your Kubernetes manifests.