Install onto EKS
Backed by RDS Postgres 16 (external or provisioned) + bundled Redis, fronted by an ALB. Two postures via deploy_cluster, mirroring GKE.
Reuse path (deploy_cluster = false, default) — must already exist
- The cluster itself, reachable with
eks:DescribeClusterand mapped to Kubernetes RBAC (an EKS access entry oraws-authConfigMap). - The AWS Load Balancer Controller, already installed — without it, apply succeeds but no load balancer is ever created.
- The EBS CSI driver, enabled as an EKS addon — unlike GKE, EKS does not enable this by default; without it the bundled Redis PVC never binds.
- A managed RDS Postgres 16 instance, unless
create_rds = true(provisioning path only).
Provisioning path (deploy_cluster = true) sets up automatically
- Networking — dedicated VPC, public+private subnets per AZ, one NAT gateway (cost tradeoff over per-AZ HA), subnets tagged for ALB controller auto-discovery.
- The EKS cluster — private-only by default (
cluster_endpoint_public_access = false), matching GKE’s posture. - A bastion (default on) — no public IP, no open inbound ports, reached only via
aws ssm start-session. Terraform/kubectl/helm run from the bastion once the endpoint is private-only. - Two node groups —
spaces(defaultm5.xlarge) andcontrol_plane(defaultm5.2xlarge— not smaller, same sizing lesson as GKE), with the sametaint_control_plane_pooltoggle shape. - OIDC provider + IRSA for the EBS CSI addon and a Helm-installed AWS Load Balancer Controller.
create_rds = true(opt-in) — a smalldb.t4g.microinstance, single-AZ.
Registry — direct pull by default; Harbor as an opt-in fallback
Pulls straight from registry.marshal.codes. There is no ECR pull-through cache option — confirmed live that CreatePullThroughCacheRule only accepts a hardcoded enum of known upstreams, unlike GCP Artifact Registry’s REMOTE_REPOSITORY mode, which accepts any Docker v2 host. See Registry options for the full picture.
Harbor cache (create_harbor_cache) — opt-in fallback, what it sets up
Use only when direct-pull’s shared rate limit becomes a real problem — not a default-on replacement.
-
Harbor deployed in-cluster (Helm), pinned to the control-plane node group; no vulnerability scanner; blob storage on S3 via IRSA.
-
A
harbor_registry+harbor_project(public/anonymous-read, safe since only reachable inside the private VPC) targetregistry.marshal.codesvia Harbor’s genericdocker-registryadapter. -
Nodes reach it via a fixed ClusterIP (not a hostname — node-level containerd can’t resolve
*.svc.cluster.local); Harbor serves plain HTTP internally since a raw IP has no valid cert hostname to match. -
Configuring the proxy needs one manual step — the Harbor Terraform provider can only reach the ClusterIP from an actual node, not the bastion, so background a port-forward first:
kubectl port-forward -n harbor-cache svc/harbor 8080:80 & terraform apply
Quickstart
Configure terraform.tfvars
cp ../examples/aws-eks.tfvars.example terraform.tfvars
$EDITOR terraform.tfvarsPlan and apply
terraform init
terraform plan # review
terraform applyVerify
terraform output next_stepsOptional storage buckets
create_audit_persistence_bucket = true # compliance export
create_spaces_persistence_bucket = true # session pause/resume snapshotsNot yet built: reusing an existing VPC (create_network = false) — only
create_network = true is exercised so far.
Day-2
Bump chart_version and terraform apply. Roll back with helm rollback marshal -n marshal.