Security-hardened Kubernetes GitOps platform β from local Kind cluster to AWS EKS, with zero-trust networking, distroless containers, and infrastructure-as-code enforced at every layer.
π Portfolio / demo project β production-inspired architecture and security posture built to demonstrate real-world platform engineering competencies. Not a claim of live production usage.
KubeSecure-GitOps is a fully integrated DevSecOps platform that provisions hardened AWS infrastructure with Terraform, packages a Spring Boot REST API into a distroless container, and delivers it to AWS EKS through an ArgoCD GitOps pipeline. Every design decision β from subnet segmentation to Kubernetes NetworkPolicies β is driven by a least-privilege, defense-in-depth security model.
The workflow follows a local-first discipline: workloads are validated inside a Kind cluster before promotion to cloud infrastructure, reflecting how platform teams reduce blast radius during development.
| Area | Skills Evidenced |
|---|---|
| GitOps | ArgoCD Application CRs, reconciliation loop, sync strategies |
| Infrastructure as Code | Modular Terraform pattern library; environment composition roots |
| AWS Networking | VPC, 3-tier subnet segmentation (public / private / isolated), NAT Gateway |
| Compute & Data | Private EKS 1.30 managed node groups, RDS PostgreSQL 15 in an isolated subnet |
| Container Security | Multi-stage build, distroless base image, non-root UID, read-only root filesystem |
| Kubernetes Security | Default-deny NetworkPolicies, resource limits, least-privilege pod spec |
| IAM & KMS | Scoped EKS control-plane and worker-node roles, KMS-encrypted RDS storage |
| Platform Workflow | Kind β ECR β EKS promotion path with a single values-file flip |
| Layer | Technology |
|---|---|
| Application | Spring Boot (Java), REST API |
| Container | Docker multi-stage build, Google Distroless base |
| Package Management | Helm |
| GitOps Engine | ArgoCD |
| Orchestration | Kubernetes 1.30 (Kind locally, AWS EKS in cloud) |
| Infrastructure | Terraform (modular) |
| Cloud | AWS β EKS, RDS PostgreSQL, VPC, KMS, IAM, NAT Gateway, ECR |
| Registry | Amazon ECR (private) |
Three isolated network tiers enforce strict workload separation:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AWS VPC (10.0.0.0/16) β
β β
β ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββ β
β β Public β β Private β β Isolated β β
β β Subnets β β Subnets β β Subnets β β
β β β β β β β β
β β NAT Gateway β β EKS Node Group β β RDS PG 15 β β
β β (egress) β β ArgoCD β β (no IGW, β β
β β β β Spring Boot API β β no NAT) β β
β ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- EKS worker nodes communicate with the control plane over private endpoints only.
- RDS accepts connections exclusively from the EKS cluster security group on port
5432. - Application pod egress is restricted to DNS (
UDP 53) and PostgreSQL (TCP 5432).
Container hardening:
- Multi-stage Docker build β Maven toolchain never ships to the runtime image
- Distroless base image β no shell, no package manager, minimal attack surface
- Non-root runtime β unprivileged
appuser(UID10001) - Immutable root filesystem (
readOnlyRootFilesystem: true);/tmpusesemptyDir
Kubernetes hardening:
- Default-deny NetworkPolicy β all traffic blocked unless explicitly permitted
- Egress allow-list: CoreDNS (
UDP 53) and PostgreSQL (TCP 5432) only - CPU and memory resource limits on every pod
AWS / Terraform hardening:
- EKS nodes provisioned with
ec2_ssh_key = nullβ SSH surface eliminated - RDS security group accepts inbound only from the EKS cluster SG (no public exposure)
- KMS encryption enabled on all RDS storage volumes
- Least-privilege IAM roles scoped separately for control-plane and worker nodes
Most GitOps tutorials stop at deploying an app. This project goes further:
- Security is structural, not bolted on. Isolation, encryption, and least-privilege are baked into the Terraform modules, Helm chart, and Kubernetes manifests β not applied as afterthoughts.
- The Terraform pattern library is reusable. Atomic, single-responsibility modules (
network,iam,compute,database) are composed at the environment layer, enabling multi-environment rollout without module duplication. - The local-first workflow mirrors real platform engineering. Kind validation before cloud promotion reduces cloud spend and catch configuration errors before they become expensive incidents.
- The architectural retrospective is honest. The Secret Management section documents the current demo trade-offs and the production-grade IRSA + External Secrets Operator path forward β showing engineering judgement, not just happy-path demos.
The cloud infrastructure is provisioned with modular Terraform, enforcing strict micro-segmentation and defense-in-depth across three separate subnet tiers:
- Public Tier β Hosts only the AWS NAT Gateways. No application or compute workload runs here.
- Private Tier β Houses the private AWS EKS 1.30 Managed Node Groups. Worker nodes reach the EKS control plane over private endpoints (
endpoint_private_access = true). - Isolated Tier β Hosts the encrypted Amazon RDS PostgreSQL 15 instance. This tier has zero routing to the internet or the NAT Gateway β it is unreachable from the outside world by design.
graph TD
subgraph Internet
User([External Traffic])
end
subgraph AWS["AWS Cloud β VPC 10.0.0.0/16"]
subgraph Public["Public Subnets"]
NAT[AWS NAT Gateway]
end
subgraph Private["Private Subnets β EKS Cluster v1.30"]
Argo[ArgoCD Engine]
API[Spring Boot API Pods x2]
end
subgraph Isolated["Isolated Subnets"]
RDS[(RDS PostgreSQL 15)]
end
end
User -->|Secure Tunnel / Port-Forward| API
Argo -->|Reconciliation Loop| GitHub[(GitHub Repository)]
API -->|Egress allowed only on 5432 / 53| RDS
API -->|Outbound updates| NAT
style RDS fill:#f9f,stroke:#333,stroke-width:2px
style API fill:#bbf,stroke:#333,stroke-width:2px
style Argo fill:#bfb,stroke:#333,stroke-width:2px
- Multi-Stage Build β Compilation tools (Maven) are discarded in the builder stage. The final runtime image ships only the compiled
.jar. - Distroless Base β Runs on a minimal image with no shells (
sh,bash), package managers (apt), or OS utilities, reducing the attack surface to near-zero. - Non-Root Enforcement β The container process runs under an unprivileged user (
appuser, UID10001). - Immutable Filesystem β The root filesystem is mounted read-only (
readOnlyRootFilesystem: true). Volatile operations write exclusively to an ephemeral scratch volume mounted at/tmp(emptyDir: {}).
- Least-Privilege NetworkPolicies β A default-deny posture is applied. Egress from the application pods is restricted to UDP port
53(CoreDNS) and TCP port5432(PostgreSQL endpoint) only. - Resource Constraints β Explicit CPU and memory requests and limits are configured via Helm values to protect nodes against malicious or accidental resource exhaustion (OOM-Killed protection).
- Infrastructure Pattern Library β A decoupled architecture where atomic, single-responsibility modules (
network,iam,compute,database) are entirely cloud-state agnostic. Multi-environment execution (dev, staging, prod) is driven purely by variable composition roots. - Security Group Micro-Filtering β The RDS PostgreSQL instance accepts inbound connections (Ingress) exclusively from the EKS cluster's primary Security Group ID.
- Data-at-Rest Encryption β Storage-level encryption is enabled on the RDS volumes using AWS KMS keys.
- No Backdoors β EKS worker nodes are provisioned with
ec2_ssh_key = null, closing the SSH surface entirely. Maintenance relies on audited session-management tooling instead.
KubeSecure-GitOps/
βββ app/ # Spring Boot REST API application source
β βββ Dockerfile # Secure multi-stage, non-root container build
βββ helm/ # Helm packaging
β βββ kubesecure-app/
β βββ templates/ # Manifests (Deployment, Service, NetworkPolicy)
β βββ values.yaml # Environment parameters
βββ argocd/ # GitOps manifests
β βββ application.yaml # ArgoCD Application custom resource
βββ terraform/ # Infrastructure as Code (Pattern Library)
βββ modules/ # Reusable building blocks
β βββ network/ # VPC Β· 3 subnet tiers Β· routing Β· NAT Gateway
β βββ iam/ # EKS control-plane & worker-node least-privilege roles
β βββ compute/ # Private EKS 1.30 cluster & managed node group
β βββ database/ # Encrypted RDS PostgreSQL 15 & security groups
βββ environments/ # Composition roots (state allocation)
βββ aws-final/ # Orchestration root wiring the infrastructure modules
Build the container image locally:
docker build -t kubesecure/gitops-api:1.0.0 ./appLoad the artifact directly into the Kind cluster node cache:
kind load docker-image kubesecure/gitops-api:1.0.0 --name kubesecure-clusterStart GitOps syncing:
kubectl apply -f argocd/application.yamlAccess the ArgoCD Web UI (via port-forward or ingress) to monitor the synchronization state of the application:
Log in using your administrator credentials:
Once authenticated, verify that the kubesecure-api-gitops application is successfully synchronized (Healthy and Synced status):
Initialize and provision the cloud infrastructure:
cd terraform/environments/aws-final/
terraform init
terraform apply -auto-approvePoint your local context at the remote cluster:
aws eks update-kubeconfig --region eu-west-3 --name kubesecure-final-demo-clusterPush the image to your private ECR registry:
aws ecr get-login-password --region eu-west-3 \
| docker login --username AWS --password-stdin <ACCOUNT_ID>.dkr.ecr.eu-west-3.amazonaws.com
docker tag kubesecure/gitops-api:1.0.0 \
<ACCOUNT_ID>.dkr.ecr.eu-west-3.amazonaws.com/kubesecure/gitops-api:1.0.0
docker push \
<ACCOUNT_ID>.dkr.ecr.eu-west-3.amazonaws.com/kubesecure/gitops-api:1.0.0Trigger the GitOps reconciliation loop: set environment: "aws", point database.host at the RDS endpoint in your values.yaml, push to Git, and watch ArgoCD sync the manifests onto EKS automatically.
Note on the current posture. For the temporary validation phase of this demo, application secrets (database credentials) are managed with standard static Kubernetes Secrets injected into the namespace. This is acceptable for an ephemeral demo but is not a production-grade approach.
In a continuous, long-lived production pipeline, this posture should evolve toward a zero-trust model:
- Enable an OIDC provider identity on the EKS cluster.
- Set up IRSA (IAM Roles for Service Accounts) to establish a cryptographic trust link between an EKS pod and AWS IAM without hardcoded access tokens.
- Introduce the External Secrets Operator (ESO) inside the cluster to consume that token, fetch credentials directly from AWS Secrets Manager, and rotate them automatically without manual intervention.

