The platform setup belongs in code.

I started this project to explore GitOps application delivery and Kubernetes on AWS. The practical idea was to put most of the infrastructure and platform setup in Terraform, so another DevOps team could adapt a set of inputs instead of assembling the same components individually.

Terraform brings together EKS, Karpenter, ingress, DNS automation, certificates, storage, and ArgoCD. Application YAML and ArgoCD Application definitions then describe what runs on that foundation. That division is the thread through this series.

For a company migration, start in an approved non-production AWS account with identity, network ranges, and DNS ownership agreed. Install Git, an authenticated AWS CLI, Terraform with S3 native locking support, kubectl, and Helm. Task is optional.

git clone https://github.com/happycuban/eks-karpenter.git
cd eks-karpenter
aws sts get-caller-identity

Verify the account before provisioning anything. These are the deployable roots:

global/create-bucket/  → state bucket bootstrap
global/repos-ecr/      → application image repositories
global/github-oidc/    → optional GitHub Actions identity
environments/dev/     → development platform
environments/pro/     → separate production configuration
modules/              → reusable implementation
k8s-argo-apps/         → ArgoCD and application YAML examples

1. Supply your environment values.

cp global/create-bucket/terraform.tfvars.example global/create-bucket/terraform.tfvars
cp environments/dev/terraform.tfvars.example environments/dev/terraform.tfvars
cp environments/dev/backend.hcl.example environments/dev/backend.hcl

Edit the copied files. Keep the full examples as the starting point; these are selected fields from the environment configuration:

region                    = "eu-central-1"
environment               = "dev"
cluster_name              = "company-dev-eks"
bucket                    = "YOUR_UNIQUE_STATE_BUCKET"
hosted_zone_id            = "YOUR_PUBLIC_ZONE_ID"
domain_name               = "pilot.example.com"
subject_alternative_names = "*.pilot.example.com"
acme_email                = "platform-team@example.com"
restrict_cluster_access   = true
additional_allowed_ips    = ["YOUR_PUBLIC_EGRESS_IP/32"]
enable_github_actions_access = false

Replace every placeholder, including the public egress IP of your Terraform runner. Review the subnet ranges and admin identity settings. The module creates a VPC; using an existing corporate VPC requires adapting that module.

Set the backend separately, using the state bucket’s region:

# environments/dev/backend.hcl
bucket       = "YOUR_UNIQUE_STATE_BUCKET"
key          = "dev/terraform.tfstate"
region       = "eu-central-1"
use_lockfile = true
encrypt      = true

Keep local values and state out of Git. Each root needs a distinct state key. The S3 backend documentation covers state and lock-object permissions.

2. Prepare the DNS delegation.

The stack expects a public Route53 hosted zone. Follow the Route53 setup wiki, then put its ID and domain in the environment values. For a company pilot, a delegated subdomain can keep the parent domain with its existing provider.

aws route53 list-hosted-zones-by-name --dns-name pilot.example.com
aws route53 get-hosted-zone --id YOUR_PUBLIC_ZONE_ID \
  --query 'DelegationSet.NameServers' --output text
dig NS pilot.example.com +short

Select the exact public zone, compare its name servers with the delegation, and use the ID without the /hostedzone/ prefix. For subdomains, the parent zone owner adds the NS delegation; see AWS’s delegation instructions. Terraform supplies the DNS controller and certificate configuration after this prerequisite is ready.

3. Bootstrap the state bucket.

Set the unique bucket name and region in global/create-bucket/terraform.tfvars, then run:

terraform -chdir=global/create-bucket init
terraform -chdir=global/create-bucket validate
terraform -chdir=global/create-bucket plan
terraform -chdir=global/create-bucket apply

Review the plan and confirm the prompt. The S3 module declares versioning, encryption, and public-access blocking. Preserve this bootstrap root’s local state securely; the other roots use the bucket after it exists.

4. Provision the image repositories.

For the ECR delivery path, use global/repos-ecr. The ECR wiki explains the registry workflow.

cp global/repos-ecr/terraform.tfvars.example global/repos-ecr/terraform.tfvars
cp global/repos-ecr/backend.hcl.example global/repos-ecr/backend.hcl

Edit the region, bucket, and github_repos. Use a separate backend key such as global/repos-ecr/terraform.tfstate. The module creates repository names by lowercasing entries in this list:

github_repos = ["your-company/your-application"]

This creates your-company/your-application. The actual code uses this list, not the separate repository_names input shown in the wiki.

terraform -chdir=global/repos-ecr init -backend-config=backend.hcl
terraform -chdir=global/repos-ecr validate
terraform -chdir=global/repos-ecr plan
terraform -chdir=global/repos-ecr apply
terraform -chdir=global/repos-ecr output ecr_repository_urls

Use the output as the image repository in part 3. Review modules/ecr/ecr.tf for retention settings so release images needed for rollback remain available. An existing company registry can also be used with the corresponding image and authentication configuration.

5. Deploy the platform from the environment root.

environments/dev/main.tf invokes the shared EKS/Karpenter module, KMS, and EBS CSI. The shared module contains the Helm releases and platform manifests. The deployment workflow is:

terraform -chdir=environments/dev init -backend-config=backend.hcl
terraform -chdir=environments/dev validate
terraform -chdir=environments/dev plan
terraform -chdir=environments/dev apply

The apply includes the controllers described in part 2. You do not need to install each chart manually. The runner still needs AWS permissions, cluster endpoint connectivity, and Kubernetes access for the provider operations.

Before applying your adapted configuration, resolve the admin access-entry overlap between main.tf and manager-role.tf, and confirm the runner can authenticate to Kubernetes. Choose compatible component versions: the source’s Kubernetes 1.33 pin has passed EKS standard support according to the AWS release calendar. Keep these adjustments in the shared implementation.

The Taskfile wraps the same process.

task deploy-infrastructure ENV=dev REGION=eu-central-1 CLUSTER_NAME=company-dev-eks

This initializes, validates, plans, prompts, applies, and updates kubeconfig. Match REGION and CLUSTER_NAME to your environment: the Taskfile has its own defaults. They configure Task commands; they do not replace Terraform variables.

Optional: provision GitHub Actions access.

Keep enable_github_actions_access = false for a first local deployment. To enable it, configure and deploy the OIDC root first:

cp global/github-oidc/terraform.tfvars.example global/github-oidc/terraform.tfvars
cp global/github-oidc/backend.hcl.example global/github-oidc/backend.hcl
# Edit values and the distinct backend key.
terraform -chdir=global/github-oidc init -backend-config=backend.hcl
terraform -chdir=global/github-oidc plan
terraform -chdir=global/github-oidc apply

Then enable environment access with the correct role name. Review the role’s trust and permissions. The workflow example under .github/workflows/eks-terraform.yml.example remains inactive until configured and enabled. An application build role need not have the same permissions as an infrastructure deployment role.

6. Inspect what Terraform created.

terraform -chdir=environments/dev state list
aws eks update-kubeconfig --region YOUR_REGION --name YOUR_CLUSTER \
  --role-arn arn:aws:iam::YOUR_ACCOUNT_ID:role/YOUR_EKS_ADMIN_ROLE
kubectl get nodes
kubectl get pods -A
helm list -A

Use your authorized admin role. State shows managed resources; kubectl and Helm show runtime health. If an apply partially completes, inspect its error and state before retrying.

For later changes, edit the variables, module, or template and repeat plan/apply. Keep the shared state and registry roots separate from environment retirement. Review controller-created resources and application data before planning destruction of a disposable environment.

PART 2

What the Terraform Modules Build

Follow the module wiring →