Amazon EKS + DocumentDB (Terraform)

August 11, 2026 · View on GitHub

Terraform stack that creates a basic Kubernetes cluster (EKS) and a managed MongoDB-compatible database (Amazon DocumentDB) on AWS, wired together so Kerberos Hub can be installed on it straight away.

It is primarily meant as a reproducible test environment for the DocumentDB support in the hub helm chart, in particular the mongodb.tls.* values that mount the Amazon RDS certificate authority bundle. It is deliberately small and cheap, not a hardened production landing zone.

What it creates

flowchart LR
    subgraph VPC["VPC (10.20.0.0/16)"]
        subgraph Public["Public subnets"]
            NAT[NAT gateway]
            LB[Load balancers]
        end
        subgraph Private["Private subnets"]
            NODES[EKS managed node group]
            DOCDB[(DocumentDB cluster<br/>TLS enforced)]
        end
    end
    EKSCP[EKS control plane] --- NODES
    NODES -- "27017 / TLS" --> DOCDB
    NODES --> NAT
ComponentDetails
VPCPublic + private subnets across 3 availability zones, internet gateway, NAT gateway
EKSManaged control plane, one managed node group, coredns, kube-proxy, vpc-cni, eks-pod-identity-agent and aws-ebs-csi-driver add-ons (IRSA role included)
DocumentDBCluster + instances in the private subnets, encryption at rest (KMS) and in transit (tls=enabled), subnet group, cluster parameter group
SecurityA dedicated security group that only allows port 27017 from the EKS worker node security group (plus any extra CIDRs you pass in)

Important

DocumentDB has no public endpoint. It can only be reached from inside the VPC, which is why the workloads that talk to it must run on this cluster (or you must tunnel through a bastion host / VPN).

Warning

This stack costs money while it exists (EKS control plane, NAT gateway, EC2 nodes, DocumentDB instances and storage). Run terraform destroy when you are done.

Prerequisites

  • Terraform >= 1.5
  • AWS CLI v2, authenticated with permissions to create VPC, EKS, IAM and DocumentDB resources
  • kubectl and helm

Usage

cd deployment/modules/amazon-eks-documentdb

cp terraform.tfvars.example terraform.tfvars
$EDITOR terraform.tfvars

terraform init
terraform plan
terraform apply

Creating the cluster and the database takes a while (EKS and DocumentDB are both slow to provision).

Replacing the VPC

AWS cannot move a DocumentDB subnet group or cluster between VPCs. The subnet group name therefore includes the VPC ID, allowing Terraform to create a new group and replace the cluster when the VPC changes instead of attempting an unsupported in-place subnet update.

Discard any saved plan created before a VPC replacement or configuration change, then create and apply a fresh one:

rm -f tfplan
terraform plan -out=tfplan
terraform apply tfplan

Warning

Replacing the VPC also replaces the DocumentDB cluster. If it contains data, create and verify a snapshot before applying the plan; a final snapshot preserves the old data but is not restored into the replacement cluster automatically.

State is kept locally by default. For anything shared, add a backend, for example:

terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "kerberos-hub/eks-documentdb.tfstate"
    region = "eu-west-1"
  }
}

Connect kubectl

$(terraform output -raw update_kubeconfig_command)
kubectl get nodes

Installing Kerberos Hub against DocumentDB

1. Create the certificate authority secret

DocumentDB presents a certificate signed by the Amazon RDS certificate authority, so every client needs the bundle:

kubectl create namespace kerberos-hub

curl -O https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
kubectl create secret generic mongodb-ca \
  --from-file=global-bundle.pem \
  -n kerberos-hub

2. Generate the values

terraform output -raw hub_values_snippet > hub-documentdb-values.yaml

Which produces something like:

mongodb:
  flavor: "documentdb"
  retryWrites: "false"
  uri: "mongodb://kerberos:...@kerberos-hub-docdb.cluster-xxxx.eu-west-1.docdb.amazonaws.com:27017/?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false"
  adminDatabase: "admin"
  authenticationMechanism: "SCRAM-SHA-1"
  tls:
    enabled: true
    existingSecret: "mongodb-ca"
    caFileName: "global-bundle.pem"
    mountPath: "/certs"

The chart mounts the bundle into every workload that talks to MongoDB, appends tls=true&tlsCAFile=/certs/global-bundle.pem to the URI, and exposes MONGODB_TLS, MONGODB_TLS_CA_FILE and MONGODB_TLS_INSECURE_SKIP_VERIFY through the mongodb-config ConfigMap.

Note

With DocumentDB you must configure the database through mongodb.uri, not through mongodb.host / mongodb.username / mongodb.password, so that the TLS parameters end up in the connection string that every service uses.

3. Install the chart

helm repo add kerberos https://charts.kerberos.io
helm install hub kerberos/hub \
  --version 0.127.0 \
  -n kerberos-hub \
  -f your-hub-values.yaml \
  -f hub-documentdb-values.yaml

The hub_values_snippet output contains credentials, so treat the generated file as a secret and do not commit it.

4. Verify

kubectl logs -n kerberos-hub deploy/hub-api | head -50
kubectl exec -n kerberos-hub deploy/hub-api -- ls -l /certs

A one-off connectivity check from inside the cluster:

kubectl run mongosh --rm -it --restart=Never -n kerberos-hub \
  --image=mongodb/mongodb-community-server:7.0-ubi8 \
  --overrides='{"spec":{"volumes":[{"name":"ca","secret":{"secretName":"mongodb-ca"}}],"containers":[{"name":"mongosh","image":"mongodb/mongodb-community-server:7.0-ubi8","stdin":true,"tty":true,"command":["mongosh"],"args":["'"$(terraform output -raw mongodb_uri)"'&tls=true&tlsCAFile=/certs/global-bundle.pem"],"volumeMounts":[{"name":"ca","mountPath":"/certs"}]}]}}'

5. Import the example Hub data

This module has a separate DocumentDB import under database-import. It uses the chart-managed mongodb-config, mounts mongodb-ca, forces TLS with the Amazon RDS CA bundle, and refuses to run unless the backend flavor is documentdb with retryable writes disabled. Install Hub chart 0.127.0 or newer before running it.

Run it after installing Hub:

./database-import/run.sh

The import is idempotent: it upserts two example users, one subscription and five settings documents using fixed IDs, then verifies those records. It can be rerun after deleting or replacing the DocumentDB cluster.

AccountPasswordRole
example-userexample-passwordHub owner
example-applicationexample-passwordAdmin application

These are public example credentials. Do not use this seed data in a production deployment.

Public HTTPS ingress

The ingress package installs ingress-nginx behind an internet-facing AWS Network Load Balancer, installs cert-manager, and creates Let's Encrypt certificates for exactly these routes:

HostService
aws-app.kerberos.lolhub-frontend-svc:80
aws-api.kerberos.lolhub-api-svc:8081

Keep the Hub chart's global ingress value disabled. The module owns these two Ingress resources so that enabling public access does not also expose the Hub administration services.

Install the controllers and resources:

./ingress/install.sh

The cert-manager values use 1.1.1.1 and 8.8.8.8 for HTTP-01 self-checks. This avoids waiting for the AWS VPC resolver if it cached an NXDOMAIN before the public records were created. The override affects only cert-manager's ACME self-checks; normal cluster DNS continues to use the VPC resolver.

The script prints the NLB hostname. Create both DNS records as CNAMEs pointing to that hostname. You can retrieve it again with:

kubectl get service ingress-nginx-controller \
  --namespace ingress-nginx \
  --output jsonpath='{.status.loadBalancer.ingress[0].hostname}{"\n"}'
DNS nameTypeTarget
aws-app.kerberos.lolCNAMEThe ingress-nginx NLB hostname
aws-api.kerberos.lolCNAMEThe ingress-nginx NLB hostname

cert-manager automatically retries its HTTP-01 challenges after DNS resolves; do not delete pending CertificateRequests. Wait for both certificates:

kubectl wait --namespace kerberos-hub \
  --for=condition=Ready certificate/aws-app-kerberos-lol-tls \
  certificate/aws-api-kerberos-lol-tls \
  --timeout=10m

Set Hub's public URLs with the non-secret values fragment after the certificates are ready. Include the same private Hub and DocumentDB values used for the original installation:

helm upgrade hub kerberos/hub \
  --version 0.127.0 \
  --namespace kerberos-hub \
  --file your-hub-values.yaml \
  --file hub-documentdb-values.yaml \
  --file ingress/hub-public-values.yaml \
  --atomic \
  --wait

Verify both public endpoints:

curl --fail https://aws-api.kerberos.lol/health
curl --fail --output /dev/null https://aws-app.kerberos.lol/login

Persistent volumes

The EBS CSI driver is installed, but EKS ships gp2 as the default storage class. To use gp3 instead:

kubectl patch storageclass gp2 -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
kubectl apply -f - <<'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  type: gp3
EOF

Tear down

# Remove Kubernetes resources first so AWS load balancers and volumes are cleaned up.
kubectl delete -f ingress/hub-ingresses.yaml --ignore-not-found
helm uninstall hub -n kerberos-hub
kubectl delete -f ingress/cluster-issuer.yaml --ignore-not-found
helm uninstall cert-manager -n cert-manager
helm uninstall ingress-nginx -n ingress-nginx

terraform destroy

Inputs

The defaults are tuned for a small test stack. See variables.tf for the full list; the ones you are most likely to change:

VariableDefaultDescription
namekerberos-hubName prefix for every resource
regioneu-west-1AWS region
vpc_cidr10.20.0.0/16VPC CIDR block
single_nat_gatewaytrueOne shared NAT gateway (cheaper, not highly available)
kubernetes_version1.31EKS control plane version
cluster_endpoint_public_access_cidrs["0.0.0.0/0"]Who may reach the Kubernetes API, narrow this down
node_instance_types["t3.large"]Worker node instance types
node_desired_size2Number of worker nodes
docdb_instance_classdb.t3.mediumDocumentDB instance class
docdb_instance_count1Number of DocumentDB instances
docdb_usernamekerberosMaster username
docdb_passwordgeneratedMaster password, generated when unset
docdb_tlstrueEnforce TLS on the cluster
docdb_allowed_cidrs[]Extra CIDRs allowed on port 27017

Outputs

OutputDescription
cluster_name, cluster_endpointEKS cluster identity
update_kubeconfig_commandReady to run aws eks update-kubeconfig ...
vpc_id, private_subnet_idsNetworking identifiers
docdb_endpoint, docdb_reader_endpoint, docdb_portDocumentDB connection details
docdb_username, docdb_passwordMaster credentials (password is sensitive)
mongodb_uriConnection string for mongodb.uri (sensitive)
hub_values_snippetReady to paste helm values including the TLS block (sensitive)