ECS

Elastic Container Service — clusters, task definitions, (later) real Fargate-style task execution via Docker, services, and rolling deployments.

fakecloud implements Amazon Elastic Container Service (ECS) with full API coverage. 60 operations, shipped across four batches.

Status: all four batches shipped — full API. Covers clusters, task definitions, real Fargate-style task execution, services with rolling deployments, task sets, container instances, capacity providers, attributes, task protection, ECS Exec, and the agent-side Submit* / DiscoverPollEndpoint surface.

Supported today (full API)

  • ClustersCreateCluster, DescribeClusters, DeleteCluster, ListClusters, UpdateCluster, UpdateClusterSettings, PutClusterCapacityProviders
  • Task definitionsRegisterTaskDefinition, DescribeTaskDefinition, DeregisterTaskDefinition, DeleteTaskDefinitions, ListTaskDefinitions, ListTaskDefinitionFamilies
  • TasksRunTask, StartTask, StopTask, DescribeTasks, ListTasks with real Fargate-style execution via Docker/Podman
  • ServicesCreateService, UpdateService, DeleteService, DescribeServices, ListServices, ListServicesByNamespace with desired-count enforcement and rolling deployments
  • Service deploymentsStopServiceDeployment, ListServiceDeployments, DescribeServiceDeployments, DescribeServiceRevisions
  • Task setsCreateTaskSet, UpdateTaskSet, DeleteTaskSet, DescribeTaskSets, UpdateServicePrimaryTaskSet (EXTERNAL deployment controller)
  • Container instancesRegisterContainerInstance, DeregisterContainerInstance, DescribeContainerInstances, ListContainerInstances, UpdateContainerAgent, UpdateContainerInstancesState
  • AttributesPutAttributes, DeleteAttributes, ListAttributes
  • Capacity providersCreateCapacityProvider, DeleteCapacityProvider, DescribeCapacityProviders, UpdateCapacityProvider
  • Task protectionGetTaskProtection, UpdateTaskProtection
  • ECS ExecExecuteCommand proxies to docker exec against the task's running container
  • Agent surfaceSubmitContainerStateChange, SubmitTaskStateChange, SubmitAttachmentStateChanges, DiscoverPollEndpoint
  • TaggingTagResource, UntagResource, ListTagsForResource (clusters and task definitions)
  • Account settingsPutAccountSetting, PutAccountSettingDefault, DeleteAccountSetting, ListAccountSettings

Services + rolling deployments (Batch 3)

CreateService spawns tasks to match desiredCount under the service, tagging each with startedBy=ecs-svc/<name> so the tasks reconcile back to the service. UpdateService supports two independent mutations:

  • Scale — set a new desiredCount. The service spawns additional tasks when scaling up and flips excess tasks to desiredStatus=STOPPED (runtime kill on the container) when scaling down.
  • Rolling deployment — pass a new taskDefinition. The service marks the previous PRIMARY deployment as ACTIVE, creates a new PRIMARY deployment for the target revision, and drains tasks on the old task definition while new ones come up. Deployment circuit breaker + minimumHealthyPercent / maximumPercent are honoured in deploymentConfiguration.

DeleteService refuses while desiredCount > 0 unless force=true; the forced path scales to 0 and stops every running task under the service before removing it.

Task-definition families track revisions monotonically; DeleteTaskDefinitions requires DeregisterTaskDefinition first (real AWS behaviour), and the result flips status to DELETE_IN_PROGRESS.

Task execution (Batch 2)

RunTask records the task synchronously and kicks off a background docker execution per spawned task:

  1. docker pull <image> (timestamps captured on the task: pullStartedAt / pullStoppedAt)
  2. docker run -d <image> (container ID recorded on the task's container)
  3. docker wait <id> (blocks on container exit; exit code → containers[].exitCode)
  4. docker logs <id> (captured stdout/stderr stored on the task + exposed via the introspection endpoint)
  5. docker rm <id> (cleanup)

Environment variables from the task definition are forwarded with localhost / 127.0.0.1 rewritten to host.docker.internal so containers reach fakecloud itself the same way Lambda does.

Without a container runtime (docker/podman missing), RunTask still returns tasks but they immediately transition to STOPPED with stopCode=TaskFailedToStart. This keeps the API surface shape-correct so tests on CI agents without Docker can still drive the control-plane surface.

Protocol

JSON protocol over POST /, with X-Amz-Target: AmazonEC2ContainerServiceV20141113.<Action>. Request + response bodies are JSON; tags use lowercase key / value (matches AWS SDK serialization).

Introspection

Endpoints bypass the public AWS API so tests can assert deterministic state without pagination or role-assumption noise.

EndpointMethodPurpose
/_fakecloud/ecs/clustersGETDump every cluster across all accounts
/_fakecloud/ecs/tasksGETDump every task; filter with ?cluster= / ?status=
/_fakecloud/ecs/tasks/{taskId}GETSingle-task deep detail
/_fakecloud/ecs/tasks/{taskId}/logsGETCaptured docker stdout/stderr + exit code
/_fakecloud/ecs/tasks/{taskId}/force-stopPOSTSIGTERM + SIGKILL the running container
/_fakecloud/ecs/tasks/{taskId}/mark-failedPOSTFlip to STOPPED without killing the container (inject exit code + reason)
/_fakecloud/ecs/eventsGETReplay the lifecycle event log

All endpoints are sorted deterministically (by ARN for clusters/tasks, by timestamp for events) so test assertions don't flake on map iteration order.

Clusters dump

{
  "clusters": [
    {
      "clusterName": "prod",
      "clusterArn": "arn:aws:ecs:us-east-1:111122223333:cluster/prod",
      "status": "ACTIVE",
      "runningTasksCount": 0,
      "pendingTasksCount": 0,
      "activeServicesCount": 0,
      "registeredContainerInstancesCount": 0,
      "capacityProviders": ["FARGATE"],
      "tags": [{"key": "env", "value": "prod"}],
      "createdAt": "2026-04-23T23:00:00+00:00"
    }
  ]
}

SDK usage (testing helper)

The fakecloud client SDKs ship typed wrappers for every introspection endpoint. Use them instead of poking /_fakecloud/* paths by hand.

// Go
clusters, _ := fakecloud.New("http://localhost:4566").ECS().GetClusters(ctx)
# Python
async with FakeCloud() as fc:
    clusters = await fc.ecs.get_clusters()
// TypeScript
const fc = new FakeCloud();
const { clusters } = await fc.ecs.getClusters();
// Rust
let fc = FakeCloud::new("http://localhost:4566");
let clusters = fc.ecs().get_clusters().await?;

Roadmap

  • Batch 4 — Container instances, attributes, capacity providers, task protection, ECS Exec (ExecuteCommand via docker exec), task sets (EXTERNAL deployment controller), snapshot/restore of in-flight tasks, IAM task-role credential injection via AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, EventBridge ECS Task State Change events, awslogs-driver CloudWatch Logs streaming.

Source