<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: saurabh gupta</title>
    <description>The latest articles on DEV Community by saurabh gupta (@saurabh_gupta).</description>
    <link>https://dev.to/saurabh_gupta</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4060577%2F2ab26425-f572-4764-b6f9-2d42f9b8bb31.jpeg</url>
      <title>DEV Community: saurabh gupta</title>
      <link>https://dev.to/saurabh_gupta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saurabh_gupta"/>
    <language>en</language>
    <item>
      <title>ShopSphere — A Cloud-Native E-Commerce Platform</title>
      <dc:creator>saurabh gupta</dc:creator>
      <pubDate>Mon, 03 Aug 2026 12:11:15 +0000</pubDate>
      <link>https://dev.to/saurabh_gupta/shopsphere-a-cloud-native-e-commerce-platform-110c</link>
      <guid>https://dev.to/saurabh_gupta/shopsphere-a-cloud-native-e-commerce-platform-110c</guid>
      <description>&lt;p&gt;`&lt;/p&gt;

&lt;h1&gt;
  
  
  8 Things That Broke When I Deployed Kubernetes on AWS EKS Fargate (And How I Fixed Them)
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Building a production-shaped microservices platform from scratch — what the tutorials don't cover.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I spent 8 weeks building ShopSphere: a cloud-native e-commerce backend with 3 FastAPI microservices, deployed on Amazon EKS, monitored with Prometheus and Grafana, secured with GuardDuty and AWS Secrets Manager, and delivered by a GitHub Actions CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;The stack: Python 3.13 + FastAPI → Docker → ECR → EKS Fargate → RDS PostgreSQL → ALB → Terraform IaC → GitHub Actions.&lt;/p&gt;

&lt;p&gt;Everything I've written below is real. I didn't learn it from a tutorial — I learned it because it broke at 11pm and I had to figure out why.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why EKS Fargate (and the EC2 quota wall)
&lt;/h2&gt;

&lt;p&gt;The original plan was a standard EKS cluster with managed EC2 node groups. That plan died immediately.&lt;/p&gt;

&lt;p&gt;New AWS accounts start with an EC2 vCPU quota of 0 for several instance families. Both On-Demand and Spot were blocked. Requesting quota increases takes days and isn't guaranteed. The project couldn't wait.&lt;/p&gt;

&lt;p&gt;Fargate is the alternative: AWS runs each pod on its own dedicated microVM. No EC2 Auto Scaling Groups, no node management, no quota to hit. You pay per pod-second of CPU and memory rather than per instance.&lt;/p&gt;

&lt;p&gt;I migrated the cluster to Fargate. This was the right call. But Fargate has its own set of constraints that are scattered across AWS documentation, GitHub issues, and Stack Overflow threads — never in one place. Here's what I hit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 1: CoreDNS silently failing to schedule
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; After applying the Fargate profile and deploying, service name resolution failed cluster-wide. The ALB controller couldn't start. External Secrets Operator couldn't start. Prometheus couldn't scrape anything. Everything that needed to reach &lt;code&gt;some-service.namespace.svc.cluster.local&lt;/code&gt; just timed out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; Fargate uses a mutating admission webhook to intercept pod creation and inject Fargate-specific configuration. For a pod to be scheduled on Fargate, the webhook needs to process it. For the webhook to process it, the pod needs the annotation &lt;code&gt;eks.amazonaws.com/compute-type: fargate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;CoreDNS's default Kubernetes deployment doesn't have this annotation. So Fargate's webhook ignores CoreDNS pods, they never get scheduled, and they sit &lt;code&gt;Pending&lt;/code&gt; indefinitely. Since CoreDNS &lt;em&gt;is&lt;/em&gt; the cluster's DNS resolver, everything else fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
kubectl patch deployment coredns -n kube-system \&lt;br&gt;
  --type=json \&lt;br&gt;
  -p='[{"op":"add","path":"/spec/template/metadata/annotations/eks.amazonaws.com~1compute-type","value":"fargate"}]'&lt;br&gt;
kubectl rollout restart deployment/coredns -n kube-system&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is documented in AWS's EKS + Fargate guide but easy to miss when you're following a general EKS tutorial.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 2: Prometheus, AlertManager, Grafana all refuse to start
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; &lt;code&gt;helm install monitoring prometheus-community/kube-prometheus-stack&lt;/code&gt; completes without error. But all pods are stuck with &lt;code&gt;Pod not supported on Fargate: volumes not supported&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; &lt;code&gt;kube-prometheus-stack&lt;/code&gt;'s default Helm values request &lt;code&gt;PersistentVolumeClaims&lt;/code&gt; backed by EBS storage for Prometheus (metrics storage), AlertManager (alert state), and Grafana (dashboard state). Fargate pods cannot mount EBS volumes. At all. It's not a configuration issue — it's a fundamental architectural constraint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Switch to ephemeral in-pod storage for the entire monitoring stack:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  prometheus-values.yaml
&lt;/h1&gt;

&lt;p&gt;prometheus:&lt;br&gt;
  prometheusSpec:&lt;br&gt;
    storageSpec: {}         # no PVC — ephemeral storage&lt;br&gt;
    retention: 6h           # short retention; this is dev, not production&lt;/p&gt;

&lt;p&gt;alertmanager:&lt;br&gt;
  alertmanagerSpec:&lt;br&gt;
    storage: {}             # no PVC&lt;/p&gt;

&lt;p&gt;grafana:&lt;br&gt;
  persistence:&lt;br&gt;
    enabled: false          # no PVC&lt;br&gt;
  sidecar:&lt;br&gt;
    dashboards:&lt;br&gt;
      enabled: true         # load dashboards from ConfigMaps instead&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The monitoring stack becomes stateless by design. Prometheus re-scrapes from pod startup on restart. Grafana dashboards live in ConfigMaps — code-defined, version-controlled, no state to lose.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 3: External Secrets Operator webhook port collision
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; ESO installs successfully. The &lt;code&gt;SecretStore&lt;/code&gt; applies without error. But &lt;code&gt;ExternalSecret&lt;/code&gt; objects never sync — they stay in a permanent pending state. ESO pods show TLS errors in their logs: &lt;code&gt;x509: certificate is valid for [...], not for [fargate-node-ip]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; ESO's admission webhook runs on port 10250 by default. On Fargate, every pod runs in its own microVM that has its own kubelet — also on port 10250. When ESO registers its webhook with the Kubernetes API server, and the API server tries to call the webhook to validate ExternalSecret objects, it connects to what it thinks is the ESO webhook address but is actually the Fargate node's kubelet on that port. The TLS certificate ESO presents doesn't include the Fargate node's internal address in its SANs — hence the mismatch.&lt;/p&gt;

&lt;p&gt;This affects several Kubernetes webhook-based operators on Fargate: cert-manager, ADOT, and ESO all have open issues for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  In the ESO Helm values
&lt;/h1&gt;

&lt;p&gt;webhook:&lt;br&gt;
  port: 9443    # anything other than 10250&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 4: Fargate profile updates stranding pods permanently Pending
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; After adding a new namespace to the Fargate profile, some pods in existing namespaces get stuck &lt;code&gt;Pending&lt;/code&gt; and never schedule, despite the profile update completing successfully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; Fargate profiles are immutable — adding a namespace selector requires destroying and recreating the profile. AWS does this automatically during an update, but there's a brief window (a few seconds to a minute) where no Fargate profile is active. Any pod that gets created during this window goes through normal Kubernetes scheduling. The default scheduler tries to find an EC2 node — there are none. The pod sits &lt;code&gt;Pending&lt;/code&gt;. When the new profile comes back, &lt;strong&gt;it only evaluates pods at creation time, not retroactively&lt;/strong&gt;. Pods already stuck Pending with the wrong scheduler state never get reconsidered for Fargate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; After any Fargate profile update, find and delete all &lt;code&gt;Pending&lt;/code&gt; pods so they get recreated and scheduled correctly:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`bash&lt;br&gt;
kubectl get pods --all-namespaces | grep Pending&lt;/p&gt;

&lt;h1&gt;
  
  
  For each stuck pod:
&lt;/h1&gt;

&lt;p&gt;kubectl delete pod  -n &lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Automate this if you're doing frequent profile updates during setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 5: IRSA roles referenced but not created
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Pods start successfully. Health checks pass. But any AWS API call — reading from Secrets Manager, listing ECR images — fails with &lt;code&gt;AccessDenied&lt;/code&gt;. The pod appears healthy but is silently broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; I added IRSA annotations to the Kubernetes ServiceAccounts:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;yaml&lt;br&gt;
annotations:&lt;br&gt;
  eks.amazonaws.com/role-arn: "arn:aws:iam::123456789012:role/shopsphere-user-service-role"&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But I hadn't actually created that IAM role in Terraform yet. The annotation references a role that doesn't exist. Kubernetes applies the ServiceAccount fine. The pod starts fine. The EKS pod identity webhook injects the &lt;code&gt;AWS_ROLE_ARN&lt;/code&gt; and &lt;code&gt;AWS_WEB_IDENTITY_TOKEN_FILE&lt;/code&gt; environment variables correctly. The AWS SDK tries to assume the role — and gets &lt;code&gt;AccessDenied&lt;/code&gt; because the role doesn't exist.&lt;/p&gt;

&lt;p&gt;This is completely invisible until you actually make an AWS API call from inside the pod.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; For each service, add to Terraform:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;hcl&lt;br&gt;
resource "aws_iam_role" "user_service" {&lt;br&gt;
  name = "shopsphere-user-service-role"&lt;br&gt;
  assume_role_policy = jsonencode({&lt;br&gt;
    Version = "2012-10-17"&lt;br&gt;
    Statement = [{&lt;br&gt;
      Effect = "Allow"&lt;br&gt;
      Principal = {&lt;br&gt;
        Federated = "arn:aws:iam::${var.aws_account_id}:oidc-provider/${local.oidc_provider}"&lt;br&gt;
      }&lt;br&gt;
      Action = "sts:AssumeRoleWithWebIdentity"&lt;br&gt;
      Condition = {&lt;br&gt;
        StringEquals = {&lt;br&gt;
          "${local.oidc_provider}:sub" = "system:serviceaccount:shopsphere:user-service-sa"&lt;br&gt;
        }&lt;br&gt;
      }&lt;br&gt;
    }]&lt;br&gt;
  })&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Every Kubernetes ServiceAccount that needs AWS permissions needs a corresponding IAM role and OIDC trust policy in Terraform. No exceptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 6: read-only root filesystem breaking container startup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; After adding &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt; and &lt;code&gt;capabilities: drop: [ALL]&lt;/code&gt; to the pod security context, pods fail to start with permission errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; The original Dockerfile used a CMD pattern that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;chown&lt;/code&gt;-ed the application directory at container start&lt;/li&gt;
&lt;li&gt;Used &lt;code&gt;su&lt;/code&gt; or &lt;code&gt;gosu&lt;/code&gt; to drop from root to the app user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both require either write access to the filesystem (for chown) or Linux capabilities that we just dropped (for su/gosu). With &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt; and no capabilities, the container can't start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Move all ownership-setting to Dockerfile build time:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`dockerfile&lt;/p&gt;

&lt;h1&gt;
  
  
  In the runtime stage, before switching to appuser:
&lt;/h1&gt;

&lt;p&gt;RUN adduser --disabled-password --no-create-home appuser &amp;amp;&amp;amp; \&lt;br&gt;
    chown -R appuser:appuser /app&lt;/p&gt;

&lt;p&gt;USER appuser&lt;/p&gt;

&lt;h1&gt;
  
  
  CMD just starts the app — no chown, no su needed
&lt;/h1&gt;

&lt;p&gt;CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For volumes that need write access at runtime (temp files, SQLite), use &lt;code&gt;emptyDir&lt;/code&gt; volumes in the pod spec and configure &lt;code&gt;fsGroup&lt;/code&gt; in the security context — Kubernetes handles the ownership:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`yaml&lt;br&gt;
securityContext:&lt;br&gt;
  fsGroup: 1000    # Kubernetes chowns volume mounts to this GID at pod start&lt;br&gt;
volumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name: tmp
emptyDir: {}
`&lt;code&gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Problem 7: Diagnosing an account-level ELB restriction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Creating a LoadBalancer-type Service fails. The AWS Load Balancer Controller logs show &lt;code&gt;OperationNotPermitted&lt;/code&gt;. IAM permissions look correct. Quotas look fine. No relevant errors in CloudTrail except the failure itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; Some AWS accounts have account-level API restrictions applied that are distinct from IAM permissions and service quotas. These appear in the raw API response but not in the kubectl error summary. The pattern: &lt;code&gt;OperationNotPermitted&lt;/code&gt; rather than &lt;code&gt;AccessDenied&lt;/code&gt; or &lt;code&gt;LimitExceeded&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This required opening an AWS Support case. It's not a configuration mistake. Attempting to work around it through configuration changes wastes time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workaround while the Support case resolves:&lt;/strong&gt; &lt;code&gt;kubectl port-forward&lt;/code&gt; for Grafana and AlertManager access. This is actually more secure — no public LoadBalancer for the monitoring stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Read the full raw API error response, not just the kubectl summary. &lt;code&gt;OperationNotPermitted&lt;/code&gt; and &lt;code&gt;AccessDenied&lt;/code&gt; have different root causes and require different responses. Knowing which one you're looking at tells you whether to keep debugging configuration or open a Support case.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 8: Fargate profile namespace selectors and webhook timing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; After applying a new Fargate profile that should cover a new namespace, pods in that namespace still don't schedule. They show &lt;code&gt;0/0 nodes available&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; A Fargate profile only schedules pods that match its namespace + label selectors, but the profile has to exist &lt;em&gt;before&lt;/em&gt; the pod is created. If you apply the profile and then immediately apply the namespace and deployment in the same &lt;code&gt;kubectl apply -f&lt;/code&gt;, there's a race — the pods may be created before the profile is fully active.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Add a sleep or confirmation step:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Wait for the Fargate profile to be ACTIVE before deploying
&lt;/h1&gt;

&lt;p&gt;aws eks wait fargate-profile-active \&lt;br&gt;
  --cluster-name shopsphere-cluster \&lt;br&gt;
  --fargate-profile-name shopsphere-fp&lt;/p&gt;

&lt;h1&gt;
  
  
  Then deploy
&lt;/h1&gt;

&lt;p&gt;kubectl apply -f k8s/base/&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What the finished system looks like
&lt;/h2&gt;

&lt;p&gt;After solving all of the above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git push origin main&lt;/code&gt; → 4 GitHub Actions jobs (test → scan → approve → deploy) → new version live in EKS with zero manual steps&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;curl http://ALB_URL/health&lt;/code&gt; → 200 from all 3 services&lt;/li&gt;
&lt;li&gt;Grafana shows p95 latency, request rate, error rate, and pod health in real time&lt;/li&gt;
&lt;li&gt;AlertManager sent a real email when I deliberately triggered an error spike, and a resolution email when I fixed it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl get externalsecret -n shopsphere&lt;/code&gt; → &lt;code&gt;SecretSynced&lt;/code&gt; — DB credentials come from Secrets Manager, not a YAML file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl get networkpolicies -n shopsphere&lt;/code&gt; → 5 policies, default-deny enforced&lt;/li&gt;
&lt;li&gt;GuardDuty and CloudTrail monitoring the account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is at &lt;a href="https://github.com/saurabhg4356/shopsphere" rel="noopener noreferrer"&gt;github.com/saurabhg4356/shopsphere&lt;/a&gt; with full source, architecture diagram, and setup instructions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd tell someone starting this today
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set up a AWS budget alert before your first &lt;code&gt;terraform apply&lt;/code&gt;.&lt;/strong&gt; The NAT Gateway is always running and costs money even when nothing is happening.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Read the Fargate-specific documentation, not just the EKS documentation.&lt;/strong&gt; The two have meaningfully different constraints and the Fargate docs are more scattered.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;When something fails, read the full raw API error.&lt;/strong&gt; &lt;code&gt;kubectl describe&lt;/code&gt; gives summaries. The raw API response — in CloudTrail, in pod events, in controller logs — tells you the actual error code, which often tells you exactly what category of problem you're dealing with.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Terraform modules from the start.&lt;/strong&gt; I built a flat structure first and refactored it into modules. It's much easier to start modular than to extract modules from a flat structure later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The hard problems aren't the code.&lt;/strong&gt; FastAPI is simple. Docker is simple. Kubernetes YAML is tedious but learnable. The genuinely hard part is the system-level debugging — when six different components interact and only one of them is wrong, and that one has a misleading error message.&lt;br&gt;
`&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>kubernetes</category>
      <category>docker</category>
      <category>aws</category>
      <category>python</category>
    </item>
  </channel>
</rss>
