ECS Fargate cost optimization for startups starts with one fact: Fargate bills the CPU and memory you declare, not what your containers actually consume. A task declared with 1 vCPU and 2 GB is paying for 1 vCPU and 2 GB every second it runs — whether it's handling 1,000 requests per minute or sitting idle at 3am on a Tuesday.
Most startup ECS bills are 2–3x higher than they need to be. The fixes aren't complicated, but they need to happen in the right order.
This guide is written for startups with 2–10 ECS services — the kind of setup you have after migrating from Railway or Render. Not for teams managing 50-service fleets with dedicated FinOps engineers.
What Fargate Actually Costs (The Math You Need)
Before optimizing, know the numbers. In us-east-1 (May 2026):
| Resource | On-Demand (x86) | Graviton (ARM) | Fargate Spot (x86) | |---|---|---|---| | vCPU-hour | $0.04048 | $0.03238 | ~$0.01214 | | GB-hour | $0.004445 | $0.003560 | ~$0.001334 |
A single service running 0.5 vCPU / 1 GB on-demand costs:
0.5 × $0.04048 + 1 × $0.004445 = $0.02468/hour
× 730 hours/month = ~$18/month per service
At 5 services, you're at ~$90/month just in compute. Add dev and staging environments running the same 5 services 24/7, and you're at $270/month before you've shipped a single feature.
That's the number worth cutting.
The Fix Order That Actually Matters
Most cost guides hand you a list of 8 things to do without telling you which one cuts the most. Here's the honest order for a startup:
- Stop dev/staging environments outside business hours — biggest lever, zero risk
- Right-size over-provisioned task definitions — typically 30–50% CPU savings
- Switch to Graviton (ARM) — flat 20% off with minimal effort
- Move dev/staging to Fargate Spot — 60–70% off those environments
- Enable Compute Savings Plan — commit after you've done 1–4, not before
Don't skip ahead to Savings Plans. Committing to a spend rate before right-sizing locks in waste.
Fix 1: Stop Paying for Dev/Staging at 3am
Your dev environment runs 168 hours a week. Your team works maybe 50 of them. You're paying for 118 hours per environment where no one is using it.
Business-hours scheduling — Mon–Fri 9am–7pm — brings 24/7 runtime down to about 50 hours per week, or 30% of the full-time baseline. On a 5-service dev environment:
5 services × $18/month = $90/month (24/7)
→ $27/month at business hours only
→ $63/month saved per environment
Two dev environments and one staging: that's ~$189/month saved before touching a single task definition.
How to implement it:
Create two EventBridge Scheduler rules — one to scale services down to 0 at night, one to scale them back up in the morning. Here's the pattern for a single service:
# Scale down — runs Mon-Fri at 7pm UTC (adjust for your timezone)
aws scheduler create-schedule \
--name ecs-scale-down-dev \
--schedule-expression "cron(0 19 ? * MON-FRI *)" \
--target '{"Arn": "arn:aws:scheduler:::aws-sdk:ecs:updateService", "RoleArn": "YOUR_ROLE_ARN", "Input": "{\"Cluster\": \"your-cluster\", \"Service\": \"your-service\", \"DesiredCount\": 0}"}' \
--flexible-time-window '{"Mode": "OFF"}'
Repeat for each service. If you have 10+ services across multiple environments, this gets tedious — NoahOps automates environment scheduling so you set one schedule per environment rather than one rule per service.
Important: Scale back up 10–15 minutes before your team's start time. Fargate takes time to pull images and pass health checks. Schedule the start at 8:45am if your team starts at 9am.
Fix 2: Right-Size Your Task Definitions
The second-biggest mistake after leaving environments on 24/7: declaring 1 vCPU for a service that uses 10% CPU on a normal day.
You can't right-size what you can't see. First, enable AWS Compute Optimizer (free) and give it 14 days to collect CloudWatch metrics. Then check CPU and memory utilization percentiles per service.
The rule: if your p99 CPU utilization is below 40% of your declared vCPU, you're over-provisioned and safe to drop down.
Common drop pattern:
| Declared | Typical actual usage | Drop to | CPU cost saving | |---|---|---|---| | 1 vCPU | 10–20% | 0.5 vCPU | −50% | | 0.5 vCPU | 10–15% | 0.25 vCPU | −50% | | 2 GB | 400–600 MB used | 1 GB | −50% |
Right-sizing requires a new task definition revision and a service update, but the rolling deployment has no downtime if you're running ≥2 tasks. Safe to do during business hours.
One exception: don't right-size production without load testing first. Dev and staging are safe targets; production needs evidence from your actual traffic pattern under load.
Fix 3: Migrate to Graviton (ARM) for a Flat 20% Discount
AWS Graviton (ARM architecture) tasks cost 20% less per vCPU-hour and per GB-hour than equivalent x86 tasks. For most startup workloads — Node.js, Python, Go, Java — you only need to rebuild your Docker image.
# Before (default x86)
FROM node:20-alpine
# After (explicit ARM)
FROM --platform=linux/arm64 node:20-alpine
Build the ARM image:
docker buildx build --platform linux/arm64 -t your-image:arm64 .
docker push your-ecr-repo:arm64
Update your task definition to set runtimePlatform.cpuArchitecture to ARM64, then redeploy.
Two things to verify before switching production:
- Run your test suite on the ARM image first
- If your app uses any native C extensions (some Python packages), test those specifically
Most pure Node/Python/Go apps migrate without touching a single line of application code. Combined with Fargate Spot for dev, ARM Spot tasks cost up to 76% less than x86 on-demand.
Fix 4: Move Dev/Staging to Fargate Spot
Fargate Spot runs tasks on spare AWS capacity at up to 70% off. The tradeoff: AWS can reclaim tasks with a 2-minute SIGTERM warning.
For dev and staging, 2-minute interruptions are fine. For production, they're not — unless your services handle SIGTERM gracefully and you have auto-recovery in place.
To enable Spot for a service, update the capacity provider strategy:
{
"capacityProviderStrategy": [
{
"capacityProvider": "FARGATE_SPOT",
"weight": 4,
"base": 0
},
{
"capacityProvider": "FARGATE",
"weight": 1,
"base": 1
}
]
}
The base: 1 on FARGATE ensures at least one on-demand task is always running — which AWS requires to use Spot as the primary provider. The 4:1 weight ratio puts ~80% of your tasks on Spot.
Also add stopTimeout: 120 to your task definition container config. This gives the container the full 2 minutes to drain connections before hard shutdown.
One gotcha from the field: services with high CPU/memory allocations are more likely to be interrupted when Spot capacity is constrained. If your auth service keeps getting killed and breaking everything, constrain it to the same 0.25 vCPU / 0.5 GB as your other dev tasks so AWS randomizes interruptions rather than targeting your highest-resource container.
Fix 5: Savings Plans — Buy After Optimizing, Not Before
Compute Savings Plans commit you to a consistent $/hour of compute in exchange for 36–52% off Fargate on-demand rates. The discount applies automatically to ECS, Lambda, and EC2 — no infrastructure changes.
The catch: a Savings Plan commitment locks in your spend rate. If you buy before right-sizing, you're locking in payment for over-provisioned tasks. Do fixes 1–4 first, wait one billing cycle, then look at Savings Plan recommendations in Cost Explorer.
Starting point: commit to 70–80% of your consistent baseline compute spend. Leave headroom for spikes. A 1-year no-upfront plan saves ~36% on what it covers — enough to justify the commitment without locking in too much.
The Hidden Cost: ECR Image Storage
One frequently missed bill item: ECR charges for stored images. If you deploy 20 times a day and keep every image, your ECR repository accumulates images fast. A year of continuous deployments on a 500 MB image = terabytes.
Set lifecycle policies on every repository:
aws ecr put-lifecycle-policy \
--repository-name your-repo \
--lifecycle-policy-text '{"rules":[{"rulePriority":1,"description":"Keep last 20 images","selection":{"tagStatus":"any","countType":"imageCountMoreThan","countNumber":20},"action":{"type":"expire"}}]}'
Keeping the last 20 images is usually enough for rollback purposes. This won't cut your compute bill, but teams have reported cutting total AWS spend by 5% from ECR cleanup alone.
The Hidden Cost: NAT Gateway
If your ECS tasks run in private subnets (which they should — don't run tasks with public IPs), outbound traffic goes through a NAT Gateway. NAT Gateway costs $0.045/GB of data processed, on top of the hourly charge.
For most startups with moderate traffic, NAT Gateway fees are manageable. But if you're seeing unexpected data transfer charges, check: are your ECS tasks hitting the internet frequently? Package downloads during task startup, logging to external services, frequent health check pings to external URLs all flow through the NAT.
For cost-sensitive teams: a self-managed NAT instance on a t4g.nano (~$3/month) costs far less than a NAT Gateway once you're processing more than ~100 GB/month. It requires more setup and you own the availability, but the trade-off is often worth it at startup scale.
What This Looks Like in Practice
Typical startup starting point: 3 environments (prod, staging, dev), 5 services each, 0.5 vCPU / 1 GB per task, all x86, all on-demand, all running 24/7.
Before:
15 tasks × 0.5 vCPU × $0.04048 × 730 hrs = $221/month (CPU)
15 tasks × 1 GB × $0.004445 × 730 hrs = $49/month (memory)
Total: ~$270/month
After fixes 1–4 on dev + staging:
- Dev and staging (10 tasks) on business-hours scheduling → 70% reduction on those
- All 15 tasks right-sized to 0.25 vCPU → 50% CPU reduction
- All 15 tasks migrated to Graviton → additional 20% off
- Dev + staging on Fargate Spot → additional 70% off those environments
After:
- Prod (5 tasks, Graviton, right-sized): ~$45/month
- Dev+staging (10 tasks, Graviton, Spot, business hours): ~$18/month
- Total: ~$63/month
That's a 77% reduction from the same 15 services. No code changes. No architecture changes. One afternoon of configuration work.
Where NoahOps Fits In
NoahOps manages all of this for you at the platform level. When you deploy through NoahOps, task definitions are right-sized based on observed utilization, environments have built-in scheduling controls, and Graviton is available as a one-click option per service. You don't need EventBridge rules, separate task definitions per environment, or manual cost audits in Cost Explorer.
Request a free demo at noahops.com to see how the cost controls work in practice.
FAQ
Does Fargate Spot work for production services?
Sometimes. Stateless services that handle SIGTERM gracefully and have auto-recovery can run on Spot in production with a small on-demand base. Services with stateful in-memory state, external database connections that don't handle reconnects, or guaranteed SLA requirements should stay on on-demand.
Does right-sizing require downtime?
No. Changing task CPU/memory requires a new task definition revision and a service update, but ECS does this as a rolling deployment. If you have ≥2 tasks running, there's no downtime.
When should I buy a Savings Plan?
After you've done right-sizing and Graviton migration. Give it one billing cycle to stabilize, then look at the recommendations in Cost Explorer. Buying before optimizing commits you to wasteful spend.
What's the fastest first win?
Environment scheduling on dev and staging. Set up two EventBridge rules per environment, and you'll see the savings in the next billing cycle. No task definition changes, no deployments, no risk to production.