Skip to content
cloudemu
Services

Container Orchestration

In-memory ECS control plane — clusters, task definitions, tasks, and services with real EC2/Fargate placement, driven with the real AWS SDK

aws ECSazr gcp

Emulates the ECS control plane — the clusters, task definitions, tasks, and services you call to schedule containers on AWS. You register a task definition, then run tasks or stand up a service; the emulator places EC2-launch-type tasks onto container instances with spare capacity, or synthesizes a Fargate ENI attachment, exactly the way the real scheduler would decide. Everything lives in memory, so a run starts empty and converges synchronously — no eventual-consistency waits.

Reach for it in tests when your code provisions clusters, registers task definitions, or scales services up and down and then asserts on the resulting tasks and deployments — without a real ECS account or a container runtime. It is AWS-only: there is no cross-cloud abstraction, because ECS has no Azure or GCP counterpart to model.

ProviderServiceSDK-compatDriver
AWSECS✓ Liveaws.ECS
Azure
GCP

Drive it with the real SDK#

The recommended path drops the SDK-compat server in front of cloudemu and points a stock aws-sdk-go-v2/service/ecs client at it. This example registers a container instance so the cluster has EC2 capacity, registers a task definition, and runs a task that gets first-fit placed onto that instance:

import (
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/service/ecs"
    ecstypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
    "github.com/stackshy/cloudemu/v2"
    awsserver "github.com/stackshy/cloudemu/v2/server/aws"
)

cloud := cloudemu.NewAWS()
ts := httptest.NewServer(awsserver.New(awsserver.Drivers{
    ECS: cloud.ECS, EC2: cloud.EC2,
}))
defer ts.Close()

client := ecs.NewFromConfig(cfg, func(o *ecs.Options) {
    o.BaseEndpoint = aws.String(ts.URL)
})

client.CreateCluster(ctx, &ecs.CreateClusterInput{ClusterName: aws.String("prod")})
client.RegisterTaskDefinition(ctx, &ecs.RegisterTaskDefinitionInput{
    Family: aws.String("web"),
    ContainerDefinitions: []ecstypes.ContainerDefinition{{
        Name: aws.String("app"), Image: aws.String("nginx"),
        Cpu: 256, Memory: aws.Int32(512),
    }},
})

out, _ := client.RunTask(ctx, &ecs.RunTaskInput{
    Cluster:        aws.String("prod"),
    TaskDefinition: aws.String("web"),
    LaunchType:     ecstypes.LaunchTypeEc2,
})
// out.Tasks[0] is placed on a container instance; out.Failures is empty.

See the SDK-Compat Server page for the full quick start.

Call the driver directly#

For in-process setup, skip the HTTP hop and call the driver with its own input structs — the same flow, minus the client boilerplate:

import ecsdriver "github.com/stackshy/cloudemu/v2/services/ecs/driver"

aws.ECS.CreateCluster(ctx, ecsdriver.CreateClusterInput{ClusterName: "prod"})
aws.ECS.RegisterTaskDefinition(ctx, ecsdriver.RegisterTaskDefinitionInput{Family: "web"})
tasks, failures, _ := aws.ECS.RunTask(ctx, ecsdriver.RunTaskInput{
    Cluster: "prod", TaskDefinition: "web", LaunchType: "EC2",
})

Batch reads (DescribeClusters, DescribeTasks, DescribeServices) and RunTask return a partial-success failures slice alongside the results, rather than erroring — matching how the real API reports per-item problems.

Behavior & fidelity#

The scheduler is real: it places tasks against actual container-instance capacity and converges services synchronously, so a run reaches its end state before the call returns — no polling for RUNNING.

BehaviorWhat happens
Task definitions auto-increment revisionsRegisterTaskDefinition bumps family:revision on each call.
Full runtime surface round-tripsportMappings, environment, secrets, healthCheck, volumes, and the rest are stored on the task definition, though launched containers carry only name/image/status.
EC2 tasks are first-fit placedA task lands on the first container instance with enough CPU/memory, reserving it and releasing on stop.
No capacity is a soft failureA task that can't be placed lands in failures[] (AGENT / RESOURCE:*); a service stays PENDING.
Fargate synthesizes its networkingA FARGATE task needs awsvpcConfiguration and an awsvpc task-def with cpu+memory; the emulator adds an ENI attachment and a platformVersion.
Launch type is validatedlaunchType is checked against the task-def's requiresCompatibilities, and Fargate task-level cpu/memory against the supported-configuration table.
Services converge synchronouslyCreateService launches desiredCount tasks and records a PRIMARY deployment; UpdateService reconciles and promotes a new deployment as superseded ones drain.
DAEMON runs one task per instanceA DAEMON service places a task on every container instance and rejects a caller-supplied desiredCount.
ECS composes with EC2RegisterContainerInstance provisions a backing managed EC2 instance, discoverable as a real instance under managed-resource visibility — wire EC2: cloud.EC2 into the server to see it.
Typed exceptions match the SDKMissing clusters, services, and bad parameters surface as the same named errors a real client would catch.
Accepted but not simulatedcapacityProviderStrategy, loadBalancers / serviceRegistries, and the deployment circuit-breaker are stored and round-tripped so SDK calls succeed, but have no behavioral effect.

SDK-compat — Live#

Real aws-sdk-go-v2/service/ecs clients drive the control plane end-to-end:

FamilyCoverage
ClustersLifecycle, settings, capacity providers
Task definitionsRegistration with auto-incrementing revisions
TasksPlacement, lifecycle, and command execution
ServicesCreation and rolling-update convergence
Container instancesRegistration, draining, and inspection
Tagging & account settingsFull

See SDK-Compat for the full per-operation list.

On this page

On this page