Guide · AWS Fargate Spot · ECS Cost Optimization

AWS Fargate Spot for MCP Server Probe Workers: 70% Cost Savings

AWS Fargate Spot lets you run ECS tasks on spare AWS capacity at up to 70% less than On-Demand Fargate pricing — making it ideal for the stateless, idempotent probe workers that power MCP server uptime monitoring. An MCP monitoring fleet that costs $50/month on Fargate On-Demand can cost $15/month on Fargate Spot with the right capacity provider configuration and a two-minute SIGTERM handler. This guide explains the Fargate Spot pricing model, why MCP probe workers are a natural fit for Spot, how to set up capacity providers to blend On-Demand and Spot capacity, and the SIGTERM handler you need to checkpoint in-flight probes before a task is interrupted. For ECS capacity provider CDK setup see ECS Capacity Providers for MCP Server Monitoring; for the SIGTERM checkpoint pattern see Handling Fargate Spot Interruptions for MCP Workers.

TL;DR

Fargate Spot gives you up to 70% off On-Demand Fargate prices in exchange for accepting 2-minute SIGTERM interruptions when AWS reclaims the capacity. MCP probe workers are stateless and short-lived — a worker interrupted mid-probe just re-queues its batch and another worker picks it up. Configure your ECS service with a FARGATE_SPOT capacity provider (weight 3) and a FARGATE base of 1–2 On-Demand tasks for the probe coordinator; add a process.on('SIGTERM', ...) handler that drains in-flight probes to SQS before the 2-minute window closes. At 1,000 monitored endpoints running 10 parallel probe workers, switching to 80% Spot reduces monthly ECS compute from ~$45 to ~$13.

What Fargate Spot is

AWS Fargate Spot is a purchasing option for ECS Fargate tasks that draws on unused EC2 Spot capacity in AWS's data centers. When spare capacity is available, Fargate Spot tasks are provisioned at a discount of up to 70% vs standard Fargate On-Demand pricing. When AWS needs the capacity back — due to increased On-Demand demand — it sends a two-minute warning (a SIGTERM signal to the task containers) before forcibly terminating the task with SIGKILL.

Key facts about Fargate Spot:

Why MCP probe workers are a strong Spot fit

Not every workload tolerates Spot interruptions. A database, a WebSocket server maintaining client connections, or a batch job that must complete within a hard deadline is a poor fit. MCP probe workers have properties that make them almost ideal for Spot:

PropertyWhy it helps on Spot
Stateless No in-memory state that cannot be reconstructed. Each probe result is written to the database immediately on completion; an interrupted worker loses at most the in-flight batch it was currently processing.
Idempotent Running the same probe twice produces two records for the same 60-second window; the dashboard aggregates by time window and deduplicates. A re-queued probe batch that runs again is harmless.
Short per-probe duration Each individual MCP endpoint probe takes 100ms–10s. A SIGTERM with a 2-minute window gives a worker time to finish its current batch of 10–20 probes and drain the results before termination.
Horizontally scalable The probe coordinator distributes endpoint batches via SQS. When one worker is interrupted, its incomplete batches become visible after the SQS visibility timeout and are picked up by surviving workers. No single worker is a bottleneck.
Continuously replacing ECS automatically launches a replacement Fargate Spot task (or falls back to On-Demand) when a Spot task is interrupted. Probe coverage resumes within 30–60 seconds of an interruption.

The one property that makes probe workers require some care on Spot: they maintain an open batch of in-progress probes at any given moment. A SIGTERM handler that checkpoints the current batch to SQS before shutdown ensures those probes are not lost. See Handling Fargate Spot Interruptions for MCP Workers for the full pattern.

Fargate Spot vs On-Demand cost model

Fargate pricing is based on vCPU-hours and GB-hours of memory consumed by running tasks. The exact Spot discount varies by region and time, but AWS typically shows 50–70% discounts. Using a representative discount of 66% in us-east-1:

Task sizeOn-Demand (mo)Spot at 66% off (mo)Saving
0.25 vCPU, 0.5 GB (probe worker) ~$4.50 ~$1.53 ~$2.97/task
0.5 vCPU, 1 GB (probe coordinator) ~$8.99 ~$3.06 ~$5.93/task
1 vCPU, 2 GB (registry crawler) ~$17.98 ~$6.11 ~$11.87/task

For AliveMCP at 1,000 monitored endpoints, a typical deployment runs:

Total: ~$27/mo. The equivalent all-On-Demand fleet would cost ~$82/mo — a 67% reduction by running 9 of 10 tasks on Spot. At 10,000 endpoints (80+ workers), Spot saves over $300/mo.

Task definition: probe worker

The probe worker task definition is identical whether you run it On-Demand or Spot — Fargate Spot is a capacity selection choice made at the ECS service level (via capacity providers), not in the task definition itself.

import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Duration } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export function createProbeWorkerTaskDef(
  scope: Construct,
  repo: ecr.IRepository,
  executionRole: iam.IRole,
  taskRole: iam.IRole,
): ecs.FargateTaskDefinition {
  const taskDef = new ecs.FargateTaskDefinition(scope, 'ProbeWorkerTaskDef', {
    // Small vCPU/memory: probe workers are I/O-bound (HTTP calls), not CPU-bound.
    // 0.25 vCPU is sufficient for 20 concurrent probes per worker.
    cpu: 256,       // 0.25 vCPU
    memoryLimitMiB: 512,
    executionRole,
    taskRole,
    runtimePlatform: {
      cpuArchitecture: ecs.CpuArchitecture.ARM64,  // Graviton: extra ~20% off both On-Demand and Spot
      operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
    },
  });

  taskDef.addContainer('ProbeWorker', {
    image: ecs.ContainerImage.fromEcrRepository(repo, 'latest'),
    environment: {
      PROBE_CONCURRENCY: '20',      // concurrent MCP endpoint probes per worker
      PROBE_TIMEOUT_MS: '8000',     // per-probe timeout
      SQS_PROBE_QUEUE_URL: process.env.SQS_PROBE_QUEUE_URL ?? '',
      DB_REGION: 'us-east-1',
    },
    logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'probe-worker' }),
    healthCheck: {
      command: ['CMD-SHELL', 'node -e "process.exit(0)"'],
      interval: Duration.seconds(30),
      timeout: Duration.seconds(5),
      retries: 3,
      startPeriod: Duration.seconds(10),
    },
  });

  return taskDef;
}

The ARM64 Graviton runtime applies an additional ~20% discount on top of the Fargate Spot price — Graviton tasks have lower base Fargate pricing than x86. This is orthogonal to Spot and compounds the savings: Graviton On-Demand is already cheaper than x86 On-Demand, and Graviton Spot is cheaper still.

ECS service: attaching Fargate Spot via capacity providers

You associate a Fargate Spot preference with an ECS service through a capacity provider strategy. The strategy specifies two capacity providers — FARGATE (On-Demand) and FARGATE_SPOT — with a base count for On-Demand tasks and a weight ratio for the remainder.

import * as ecs from 'aws-cdk-lib/aws-ecs';
import { Construct } from 'constructs';

export function createProbeWorkerService(
  scope: Construct,
  cluster: ecs.Cluster,
  taskDef: ecs.FargateTaskDefinition,
  subnetIds: string[],
  securityGroupIds: string[],
): ecs.FargateService {
  return new ecs.FargateService(scope, 'ProbeWorkerService', {
    cluster,
    taskDefinition: taskDef,
    desiredCount: 8,
    capacityProviderStrategies: [
      {
        // On-Demand: run at least 1 task at all times even during Spot shortages
        capacityProvider: 'FARGATE',
        base: 1,
        weight: 1,
      },
      {
        // Spot: remaining tasks use spare capacity at 66% discount
        capacityProvider: 'FARGATE_SPOT',
        base: 0,
        weight: 3,
      },
    ],
    vpcSubnets: { subnets: subnetIds.map(id => ({ subnetId: id } as any)) },
    assignPublicIp: false,
    enableExecuteCommand: true,
  });
}

With base: 1 for FARGATE and weight: 1 / 3, the resulting task distribution for desiredCount: 8 is:

  1. 1 On-Demand task (FARGATE base)
  2. Remaining 7 tasks distributed by weight: FARGATE gets 1/(1+3) = 25% → 1.75 ≈ 2 tasks; FARGATE_SPOT gets 3/(1+3) = 75% → 5.25 ≈ 5 tasks
  3. Actual result: 2 On-Demand + 5 Spot = 7 tasks plus 1 base = roughly 3 On-Demand + 5 Spot

For a probe worker service where losing 1–2 tasks briefly is acceptable, a more aggressive Spot-heavy strategy (base: 0, FARGATE_SPOT weight only) works fine as long as your SIGTERM handler checkpoints in-flight probes. See ECS Capacity Providers for MCP Server Monitoring for the full CDK cluster setup including enabling both capacity providers at the cluster level.

SIGTERM handler: minimum viable version

Before exploring the full checkpoint pattern, here is the minimum SIGTERM handler every Fargate Spot task must have to avoid data loss:

// probe-worker/src/index.ts

let isShuttingDown = false;
let activeProbeCount = 0;

process.on('SIGTERM', async () => {
  console.log('[shutdown] SIGTERM received — Fargate Spot interruption or ECS scale-in');
  isShuttingDown = true;

  // Wait for in-flight probes to complete (up to 90 seconds)
  const deadline = Date.now() + 90_000;
  while (activeProbeCount > 0 && Date.now() < deadline) {
    console.log(`[shutdown] Waiting for ${activeProbeCount} in-flight probes...`);
    await new Promise(res => setTimeout(res, 1000));
  }

  if (activeProbeCount > 0) {
    // Did not finish in time — re-queue incomplete batch to SQS so another worker picks it up
    await requeueIncompleteProbes();
    console.log(`[shutdown] Re-queued ${activeProbeCount} incomplete probes`);
  }

  console.log('[shutdown] Exiting cleanly');
  process.exit(0);
});

// In your probe loop: check isShuttingDown before starting a new batch
async function probeLoop(): Promise {
  while (!isShuttingDown) {
    const batch = await dequeueProbeBatch();
    if (!batch) { await new Promise(r => setTimeout(r, 5000)); continue; }

    activeProbeCount += batch.endpoints.length;
    try {
      await runProbeBatch(batch);
    } finally {
      activeProbeCount -= batch.endpoints.length;
    }
  }
}

This handler gives the probe worker 90 seconds of the 2-minute SIGTERM window to finish its current batch. The remaining 30 seconds is buffer before SIGKILL. For the full pattern — including SQS visibility timeout management and batch checkpointing — see Handling Fargate Spot Interruptions for MCP Workers.

ECS service replacement behavior on interruption

When a Fargate Spot task receives SIGTERM and eventually exits, ECS treats it as a task that stopped unexpectedly. The ECS service scheduler immediately attempts to launch a replacement task to maintain desiredCount. The replacement is provisioned from whichever capacity provider has available capacity — typically another Fargate Spot task if Spot capacity is available, or a Fargate On-Demand task if Spot is temporarily constrained.

The replacement task starts cold: it pulls from SQS for the next batch of endpoints to probe. If the interrupted task re-queued its incomplete batch to SQS before exiting, those endpoints are available for the replacement worker to pick up immediately. The probe coverage gap — the window between task interruption and replacement task picking up work — is typically 30–90 seconds (ECS launch latency for Fargate is roughly 30s for a cached container image).

For a 60-second probe interval, a 30–90 second gap means at most one probe cycle is missed per interrupted task. Uptime calculations can tolerate this: a single missed probe does not flip an endpoint's status to "down" — AliveMCP requires two consecutive failed probes before marking an endpoint as down, so a launch-gap-induced miss is invisible to status calculations.

Failure modes

SymptomCauseFix
All Spot tasks interrupted simultaneously AWS reclaims capacity for an entire AZ or instance family (rare, but possible during peak periods) Spread tasks across 3+ AZs; set FARGATE base to 2 On-Demand tasks; CloudWatch alarm on RunningTaskCount < desiredCount * 0.5 to alert on mass interruption
Replacement task stuck in PROVISIONING for >3 minutes Fargate Spot capacity unavailable in the selected AZ; ECS is waiting for capacity Use a capacity provider strategy with FARGATE fallback weight so ECS falls back to On-Demand when Spot is unavailable
Probes skipped for 5–10 minutes after interruption SIGTERM handler didn't re-queue the in-flight batch; those endpoints miss multiple 60s windows Implement the SQS re-queue pattern in SIGTERM handler; set SQS visibility timeout to 90s so unacknowledged messages become visible again automatically if the handler crashes
Duplicate probe records for some endpoints Interrupted worker partially committed results before SQS message was deleted; replacement worker processes the same SQS message again Use SQS FIFO queue with message deduplication ID = endpoint-slug + probe-window-epoch; or accept duplicates and deduplicate at query time with MAX(check_time) per window
Graviton task fails to pull ARM64 container image ECR repository only has an x86 image; ARM64 Fargate cannot run x86 images Build and push a multi-arch image: docker buildx build --platform linux/amd64,linux/arm64 --push; ECR supports multi-arch manifests natively