Amazon EKS · A worked example

Cartwright on EKS: one enterprise app, end to end

A single realistic B2C retail platform — its services, its multi-account environments, its repos, and its pipelines — drawn as one connected picture so you can see how a real application lands on Amazon EKS.

What you’re looking at

Cartwright is an invented but realistic online-retail platform: ten microservices (storefront, BFF [backend-for-frontend], catalog, cart, orders, payments, inventory, users/auth, search, notifications) backed by RDS, DynamoDB, ElastiCache, S3, and MSK. This piece walks it in four layers — the application (who calls whom), the environments (a multi-account, cluster-per-env layout), the repos (where code, config, and infrastructure live), and the pipelines (CI per service, CD by GitOps) — and then draws the whole thing as one end-to-end diagram. The through-line: every change lands as a GitOps-driven deployment onto an EKS platform, and the platform, not any single cluster, is the product.

Read this for

  • A concrete mental model of a real app on EKS, not a feature tour.
  • How repos, environments, and pipelines actually connect.
  • Where the fleet-platform services (Karpenter, Pod Identity, ESO, Kyverno, ALB, AMP/AMG) plug in.

This is not

  • A step-by-step install guide or a Terraform tutorial.
  • A claim that Cartwright’s exact shape fits every org.
  • A reference for version/pricing specifics — those carry a re-verify date.

The application — who calls whom

Start with the app itself, because everything downstream exists to ship it. A browser hits an ALB, which fronts the server-rendered storefront and a BFF/API gateway. The BFF is the fan-out point: it calls catalog, search, cart, orders, and users/auth. Checkout is where the graph deepens — orders calls inventory to reserve stock and payments to charge a card through an external processor.

State is deliberately split by access pattern rather than dumped in one database. Relational, transactional data (orders, users) sits in RDS; high-throughput key-value data (cart, catalog) in DynamoDB; sessions in ElastiCache; static assets in S3. The event backbone is MSK: orders, payments, and inventory publish domain events, and notifications consumes them to send email and push. Read the map once and the rest of the article is just “how does this get built and shipped, safely, many times a day.”

Figure 1 · The application Cartwright request path — who calls whom, and where state livesservice calldata / event storeexternal / edgeBrowserALB (ingress)StorefrontSSR webBFF / API gatewayCatalogSearchCartOrdersUsers / AuthInventoryPaymentsNotificationsPayment PSPRDSorders, usersDynamoDBcart, catalogElastiCachesessionsS3assetsMSK / Kafkadomain events
Cartwright is a graph, not a list: synchronous calls in blue, state and events in violet. The BFF fans out; checkout reaches into inventory and payments; MSK carries the events notifications lives on.

The environments — one account and one cluster per stage

Cartwright runs in four AWS accounts under one Organization: dev, staging, prod, and a shared platform account that holds ECR and the Argo CD hub. Each environment gets its own account and its own EKS cluster — hard isolation at the blast-radius boundary that matters, which is exactly what AWS’s own guidance recommends: separate clusters for dev, test, and prod rather than leaning on namespaces for environment isolation.1

Inside a cluster, the split is by namespace: one per service (storefront, cart, orders…), plus short-lived pr-preview-* namespaces spun up per pull request. So the tenancy model is account-and-cluster per environment, namespace per service — strong isolation between stages, cheap isolation between services within a stage.

Figure 2 · The environments dev account · us-east-1Cartwright dev VPCstaging account · us-east-1Cartwright staging VPCprod account · us-east-1 + eu-west-1Cartwright prod VPCshared platform account · us-east-1Platform VPCEKS clustercartwright-devnamespaces per service+ pr-preview-* (ephemeral)EKS clustercartwright-stgnamespaces per servicecanary targetEKS clustercartwright-prodnamespaces per servicemulti-AZ, two RegionsAmazon ECRsigned imagesArgo CD hub+ AMP / AMG
Four accounts, a cluster per environment, namespaces per service. Isolation is strongest where it counts — between dev, staging, and prod — and the shared platform account holds the registry and the GitOps hub every cluster answers to.

The repos — code, config, and infrastructure, kept apart

The repo layout is where teams either stay sane or drift into chaos, so it’s worth being explicit. Four kinds of repository, each with one job:

The rule that keeps it clean: app code, desired state, and infrastructure never share a repo. CI writes to the config repo but never to app source; humans change infrastructure in the IaC repo, never by clicking in a console.

Figure 3 · The repos Where code, config, and infrastructure live — and how they relateCI writesdesired state (GitOps)repositoryPer-service app repos (×10)cartwright-storefront, -cart, -orders,-payments, -catalog, -search …• src/ application code• Dockerfile container build• chart/ Helm chart + defaults• .github/ CI workflowShared Helm / library repobase chart, CI templates, commonlabels — imported by every app repoGitOps config repothe fleet's desired state• Argo CD ApplicationSets• envs/dev values• envs/staging values• envs/prod values• image tags (written by CI)IaC repo (Terraform)• accounts + Organizations• VPC / subnets / networking• EKS clusters per env• RDS, DynamoDB, MSK, S3• ECR, IAM, platform add-onsAmazon ECRsigned, scanned imagespush imagebump tagIAM ARNs → Argo secret
Separation of concerns as a repo layout: app repos build images, the shared library sets the golden path, the config repo is the single source of desired state, and the IaC repo owns everything AWS-side.

The pipelines — CI per service, CD by GitOps

The pipeline has two halves that meet at the config repo. CI is per service and runs on every pull request: build and unit test, integration test, build the container, scan it with Trivy in ECR, sign it with cosign, push to ECR, and — the step that connects everything — commit the new image tag into the GitOps config repo. Three gates can stop the line: failing tests, a HIGH/CRITICAL vulnerability, or an unsigned image. Nothing reaches a cluster that didn’t pass all three.

Figure 4 · The CI pipeline CI per service — a pull request becomes a signed, scanned image and a one-line config commitCI stageblocking gateartifact / desired statePRopenedBuildunit testsIntegrationtestsContainerbuildTrivy scanin ECRcosignsign imagePushto ECRtests must passfail on HIGH/CRITunsigned blockedCommit new image tag → GitOps config repo
CI ends not with a deploy but with a one-line commit to the config repo. The artifact is a signed, scanned image in ECR; the deploy is someone else’s job.

CD is that someone else: Argo CD. It watches the config repo and reconciles the merged tag onto the dev cluster automatically. Promotion then walks the environments — dev to staging behind automated and canary tests, staging to prod behind a manual approval — with Argo Rollouts driving progressive delivery and auto-rollback on an SLO breach at each step. Every pull request additionally gets an ephemeral preview namespace, created on open and torn down on merge. No human ever runs kubectl apply against a cluster; they change Git, and Argo converges the fleet.

Figure 5 · The CD promotion flow CD by GitOps — Argo CD syncs the merge, then promotion walks dev → staging → prodconfig repo / Argoenvironmentgate / checkGitOps config repomerged image tagArgo CD hubwatches, syncsdev clusterauto-syncstaging clustercanaryprod clusterArgo Rolloutssyncautomated /canary testsmanualapprovalPer-PR preview — ephemeral namespace, spun up on open, torn down on merge/closeprogressive deliveryauto-rollback on SLO breach
Promotion is a walk across environments gated by tests and one human approval. The clusters are downstream of Git — change the desired state, and Argo makes reality match.

The platform it rides on

None of the above works without the fleet-platform services underneath, and Cartwright consumes them rather than reinventing them:

Figure 6 · The platform prod account · Region us-east-1Cartwright prod VPCPublic subnetPrivate subnet · Karpenter nodesingressread / writeeventspull imagemetricsALB (ingress)ALB ControllerCartwright pods10 servicesEKS PodIdentityExternalSecretsKyvernopolicyKarpenternodesRDSorders, usersElastiCachesessionsDynamoDBcart, catalogS3assetsMSKeventsECRimagesAMP + AMGobservability
What the platform layer buys Cartwright: the team ships plain application code while Karpenter finds the nodes, EKS Pod Identity and ESO wire in AWS access and secrets, Kyverno holds the security baseline, the ALB fronts traffic, and the managed data stores — RDS, DynamoDB, ElastiCache, S3, and MSK — keep the state.

The complete picture

Now put the four layers on one canvas. A developer pushes a change; the app repo’s CI builds, tests, scans, and signs an image into ECR, then bumps the image tag in the GitOps config repo. The Argo CD hub — sitting in the shared platform account — reconciles that desired state into the dev, staging, and prod clusters in their separate accounts, gated by previews, canary tests, and one manual approval. And in every one of those clusters, Cartwright runs on the same platform: Karpenter, Pod Identity, External Secrets, Kyverno, the ALB Controller, and AMP/AMG. Read left to right, this is the entire lifecycle of a change.

Figure 7 · The complete picture The complete picture — one commit travels from a laptop to Cartwright running on the EKS platformdeveloper / appCI / promotionregistry + desired stateEKS environmentsplatform servicesDevelopergit push / PRApp repo CIbuild→test→scanAmazon ECRsigned + scannedGitOps config repoimage tag + valuesbump tagpush imageArgo CD hubreconciles fleetdev accountEKS cartwright-devauto-sync + PR previewsstaging accountEKS cartwright-stgcanary testsprod accountEKS cartwright-prodmanual approval • RolloutsEvery cluster rides the same EKS platformKarpenter nodesEKS Pod IdentityExternal SecretsKyverno policyALB ControllerAMP + AMGconsumes
One commit’s journey, end to end: laptop → CI → ECR and config repo → Argo CD → the EKS clusters per account → Cartwright running on the shared platform. The clusters are cattle; the platform is the product.

That’s Cartwright, whole. The value of seeing it on one page is that the boundaries become obvious: app code is separate from desired state is separate from infrastructure; isolation is strongest between environments and cheapest between services; and no change reaches production except by passing through Git and the gates on the way. Swap in your own service names and account layout and the shape holds — which is rather the point of a reference worth copying.

Currency: the EKS mechanisms named here (EKS Pod Identity as the default over IRSA, access entries over aws-auth, no SLA for Karpenter) are current as of 2026-07-01; version- and pricing-sensitive EKS facts roll forward roughly every four months, so re-verify against the primary sources before acting.

Sources

EKS mechanism, version, and pricing facts are current as of 2026-07-01 and should be re-verified against the primary source before acting.

  1. AWS — EKS Best Practices: use separate clusters for dev/test/prod rather than namespaces for hard environment isolation. docs.aws.amazon.com/eks/latest/best-practices
  2. AWS — Terraform EKS Blueprints and the GitOps bridge: IaC-generated metadata (IAM role ARNs) flows into the Argo CD cluster secret so charts consume real values. aws-ia.github.io/terraform-aws-eks-blueprints
  3. Karpenter: groupless, workload-aware autoscaling; AWS states there is no SLA for Karpenter. karpenter.sh
  4. AWS — EKS Pod Identities: the recommended default over IRSA for EKS-in-cloud. docs.aws.amazon.com/.../pod-identities.html
  5. AWS — EKS access entries: replace the deprecated aws-auth ConfigMap. docs.aws.amazon.com/.../access-entries.html
  6. Argo CD documentation: ApplicationSets and app-of-apps for GitOps-driven, multi-cluster delivery. argo-cd.readthedocs.io
  7. cosign (Sigstore): container image signing and verification in the supply chain. docs.sigstore.dev