EKS reference
Use this example when your Kubernetes deployment bundle contains both
chart/ and reference-terraform/. It creates a dedicated EKS cluster and
RDS database in an existing VPC. An existing ALB routes traffic to the
Analyzer. The Watcher API
authenticates users through WorkOS.
Keep the two directories beside each other: the Terraform release loads
../chart. For a cluster you already operate, start with the
Kubernetes infrastructure guide instead.
What you provide
- Terraform 1.13 or later, AWS CLI, Helm, and
kubectl. - AWS credentials that can create the reference's resources. Run Terraform
and
kubectlfrom a host that can reach the private EKS API. - An existing VPC and private subnets in at least two availability zones. Provide egress or private endpoints for registry pulls and AWS services. Allow API pods to reach your model providers or gateway.
- An ALB, its HTTPS listener and certificate, DNS, and permission to change the listener's forwarding rule and security-group rules.
- The matching Watcher chart and image tag, registry access, WorkOS credentials, and an approved secret manager.
- Access from API pods to the Anthropic, OpenAI, and Google models used by the default managed monitors. Follow the provider requirements for all three, even if you use a gateway for one of them.
The reference uses the default namespace for Watcher and the registry
refresher. Keep your Secret, pull credentials, Helm release, and
TargetGroupBinding in that namespace. If you move Watcher to another
namespace, update those resources and every refresher Kubernetes resource.
Update the refresher RoleBinding's subject namespace and IAM trust policy's
service-account subject too.
Infrastructure and routing
The diagram focuses on cluster access and ingress. The Kubernetes architecture also shows the background workloads, model calls, and audit volumes.
| Reference file | What it manages |
|---|---|
eks.tf | Private EKS API, managed nodes, Kubernetes Secret encryption, control-plane logs, network-policy enforcement, EBS CSI, and IAM roles for service accounts. |
rds.tf | Encrypted private PostgreSQL, its pod-access rule, generated database password, backups, and deletion protection. |
watcher.tf | An encrypted gp3 StorageClass and the Watcher Helm release. |
ingress-alb.tf | An IP target group, security-group rules, AWS Load Balancer Controller, and a TargetGroupBinding. |
registry-refresher.tf | A service account, scoped Secret permissions, and a CronJob that renews registry credentials every eight hours. |
The ALB listener, certificate, and DNS stay in your existing infrastructure.
Configure the listener to forward the Watcher hostname to the
<cluster_name>-analyzer target group created by the reference. Its
health-check path is /api/v1/health.
Allow TCP 8080 from the ALB security group to the pod security group in both
the ALB's egress rules and the pods' ingress rules. The TargetGroupBinding
selects analyzer:80; the target group registers pod IPs on port 8080.
This route uses WorkOS authentication. If you set auth.mode to proxy,
use the identity-aware proxy described in
Kubernetes prerequisites.
It must be the only path into the Analyzer. An ALB alone cannot authenticate
proxy-mode requests.
1. Adapt the reference
Review the Terraform files before running terraform init. Replace every
<PLACEHOLDER>, including organization values, the ALB security group, and
third-party image repositories and digests. Set the inputs in
reference-terraform/terraform.tfvars:
region = "eu-west-2"
cluster_name = "watcher-k8s"
vpc_id = "vpc-xxxxxxxx"
private_subnet_ids = ["subnet-aaaaaaaa", "subnet-bbbbbbbb"]
public_url = "https://watcher.example.com"
eks_version = "<supported-eks-version>"
watcher_image_tag = "<matching-watcher-release-tag>"
Choose an EKS version in standard support. In providers.tf, replace the
region placeholder in both exec-auth argument lists. If you change
cluster_name, update the literal cluster name in both lists too. These
provider settings must match terraform.tfvars.
The cluster creator gets admin access. Add explicit EKS access entries for the operator and CI roles that will manage it. A kubeconfig context alone does not grant access.
Protect your Terraform backend and restrict access to its state.
Terraform stores the generated RDS password in state. Marking the output
sensitive hides it from normal display; it stays in state. Store application
and WorkOS credentials in your secret manager.
Review the database settings against your availability and recovery needs. The RDS example uses a single availability zone, seven-day backups, deletion protection, and a final snapshot.
The supplied gp3 class uses encrypted EBS volumes. With
WaitForFirstConsumer, a claim can stay Pending until a pod is scheduled.
The reclaim policy is Delete. Helm keeps audit claims, but deleting a claim
yourself can delete its volume.
Match controller and refresher images to your node architecture when
mirroring them. Pin each image to the digest from the registry you mirror
into. The example's m6i nodes use linux/amd64. Use the load-balancer
controller chart and bundled IAM policy from the same version.
2. Provision AWS resources
Run from reference-terraform/. Split the first apply into stages.
The Kubernetes providers need a reachable cluster. Terraform also needs the
TargetGroupBinding CRD before it can plan that manifest.
terraform init
terraform apply \
-target=aws_eks_cluster.this \
-target=aws_eks_node_group.default \
-target=aws_eks_addon.vpc_cni \
-target=aws_eks_addon.ebs_csi \
-target=aws_iam_role_policy_attachment.ebs_csi \
-target=aws_db_instance.watcher \
-target=aws_vpc_security_group_ingress_rule.rds_from_pods
Include the EBS driver policy attachment and database ingress rule as separate targets. The driver needs the policy to manage volumes. The bootstrap Job needs the rule to reach the database on its first run.
Create a context using the same cluster and region as the Terraform inputs:
WATCHER_AWS_REGION=eu-west-2
WATCHER_CLUSTER=watcher-k8s
WATCHER_CONTEXT=watcher-eks
WATCHER_NAMESPACE=default
aws eks update-kubeconfig --name "$WATCHER_CLUSTER" \
--region "$WATCHER_AWS_REGION" --alias "$WATCHER_CONTEXT"
kubectl --context "$WATCHER_CONTEXT" get nodes
Fix network or EKS access failures before continuing. This reference keeps the cluster API's public endpoint disabled.
3. Install controllers and registry refresh
Before pulling images from Apollo's registry, allow the refresher's IAM role to assume the registry pull role. The pull role must also trust the refresher.
The refresher must pull its own AWS CLI and kubectl images before it creates
apollo-ecr. Use your node role's registry access or a separate credential
for those images.
terraform apply \
-target=helm_release.lb_controller \
-target=aws_iam_role_policy.lb_controller \
-target=kubernetes_cron_job_v1.ecr_refresher \
-target=aws_iam_role_policy.ecr_refresher \
-target=kubernetes_role_binding.ecr_refresher
kubectl --context "$WATCHER_CONTEXT" wait --for=condition=Established \
crd/targetgroupbindings.elbv2.k8s.aws --timeout=180s
Run the refresher once before installing Watcher, then verify completion and the pull Secret:
kubectl --context "$WATCHER_CONTEXT" -n "$WATCHER_NAMESPACE" \
create job --from=cronjob/ecr-refresher ecr-refresher-init
kubectl --context "$WATCHER_CONTEXT" -n "$WATCHER_NAMESPACE" \
wait --for=condition=complete job/ecr-refresher-init --timeout=180s
kubectl --context "$WATCHER_CONTEXT" -n "$WATCHER_NAMESPACE" get secret apollo-ecr
The CronJob renews the Secret every eight hours because ECR tokens expire
after twelve. Its Kubernetes role can create Secrets in the namespace, but
its read and patch permissions name only apollo-ecr. Monitor refresh
failures. An expired token can stop new pods from starting.
For an internal mirror, omit the
refresher resources and remove their targets from the apply command above.
Set image.registry to the mirror's full repository prefix. Replace image.pullSecrets with your own
pull Secret names, or use [] when nodes already have access. Install the
load-balancer controller and verify its CRD before proceeding.
4. Create the application Secret and install Watcher
Read the database endpoint without exposing the password:
terraform output -raw rds_endpoint
The endpoint includes its port. Transfer rds_master_password directly to
your secret manager. Set DATABASE_URL to
postgresql+asyncpg://watcher:PASSWORD@HOST:PORT/watcher.
Create watcher-external in the release namespace with these keys:
| Secret key | Value |
|---|---|
DATABASE_URL | The database URL above. |
WATCHER_INTERNAL_AUTH_TOKEN | A random shared value. |
WATCHER_WORKOS_CLIENT_ID | Your WorkOS client ID. |
WATCHER_WORKOS_API_KEY | Your WorkOS API key. |
ANTHROPIC_API_KEY | Access to Gateway and Deep Review. Omit only with an Anthropic-compatible gateway. |
OPENAI_API_KEY | Access to Triage. Omit only with an OpenAI-compatible gateway. |
GOOGLE_API_KEY | Access to session summaries. Keep this key in this example. |
Follow the model gateway settings for each provider you route through a gateway. With only an Anthropic gateway configured, keep the OpenAI and Google keys.
Keep secret values out of Terraform values, shell arguments, and committed manifests.
In watcher.tf, set organization.id, organization.name, and
auth.workos.orgId. Check existingSecret: watcher-external, the matching
image tag, and the external publicUrl. Keep the organization ID after
installing.
With the CRD, pull credentials, and application Secret ready, review and run the full apply:
terraform plan
terraform apply
Use full plans for later changes. Targeted applies are for the first install.
5. Verify and operate
Complete the checks in
Verify before connecting developers.
Check that the ALB target group is healthy and the listener points to it.
Test that WorkOS login returns to publicUrl. Set the ALB idle timeout above
client timeouts for synchronous grading calls.
Back up RDS, the audit volumes, and deployment settings. Collect pod logs
outside the nodes. Install metrics-server before enabling API autoscaling;
the reference does not include it. EBS gp3 cannot provide shared audit
storage for multiple API replicas. Follow
Scale and operate to choose
shared storage and keep your audit logs.