GitOps Kubernetes Platform on AWS EKS
A GitOps-managed Kubernetes platform with automated infrastructure, secure workload identities, DNS, TLS, persistent storage and observability.
Technologies
The project
Overview
I built a GitOps-managed Kubernetes platform on Amazon EKS, using Terraform to provision the AWS infrastructure and ArgoCD to manage Kubernetes resources declaratively.
The platform hosts code-server, a browser-accessible development environment, behind an AWS Network Load Balancer and Traefik ingress controller. ExternalDNS manages Route 53 records, while cert-manager automates TLS certificate issuance through Let's Encrypt.
The wider platform includes persistent EBS storage, workload-level IAM access through EKS Pod Identity, GitHub Actions CI/CD and observability through Prometheus and Grafana.
System design
Architecture

Architecture diagram from the project.
The challenge
The main challenge was building a reproducible Kubernetes environment in which infrastructure provisioning, platform bootstrap and ongoing workload management had clearly defined responsibilities.
GitOps introduces a bootstrap dependency: ArgoCD cannot manage the cluster until the cluster exists and ArgoCD itself has been installed.
The platform also needed secure AWS access for Kubernetes components, reliable persistent storage and a teardown process capable of removing AWS resources created dynamically by Kubernetes controllers.
How the architecture works
Infrastructure and compute
Terraform provisions a multi-AZ VPC, EKS cluster, managed node groups and IAM resources. Worker nodes and application workloads run in private subnets.
GitOps control plane
Helm bootstraps ArgoCD, after which the App-of-Apps pattern manages platform components and application resources through continuous reconciliation from Git.
Ingress and DNS
Traffic reaches the cluster through an internet-facing Network Load Balancer and Traefik. ExternalDNS reconciles Route 53 records from Kubernetes resources.
TLS and storage
cert-manager automates Let's Encrypt certificates. The AWS EBS CSI driver dynamically provisions persistent volumes for workloads that require storage.
Identity and delivery
EKS Pod Identity provides scoped AWS permissions to cluster components. GitHub Actions uses OIDC for infrastructure workflows and publishes scanned container images to ECR.
Observability
Prometheus and Grafana, deployed through kube-prometheus-stack, provide visibility into cluster and workload metrics.
Behind the build
Implementation
Separating infrastructure provisioning from GitOps
Terraform provisions the underlying AWS infrastructure, including networking, EKS, IAM and supporting services. Remote state is stored in S3 using native state locking.
I used Helm to bootstrap ArgoCD once the cluster was available, then applied the root application to establish the App-of-Apps structure.
From that point, ArgoCD reconciles the platform's Kubernetes resources against their configuration in Git, reducing the need for manual changes inside the cluster.
Automating application access and HTTPS
The application is exposed through Traefik behind an AWS Network Load Balancer. ExternalDNS manages the corresponding Route 53 records, while cert-manager requests and renews certificates from Let's Encrypt.
This allows application routing, DNS and TLS configuration to be managed through Kubernetes resources instead of separate manual deployment steps.
Resolving persistent storage provisioning failures
I encountered dynamic EBS volume provisioning failures because the cluster did not have the required default gp3 storage class configuration.
I addressed the storage configuration by deploying the AWS EBS CSI driver, configuring EKS Pod Identity for its AWS permissions and using the CSI-based provisioner instead of the deprecated in-tree approach.
This established dynamic persistent volume provisioning for the code-server workload.
Implementing workload-level AWS permissions
I configured EKS Pod Identity so components such as ExternalDNS and the EBS CSI driver could use dedicated IAM roles.
This avoids depending on broad node-level AWS permissions and allows access to be scoped according to each component's responsibilities.
GitHub Actions separately uses OIDC federation for AWS access during infrastructure and image delivery workflows.
Building the CI/CD and GitOps workflow
GitHub Actions runs Terraform formatting, validation, Checkov security analysis and infrastructure planning. Infrastructure changes are applied through an OIDC-authenticated workflow.
Application changes trigger Docker image builds and Trivy scanning before images are pushed to Amazon ECR.
The pipeline updates the image reference in the deployment manifests, allowing ArgoCD to detect the Git change and reconcile the new application version.
Handling infrastructure teardown
Kubernetes controllers can create AWS resources that are not directly represented as Terraform-managed resources. These need to be considered before destroying the underlying cluster.
I implemented a dedicated teardown workflow that cleans up Kubernetes-created AWS resources before running Terraform destroy.
This made it possible to remove the environment when it was not needed, reducing the risk of leaving chargeable infrastructure behind.
Key takeaways
GitOps requires a deliberate bootstrap process before the GitOps controller can manage the platform.
Persistent storage depends on the interaction between Kubernetes storage resources, CSI drivers and AWS permissions.
Pod Identity provides a clearer boundary between Kubernetes workloads and their required AWS permissions.
Automated DNS and TLS reduce manual configuration but introduce controllers whose behaviour must be understood.
Reliable infrastructure teardown is part of platform engineering, particularly when Kubernetes creates cloud resources dynamically.
Reflection
What this project taught me
This project brought together infrastructure provisioning, Kubernetes operations and GitOps into one platform. Resolving the storage issue and designing the bootstrap and teardown workflows were particularly valuable because they required understanding how Kubernetes controllers interact with AWS infrastructure.
Explore the source code