Skip to content

TaskDefinition

Source: src/AWS/ECS/TaskDefinition.ts

A standalone ECS task definition for bring-your-own-container workloads.

Unlike the Effect-native AWS.ECS.Task (which bundles an inline program and builds/pushes a Docker image), TaskDefinition registers user-supplied containerDefinitions — any image URI from ECR, public.ecr.aws, or an external registry — with full control over Fargate/EC2 compatibility, volumes, runtime platform, and IAM roles.

Task definitions are immutable revisions under a family:

  • reconcile registers a new revision only when the definition content changed (compared against the observed latest ACTIVE revision), so a no-op redeploy keeps the same revision;
  • changing the family replaces the resource;
  • destroy deregisters and hard-deletes every revision of the family. ECS may retain referenced revisions in DELETE_IN_PROGRESS until their tasks and services terminate; that is a successful terminal state.

Layering — why TaskDefinition is deliberately not a Platform. ECS splits “what runs” from “how it runs”: a task definition is the immutable container spec, while Task (runs to completion) and Service (long-running) are the execution vehicles. The effectful Platform abstraction requires Alchemy to own the container image and entrypoint so it can bundle the inline Effect program — that is exactly what AWS.ECS.Task does (bundle → Docker build/push → register definition → serve the program). Making TaskDefinition also a Platform would duplicate Task while contradicting this resource’s purpose: user-supplied images whose entrypoint Alchemy must not rewrite. So the effectful path is AWS.ECS.Task; the bring-your-own-container path is TaskDefinition. Both surface taskDefinitionArn / containerName / port, so either plugs into AWS.ECS.Service’s task prop unchanged.

Public Image on Fargate

const taskDef = yield* TaskDefinition("Nginx", {
containerDefinitions: [
{
name: "nginx",
image: "public.ecr.aws/nginx/nginx:stable",
essential: true,
portMappings: [{ containerPort: 80, protocol: "tcp" }],
},
],
});

With IAM Roles and CloudWatch Logs

const taskDef = yield* TaskDefinition("Api", {
cpu: 512,
memory: 1024,
taskRoleArn: taskRole, // AWS.IAM.Role resource or raw ARN
executionRoleArn: executionRole, // needed for awslogs / private images
awslogs: true, // creates /ecs/{family} and injects awslogs config
containerDefinitions: [
{
name: "api",
image: image.imageUri,
essential: true,
portMappings: [{ containerPort: 8080 }],
environment: [{ name: "STAGE", value: "prod" }],
},
],
});
const service = yield* Service("ApiService", {
cluster,
task: taskDef, // exposes taskDefinitionArn / containerName / port
vpcId: vpc.vpcId,
subnets: [subnet.subnetId],
assignPublicIp: true,
});
const taskDef = yield* TaskDefinition("Agent", {
requiresCompatibilities: ["EC2"],
networkMode: "bridge",
volumes: [{ name: "docker-sock", host: { sourcePath: "/var/run/docker.sock" } }],
containerDefinitions: [
{
name: "agent",
image: "public.ecr.aws/docker/library/busybox:stable",
memory: 128,
essential: true,
mountPoints: [{ sourceVolume: "docker-sock", containerPath: "/var/run/docker.sock" }],
},
],
});