MicroTodoSuite is a five-service distributed system delivered through a fully automated, digest-pinned, signature-verified GitOps pipeline onto AWS EKS. This is a record of the architecture decisions behind it, the security and delivery mechanics that carry every change into a cluster, and verifiable evidence of the platform's current state.
A distributed to-do application used as a vehicle to engineer a real platform: multiple languages, one contract, one delivery mechanism, and a security and observability posture that holds regardless of which service is changing.
Each service is a separate repository with its own release cadence, contract tests, and CI caller, coordinated only through published API contracts and a shared delivery mechanism — never through shared code or a shared deploy step.
auth-api — Go, JWT issuancetodos-api — Node.js, todo domain, Redis-backed eventsusers-api — Java 17 / Spring Boot 3log-message-processor — Python, Redis stream consumerfrontend — Vue 3Application code, delivery mechanism, infrastructure, shared CI/CD, and documentation are deliberately split so that each has its own review ownership and blast radius.
microservice-app-gitops — ArgoCD source of truthmicroservice-app-ops — Terraform-managed AWS infrastructure.github — reusable, SHA-pinned CI/CD workflowsmicroservice-app-docs — architecture decisions and statusmicroservice-app-slack-approval-gateway — human-in-the-loop gateEvery non-trivial change starts as a written specification — problem statement, acceptance scenarios, and a task breakdown — reviewed before implementation begins, and traced back to it in the pull request that closes it.
spec.md — problem and acceptance criteriaplan.md — technical approach and trade-offstasks.md — traceable, individually verifiable units of workEach record below states the problem being solved, the options that were on the table, what was chosen, and the reasoning — not the order in which the work happened.
Rather than design one architecture and scale it up later, the platform was specified as two explicit profiles from the start, so that the cost/complexity trade-off is a deliberate, documented choice rather than an accident of what got built first.
| Dimension | Economical profile | Full profile |
|---|---|---|
| Cluster topology | 1 EKS cluster, namespaces as environments | 3 dedicated EKS clusters (dev/staging/prod) |
| Service mesh | None — direct in-cluster networking | Istio mesh, mTLS between services |
| Log/search backend | Loki | ECK-managed Elasticsearch |
| Monthly cost profile | Low — single control plane, small nodes | High — three control planes, mesh sidecars, ECK |
| Status | live | live |
Both profiles have run end to end. The economical profile was
validated on 2026-09-14, quiesced the next day on cost discipline, and has been brought back
online since 2026-09-21. The full profile's AWS foundations were applied the same day
(2026-09-14) and re-upped on 2026-09-21; ArgoCD is bootstrapped and verified healthy on all
three dedicated clusters (fdev/fstg/fprd) as of
2026-09-22 — every capability it adds (mesh, per-environment isolation, ECK) ships with test
coverage in addition to that infrastructure now standing.
Rebuilding an image per environment risks promoting something that was never actually tested — a dev build and a prod build of "the same" tag can differ.
An image is built exactly once per change, scanned, signed, and referenced everywhere after that by its SHA-256 digest — never by a mutable tag. Promotion is a Kustomize overlay update pointing to that same digest in the next environment.
Imperative kubectl patch/scale fixes drift from what's in
Git, and the next reconciliation silently reverts a hand-applied fix — a classic GitOps
failure mode.
No hand-applied mutation to managed state. A correction is a commit; a rollback is a
git revert. The single documented exception is an audited bootstrap boundary
for standing up ArgoCD itself, which cannot reconcile before it exists.
One EKS cluster, ArgoCD-reconciled, with namespaces standing in for environments and every dependency declared as a Kustomize root ArgoCD owns.
Fig. 3 — the five services inside one environment's namespace: who calls whom, over which port, and what nothing keeps durable. Click to open full size.
Fig. 4 — the real AWS layout: one EKS cluster, three availability zones, one shared Application Load Balancer, four namespace-environments. Validated 2026-09-14. Click to open full size.
Fig. 5 — how a single cluster still keeps four environments apart: default-deny NetworkPolicy, per-namespace quotas, and IRSA-scoped secret access, all identical across environments. Click to open full size.
The full profile trades that single cluster for three isolated ones, a real service mesh, and an Azure recovery domain for production — built to the same level of detail, and now standing:
One reusable pipeline definition, pinned by commit SHA, called by a thin per-service caller workflow — so every service inherits the same security and promotion behavior without re-implementing it.
.github)ci.yml — build, unit and contract tests, static analysisrelease.yml — scan, SBOM, sign, publish to ECRpromote.yml — opens the digest-bump pull request into gitopsstack-tests.yml — integration/E2E against the composed stackEvery per-service caller pins these by commit SHA, not by tag or branch — a compromised or force-pushed upstream workflow cannot silently change what runs in a service's pipeline.
No static AWS or registry credential is stored anywhere in CI. Every job that needs AWS access requests a short-lived token via GitHub's OIDC identity provider, scoped to that repository and workflow by an IAM trust policy.
*_wo) carry secrets through a single plan/apply invocation and are never written to state.Fig. 7 — from a pull request to a production canary: one image, built once, promoted by digest through dev, staging and a human-gated prod. Click to open full size.
Fig. 8 — promotion never rebuilds and never moves a tag: one pull request per environment changes exactly one overlay's digest, and production waits for a human. Click to open full size.
Fig. 9 — the production canary decides for itself: Argo Rollouts shifts traffic in steps, gated by an AnalysisRun that queries real Prometheus error rate, not a synthetic probe. Click to open full size.
Screenshots taken directly from the running GitHub Actions, Slack, and GitOps pull request surfaces — not staged, not rendered for this page.
The full ci → release → promote-dev → promote-staging → gate-prod → promote-prod chain, green end to end, with the signed image landing in ECR.
A promotion pull request changing exactly one digest in the prod overlay — nothing else in the diff.
Security is enforced at two points: what is allowed to be built and signed, and what is allowed to run — the second does not trust the first without re-checking it.
Fig. 10 — a commit becomes a signed image: the reusable CI workflow builds once, and nothing downstream ever rebuilds it. Click to open full size.
Fig. 11 — a secret reaches a pod without touching Git: the cluster reads AWS Secrets Manager with an identity bound to the pod's own ServiceAccount. Click to open full size.
auth-api-secrets/JWT_SECRET in-cluster.Trivy Operator's own Grafana dashboard, not a one-off CLI scan.
A service earns metrics, traces, and structured logs by being deployed — it does not integrate a vendor SDK to get them.
Prometheus scrapes every service; Grafana dashboards are provisioned as code alongside the services they chart, including request-latency histograms with explicit bucket boundaries validated per service.
Jaeger in the economical profile; the full profile's design carries the same trace propagation through an Istio mesh, so a request can be followed end to end without any service authoring its own tracing glue.
Loki aggregates structured logs in the economical profile. The full profile's design swaps in ECK-managed Elasticsearch for higher query volume and longer retention, without changing what a service has to emit.
Fig. 12 — metrics, traces and logs from every workload to Grafana, plus the Alertmanager path to Slack for error-rate, latency and vulnerability alarms — a separate channel from the approval gateway below. Click to open full size.
Screenshots from Grafana, Prometheus and Jaeger against the reactivated eks-dev
cluster — not mockups.
The business-workload golden-signals dashboard for auth-api: traffic, error rate, and latency, provisioned as code.
The same dashboard's business panels: todos created/deleted and sign-ins by outcome, not just infrastructure metrics.
Unit tests verify logic; contract, integration, and end-to-end tests verify that five independently deployed services still agree with each other.
Per-service, per-language, run on every pull request as the fastest gate in ci.yml.
Spectral lints published OpenAPI specs; Pact verifies consumer-driven contracts between the frontend and each API it calls; Schemathesis fuzzes API implementations against their own schema.
Testcontainers spins up real dependencies (Redis, databases) rather than mocking them — a lesson carried from a prior incident where a mocked dependency masked a real migration failure.
Playwright drives the composed stack through stack-tests.yml, exercising the
actual login → create-todo → view flow across all five services.
Locust load-tests the composed stack to establish baseline latency and throughput before a capacity-affecting change ships.
OWASP ZAP scans the running stack for exploitable behavior that static analysis and unit tests structurally cannot see.
Every feature is specified before it is implemented, and the specification's task list is what a pull request is measured against — not a green check or a rendered manifest. A task is marked complete only after its named artifact is located and inspected.
Short-lived branches, one concern per branch, Conventional Commit titles with a scope. The failing-test commit that specifies a change is committed before the implementation that makes it pass, and the pair is never squashed — it is the recorded evidence the cycle was followed.
CODEOWNERS ties path ownership to required reviewers. No agent — human-directed or
automated — merges with an admin override, force-pushes to main, or approves
its own pull request. An automation identity may open and describe a change; only a named
human unlocks the gate.
When a specification and reality disagree — a pinned version nobody actually shipped, a task register that doesn't match the code — the fix is a recorded reconciliation decision, not a quiet edit that erases the discrepancy.
Every change to any cluster passes through a required human review on the GitOps pull request. That review used to require someone to be inside GitHub to act on it; it does not anymore.
environment: prod approval each service's
own pipeline enforces) get a rich Slack notification with a direct link, since that gate's
human-identity requirement cannot be satisfied by an app — GitHub's own approval UI is kept
exactly as it is.Every figure below comes from Infracost run against the exact Terraform that was actually applied — not a spreadsheet estimate — and every pull request that changes infrastructure carries its own cost delta into the same Slack message a reviewer approves from.
| Dimension | Economical profile | Full profile | Both, running together |
|---|---|---|---|
| Monthly cost | Priced per change, not as one total (see FinOps below) | USD 617.20 — Infracost-verified against the applied plan | about USD 930 — on-demand list price, not Infracost-verified |
| EKS control planes | 1 | 3 | 4 |
| NAT gateways | 3 — one per AZ | 2 — shared egress hub | 5 |
| Bootstrap nodes | 2 | 3 — one per cluster | 5 |
| Transit Gateway attachments | 0 | 3 | 3 |
Component counts and the combined list price come from microservice-app-docs/full-platform/capacity-and-regions.md. The full profile's own total below is the one figure on this page Infracost verified against a real applied plan.
| Root | Scope | Real monthly cost (Infracost) |
|---|---|---|
Hub networking (shd) | Shared egress and Transit Gateway hub, one per account | USD 73.00 |
| Spoke networking × 3 | fdev, fstg, fprd — one VPC each | USD 36.50 each — USD 109.50 total |
| Workload × 3 | fdev, fstg, fprd — one EKS cluster and node group each | USD 144.90 each — USD 434.70 total |
| Total, full-profile foundations | Applied 2026-09-14, re-upped 2026-09-21 | USD 617.20 / month |
USD 142.43
USD 617.20
USD 7,406.40
This is the AWS foundation layer actually standing today for the full profile: three isolated networks, three EKS control planes and node groups, and the shared egress hub — verified by Infracost against the exact saved Terraform plan for each root, not a list-price guess. It does not yet include Istio, ECK-managed Elasticsearch, or any business workload, because none of those are deployed (see State). It also does not include the economical profile's own spend, which the evidence does not roll up into one figure — every change to it is priced individually by the mechanism below instead.
A Terraform change is priced before it is reviewed, not after it is merged.
aws-profile-lifecycle.sh in microservice-app-ops is the only way the
runtime is created or destroyed. Applies use a saved plan and nothing else, and the way down
needs no GitOps pull request.
Fig. 13 — exactly how the economical runtime is quiesced and rebuilt: snapshot receipts, checksummed evidence, and applies that use a saved plan and nothing else. Click to open full size.
ArgoCD's own Applications view after the 2026-09-21 reactivation — every business and
infrastructure Application the economical profile owns, filtered to Healthy.
The root argocd app-of-apps and the business services' four environments — 34 Applications reporting Healthy.
The infrastructure Applications ArgoCD reconciles on its own: Argo Rollouts, cert-manager, the AWS Load Balancer Controller, Grafana, KEDA.
Three engineers, three areas of ownership — infrastructure, observability and security, and CI/CD and delivery — coordinated through the same specifications this page describes.