How EC2 Auto Scaling groups, ECS service auto scaling, target tracking and scheduled scaling work for a backend, where cost leaks, and how to run services that scale in your own AWS account from application code.
AWS scaling is how a backend keeps up with traffic without you editing desired counts by hand. On virtual machines that is an EC2 Auto Scaling group. On containers it is ECS service auto scaling through Application Auto Scaling. Both lean on target tracking, and both burn money when minimums, metrics or cooldowns are wrong.
This guide covers the controls that matter for a product API, the cost mistakes that show up in the bill, and how Encore (infrastructure from code) runs those services in your account so sizing lives next to the app.
An Auto Scaling group keeps a fleet of instances between a minimum and maximum size, using a launch template for AMI, instance type and user data. Target tracking is the default policy shape: you set a target such as 50 percent average CPU, and Auto Scaling creates the CloudWatch alarms that add or remove instances.
Predefined metrics include ASGAverageCPUUtilization, network in/out, and ALBRequestCountPerTarget. For ALB request count you pass a ResourceLabel that identifies the target group. Target tracking prioritizes availability: it scales out if any policy says so, and scales in only when every policy with scale-in enabled agrees. New instances have a warmup period during which they do not count toward the metric, which stops the group from thrashing during deploys.
Step scaling still exists when you need fixed increments at alarm thresholds. Most HTTP backends start with target tracking on CPU or requests per target and only reach for step policies when the metric is spiky or queue-backed.
ECS does not scale by replacing instances when you use Fargate. It changes how many tasks the service should run. Application Auto Scaling supports target tracking, step scaling, scheduled actions, predictive scaling and custom backlog-per-task metrics for SQS workers.
ECS publishes average CPU and memory utilization every minute. Target tracking on CPU is the usual starting point for an API. For workers, a custom metric of queue depth divided by running tasks tracks load better than CPU, because an idle poller looks quiet while the backlog grows. You can set minimum capacity to 0 when a worker should disappear with an empty queue; target tracking then waits for a data point before scaling out from zero.
During a deployment, Application Auto Scaling turns off scale-in but leaves scale-out active, so a rollout does not shrink the service under you. Cooldown periods stop a noisy metric from adding tasks every minute. On the EC2 launch type you also need cluster capacity (a capacity provider and an ASG) or tasks sit pending with nowhere to land.
Scheduled actions set min, max or desired capacity at a wall-clock time. Application Auto Scaling uses a six-field cron expression and an IANA time zone, for example every weekday at 08:00 before business traffic. EC2 Auto Scaling has its own scheduled action API with classic cron syntax.
Scheduled scaling fits known peaks: markets open, nightly batch windows, marketing launches. It fails when the calendar is wrong or traffic shifts and nobody updates the schedule. Pair it with target tracking so an unexpected spike still gets capacity, and set the schedule's floor high enough that cold starts or task pull time do not eat the first minutes of the peak.
Minimum capacity is a common hidden cost: an ASG with min=3 or an ECS service with desired=3 never returns to zero at night. Target values that are too low (for example 20 percent CPU) keep extra instances warm "just in case" and look like reliable scaling until finance asks why staging costs as much as production.
Other common leaks: detailed monitoring and high-resolution metrics you forgot you enabled, scale-out without a matching scale-in policy, Fargate tasks oversized on CPU or memory relative to actual p95 usage, and separate Auto Scaling paths for every microservice that could have shared a process. Predictive scaling and multiple overlapping policies can also fight each other if two metrics disagree about load.
Encore combines an open source infrastructure SDK for TypeScript and Go with a platform that provisions and deploys into your own AWS or GCP account. On AWS, services run on Fargate (or EKS if you choose it) behind an Application Load Balancer. Encore creates the task definitions, services and networking from what the code declares, and Encore provisions an IAM role for the service.

Run in your terminal to get started locally.
A small API with Redis-backed caching is enough to see the shape. The scaling knobs for the database and how services share processes live in the environment, not in a second Terraform stack:
// api/encore.service.ts
import { Service } from "encore.dev/service";
export default new Service("api");
// api/api.ts
import { api } from "encore.dev/api";
import { CacheCluster, IntKeyspace } from "encore.dev/storage/cache";
const cluster = new CacheCluster("api-cache", {
evictionPolicy: "allkeys-lru",
});
const hits = new IntKeyspace<{ path: string }>(cluster, {
keyPattern: "hits/:path",
});
export const ping = api(
{ expose: true, method: "GET", path: "/ping" },
async (): Promise<{ ok: boolean; pathHits: number }> => {
const pathHits = await hits.increment({ path: "ping" }, 1);
return { ok: true, pathHits };
},
);
encore run starts the service with a local in-memory cache so you can exercise the endpoint before deploying, as described in the caching docs. On deploy, Encore provisions the Fargate service, the load balancer path and ElastiCache for Redis in your account, and provisions an IAM role for the service. Connecting the account is covered in the own-cloud guide. In the infrastructure configuration UI you pick compute (Fargate or EKS), database sizing and process allocation: one process for all services, or one process per service. One process cuts idle cost and latency between services; separate processes isolate blast radius in production.
Encore updates resources with PATCH-style changes that modify only the attributes that need to change. Before changing a resource it reads the current properties, and when a setting was changed directly in the cloud console it updates its own representation to match, unless a change requested in the dashboard is still pending, as described in managing infrastructure.
An AI coding agent can add the cache or a second service in the same change and leave cloud sizing to the environment settings you already reviewed.
Best fit: Teams who want backends on Fargate in their own AWS account, with resource sizing and process allocation in the Encore dashboard.
AWS Auto Scaling adjusts capacity to match load. On EC2 that means an Auto Scaling group changing the number of instances. On ECS it means Application Auto Scaling changing the desired task count for a service. Target tracking keeps a metric near a value you set, such as average CPU or ALB requests per target.
Target tracking is a policy where you pick a metric and a target value, and AWS creates and manages the CloudWatch alarms that scale out or in to hold that average. On EC2 Auto Scaling, common metrics include ASGAverageCPUUtilization and ALBRequestCountPerTarget. On ECS, services commonly track average CPU or memory utilization.
Register the service as a scalable target with Application Auto Scaling, then attach a target tracking, step, scheduled or predictive policy. ECS publishes CPU and memory utilization metrics every minute. Set a minimum and maximum task count so scale-in cannot drop below what you need for availability.
With Encore, define services with api() and deploy to an AWS environment on Fargate. Encore creates the Fargate services and load balancer in your account. Size databases and choose process allocation per environment in the dashboard. Settings changed directly in the AWS console are detected as drift before the next change is applied.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.