The aws:ecs/service:Service resource, part of the Pulumi AWS provider, defines an ECS service that maintains a desired count of tasks from a task definition, handling placement, load balancing, and deployment strategies. This guide focuses on four capabilities: load balancer integration and task placement, scheduling strategies (REPLICA and DAEMON), deployment strategies (LINEAR, CANARY, EXTERNAL), and CloudWatch alarm integration.
ECS services depend on clusters, task definitions, and often reference load balancer target groups, IAM roles, and CloudWatch alarms. The examples are intentionally small. Combine them with your own cluster infrastructure, networking configuration, and monitoring setup.
Run tasks with load balancer integration and placement rules
Most ECS services run web applications that receive traffic through a load balancer, maintaining a desired count of tasks distributed across availability zones.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const mongo = new aws.ecs.Service("mongo", {
name: "mongodb",
cluster: fooAwsEcsCluster.id,
taskDefinition: mongoAwsEcsTaskDefinition.arn,
desiredCount: 3,
iamRole: fooAwsIamRole.arn,
orderedPlacementStrategies: [{
type: "binpack",
field: "cpu",
}],
loadBalancers: [{
targetGroupArn: fooAwsLbTargetGroup.arn,
containerName: "mongo",
containerPort: 8080,
}],
placementConstraints: [{
type: "memberOf",
expression: "attribute:ecs.availability-zone in [us-west-2a, us-west-2b]",
}],
}, {
dependsOn: [foo],
});
import pulumi
import pulumi_aws as aws
mongo = aws.ecs.Service("mongo",
name="mongodb",
cluster=foo_aws_ecs_cluster["id"],
task_definition=mongo_aws_ecs_task_definition["arn"],
desired_count=3,
iam_role=foo_aws_iam_role["arn"],
ordered_placement_strategies=[{
"type": "binpack",
"field": "cpu",
}],
load_balancers=[{
"target_group_arn": foo_aws_lb_target_group["arn"],
"container_name": "mongo",
"container_port": 8080,
}],
placement_constraints=[{
"type": "memberOf",
"expression": "attribute:ecs.availability-zone in [us-west-2a, us-west-2b]",
}],
opts = pulumi.ResourceOptions(depends_on=[foo]))
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ecs"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ecs.NewService(ctx, "mongo", &ecs.ServiceArgs{
Name: pulumi.String("mongodb"),
Cluster: pulumi.Any(fooAwsEcsCluster.Id),
TaskDefinition: pulumi.Any(mongoAwsEcsTaskDefinition.Arn),
DesiredCount: pulumi.Int(3),
IamRole: pulumi.Any(fooAwsIamRole.Arn),
OrderedPlacementStrategies: ecs.ServiceOrderedPlacementStrategyArray{
&ecs.ServiceOrderedPlacementStrategyArgs{
Type: pulumi.String("binpack"),
Field: pulumi.String("cpu"),
},
},
LoadBalancers: ecs.ServiceLoadBalancerArray{
&ecs.ServiceLoadBalancerArgs{
TargetGroupArn: pulumi.Any(fooAwsLbTargetGroup.Arn),
ContainerName: pulumi.String("mongo"),
ContainerPort: pulumi.Int(8080),
},
},
PlacementConstraints: ecs.ServicePlacementConstraintArray{
&ecs.ServicePlacementConstraintArgs{
Type: pulumi.String("memberOf"),
Expression: pulumi.String("attribute:ecs.availability-zone in [us-west-2a, us-west-2b]"),
},
},
}, pulumi.DependsOn([]pulumi.Resource{
foo,
}))
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var mongo = new Aws.Ecs.Service("mongo", new()
{
Name = "mongodb",
Cluster = fooAwsEcsCluster.Id,
TaskDefinition = mongoAwsEcsTaskDefinition.Arn,
DesiredCount = 3,
IamRole = fooAwsIamRole.Arn,
OrderedPlacementStrategies = new[]
{
new Aws.Ecs.Inputs.ServiceOrderedPlacementStrategyArgs
{
Type = "binpack",
Field = "cpu",
},
},
LoadBalancers = new[]
{
new Aws.Ecs.Inputs.ServiceLoadBalancerArgs
{
TargetGroupArn = fooAwsLbTargetGroup.Arn,
ContainerName = "mongo",
ContainerPort = 8080,
},
},
PlacementConstraints = new[]
{
new Aws.Ecs.Inputs.ServicePlacementConstraintArgs
{
Type = "memberOf",
Expression = "attribute:ecs.availability-zone in [us-west-2a, us-west-2b]",
},
},
}, new CustomResourceOptions
{
DependsOn =
{
foo,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ecs.Service;
import com.pulumi.aws.ecs.ServiceArgs;
import com.pulumi.aws.ecs.inputs.ServiceOrderedPlacementStrategyArgs;
import com.pulumi.aws.ecs.inputs.ServiceLoadBalancerArgs;
import com.pulumi.aws.ecs.inputs.ServicePlacementConstraintArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var mongo = new Service("mongo", ServiceArgs.builder()
.name("mongodb")
.cluster(fooAwsEcsCluster.id())
.taskDefinition(mongoAwsEcsTaskDefinition.arn())
.desiredCount(3)
.iamRole(fooAwsIamRole.arn())
.orderedPlacementStrategies(ServiceOrderedPlacementStrategyArgs.builder()
.type("binpack")
.field("cpu")
.build())
.loadBalancers(ServiceLoadBalancerArgs.builder()
.targetGroupArn(fooAwsLbTargetGroup.arn())
.containerName("mongo")
.containerPort(8080)
.build())
.placementConstraints(ServicePlacementConstraintArgs.builder()
.type("memberOf")
.expression("attribute:ecs.availability-zone in [us-west-2a, us-west-2b]")
.build())
.build(), CustomResourceOptions.builder()
.dependsOn(foo)
.build());
}
}
resources:
mongo:
type: aws:ecs:Service
properties:
name: mongodb
cluster: ${fooAwsEcsCluster.id}
taskDefinition: ${mongoAwsEcsTaskDefinition.arn}
desiredCount: 3
iamRole: ${fooAwsIamRole.arn}
orderedPlacementStrategies:
- type: binpack
field: cpu
loadBalancers:
- targetGroupArn: ${fooAwsLbTargetGroup.arn}
containerName: mongo
containerPort: 8080
placementConstraints:
- type: memberOf
expression: attribute:ecs.availability-zone in [us-west-2a, us-west-2b]
options:
dependsOn:
- ${foo}
When the service starts, ECS launches tasks according to desiredCount and registers them with the load balancer’s target group. The orderedPlacementStrategies array controls how tasks spread across instances (here, binpack by CPU to maximize utilization). The placementConstraints array restricts where tasks can run (here, only in us-west-2a and us-west-2b). The loadBalancers block connects the service to a target group, specifying which container and port receive traffic. Note the dependsOn relationship: the IAM role policy must exist before the service, preventing a race condition during deletion.
Run one task per container instance with daemon scheduling
Infrastructure services like log collectors need to run exactly one task on every container instance, automatically scaling as the cluster grows or shrinks.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const bar = new aws.ecs.Service("bar", {
name: "bar",
cluster: foo.id,
taskDefinition: barAwsEcsTaskDefinition.arn,
schedulingStrategy: "DAEMON",
});
import pulumi
import pulumi_aws as aws
bar = aws.ecs.Service("bar",
name="bar",
cluster=foo["id"],
task_definition=bar_aws_ecs_task_definition["arn"],
scheduling_strategy="DAEMON")
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ecs"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ecs.NewService(ctx, "bar", &ecs.ServiceArgs{
Name: pulumi.String("bar"),
Cluster: pulumi.Any(foo.Id),
TaskDefinition: pulumi.Any(barAwsEcsTaskDefinition.Arn),
SchedulingStrategy: pulumi.String("DAEMON"),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var bar = new Aws.Ecs.Service("bar", new()
{
Name = "bar",
Cluster = foo.Id,
TaskDefinition = barAwsEcsTaskDefinition.Arn,
SchedulingStrategy = "DAEMON",
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ecs.Service;
import com.pulumi.aws.ecs.ServiceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var bar = new Service("bar", ServiceArgs.builder()
.name("bar")
.cluster(foo.id())
.taskDefinition(barAwsEcsTaskDefinition.arn())
.schedulingStrategy("DAEMON")
.build());
}
}
resources:
bar:
type: aws:ecs:Service
properties:
name: bar
cluster: ${foo.id}
taskDefinition: ${barAwsEcsTaskDefinition.arn}
schedulingStrategy: DAEMON
The schedulingStrategy property set to DAEMON tells ECS to place one task per instance. You don’t specify desiredCount with DAEMON scheduling; ECS manages the count automatically based on cluster size.
Monitor deployments with CloudWatch alarms
Production deployments benefit from automated monitoring that detects failures and triggers rollbacks when metrics breach thresholds.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.ecs.Service("example", {
name: "example",
cluster: exampleAwsEcsCluster.id,
alarms: {
enable: true,
rollback: true,
alarmNames: [exampleAwsCloudwatchMetricAlarm.alarmName],
},
});
import pulumi
import pulumi_aws as aws
example = aws.ecs.Service("example",
name="example",
cluster=example_aws_ecs_cluster["id"],
alarms={
"enable": True,
"rollback": True,
"alarm_names": [example_aws_cloudwatch_metric_alarm["alarmName"]],
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ecs"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ecs.NewService(ctx, "example", &ecs.ServiceArgs{
Name: pulumi.String("example"),
Cluster: pulumi.Any(exampleAwsEcsCluster.Id),
Alarms: &ecs.ServiceAlarmsArgs{
Enable: pulumi.Bool(true),
Rollback: pulumi.Bool(true),
AlarmNames: pulumi.StringArray{
exampleAwsCloudwatchMetricAlarm.AlarmName,
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Ecs.Service("example", new()
{
Name = "example",
Cluster = exampleAwsEcsCluster.Id,
Alarms = new Aws.Ecs.Inputs.ServiceAlarmsArgs
{
Enable = true,
Rollback = true,
AlarmNames = new[]
{
exampleAwsCloudwatchMetricAlarm.AlarmName,
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ecs.Service;
import com.pulumi.aws.ecs.ServiceArgs;
import com.pulumi.aws.ecs.inputs.ServiceAlarmsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new Service("example", ServiceArgs.builder()
.name("example")
.cluster(exampleAwsEcsCluster.id())
.alarms(ServiceAlarmsArgs.builder()
.enable(true)
.rollback(true)
.alarmNames(exampleAwsCloudwatchMetricAlarm.alarmName())
.build())
.build());
}
}
resources:
example:
type: aws:ecs:Service
properties:
name: example
cluster: ${exampleAwsEcsCluster.id}
alarms:
enable: true
rollback: true
alarmNames:
- ${exampleAwsCloudwatchMetricAlarm.alarmName}
The alarms block enables CloudWatch integration. When enable is true and rollback is true, ECS monitors the specified alarms during deployments and automatically rolls back if any alarm enters ALARM state. The alarmNames array lists which CloudWatch alarms to watch.
Delegate deployment control to external tools
Teams using AWS CodeDeploy or custom orchestration need ECS to defer deployment decisions to external controllers.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.ecs.Service("example", {
name: "example",
cluster: exampleAwsEcsCluster.id,
deploymentController: {
type: "EXTERNAL",
},
});
import pulumi
import pulumi_aws as aws
example = aws.ecs.Service("example",
name="example",
cluster=example_aws_ecs_cluster["id"],
deployment_controller={
"type": "EXTERNAL",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ecs"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ecs.NewService(ctx, "example", &ecs.ServiceArgs{
Name: pulumi.String("example"),
Cluster: pulumi.Any(exampleAwsEcsCluster.Id),
DeploymentController: &ecs.ServiceDeploymentControllerArgs{
Type: pulumi.String("EXTERNAL"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Ecs.Service("example", new()
{
Name = "example",
Cluster = exampleAwsEcsCluster.Id,
DeploymentController = new Aws.Ecs.Inputs.ServiceDeploymentControllerArgs
{
Type = "EXTERNAL",
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ecs.Service;
import com.pulumi.aws.ecs.ServiceArgs;
import com.pulumi.aws.ecs.inputs.ServiceDeploymentControllerArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new Service("example", ServiceArgs.builder()
.name("example")
.cluster(exampleAwsEcsCluster.id())
.deploymentController(ServiceDeploymentControllerArgs.builder()
.type("EXTERNAL")
.build())
.build());
}
}
resources:
example:
type: aws:ecs:Service
properties:
name: example
cluster: ${exampleAwsEcsCluster.id}
deploymentController:
type: EXTERNAL
The deploymentController block with type set to EXTERNAL tells ECS not to manage deployments itself. The external tool (like CodeDeploy) handles task updates, traffic shifting, and rollback logic. ECS only maintains the service registration.
Roll out updates in fixed percentage increments
Gradual rollouts reduce risk by deploying to a fixed percentage of tasks at regular intervals, allowing time to observe metrics between steps.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.ecs.Service("example", {
name: "example",
cluster: exampleAwsEcsCluster.id,
deploymentConfiguration: {
strategy: "LINEAR",
bakeTimeInMinutes: "10",
linearConfiguration: {
stepPercent: 25,
stepBakeTimeInMinutes: "5",
},
},
});
import pulumi
import pulumi_aws as aws
example = aws.ecs.Service("example",
name="example",
cluster=example_aws_ecs_cluster["id"],
deployment_configuration={
"strategy": "LINEAR",
"bake_time_in_minutes": "10",
"linear_configuration": {
"step_percent": 25,
"step_bake_time_in_minutes": "5",
},
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ecs"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ecs.NewService(ctx, "example", &ecs.ServiceArgs{
Name: pulumi.String("example"),
Cluster: pulumi.Any(exampleAwsEcsCluster.Id),
DeploymentConfiguration: &ecs.ServiceDeploymentConfigurationArgs{
Strategy: pulumi.String("LINEAR"),
BakeTimeInMinutes: pulumi.String("10"),
LinearConfiguration: &ecs.ServiceDeploymentConfigurationLinearConfigurationArgs{
StepPercent: pulumi.Float64(25),
StepBakeTimeInMinutes: pulumi.String("5"),
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Ecs.Service("example", new()
{
Name = "example",
Cluster = exampleAwsEcsCluster.Id,
DeploymentConfiguration = new Aws.Ecs.Inputs.ServiceDeploymentConfigurationArgs
{
Strategy = "LINEAR",
BakeTimeInMinutes = "10",
LinearConfiguration = new Aws.Ecs.Inputs.ServiceDeploymentConfigurationLinearConfigurationArgs
{
StepPercent = 25,
StepBakeTimeInMinutes = "5",
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ecs.Service;
import com.pulumi.aws.ecs.ServiceArgs;
import com.pulumi.aws.ecs.inputs.ServiceDeploymentConfigurationArgs;
import com.pulumi.aws.ecs.inputs.ServiceDeploymentConfigurationLinearConfigurationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new Service("example", ServiceArgs.builder()
.name("example")
.cluster(exampleAwsEcsCluster.id())
.deploymentConfiguration(ServiceDeploymentConfigurationArgs.builder()
.strategy("LINEAR")
.bakeTimeInMinutes("10")
.linearConfiguration(ServiceDeploymentConfigurationLinearConfigurationArgs.builder()
.stepPercent(25.0)
.stepBakeTimeInMinutes("5")
.build())
.build())
.build());
}
}
resources:
example:
type: aws:ecs:Service
properties:
name: example
cluster: ${exampleAwsEcsCluster.id}
deploymentConfiguration:
strategy: LINEAR
bakeTimeInMinutes: 10
linearConfiguration:
stepPercent: 25
stepBakeTimeInMinutes: 5
The deploymentConfiguration block with strategy set to LINEAR enables stepped rollouts. The linearConfiguration specifies stepPercent (how much of the fleet updates in each step) and stepBakeTimeInMinutes (how long to wait between steps). The top-level bakeTimeInMinutes sets an initial observation period before starting the rollout.
Test updates with a small canary percentage
Canary deployments send a small percentage of traffic to the new version first, validating behavior before rolling out to the full fleet.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.ecs.Service("example", {
name: "example",
cluster: exampleAwsEcsCluster.id,
deploymentConfiguration: {
strategy: "CANARY",
bakeTimeInMinutes: "15",
canaryConfiguration: {
canaryPercent: 10,
canaryBakeTimeInMinutes: "5",
},
},
});
import pulumi
import pulumi_aws as aws
example = aws.ecs.Service("example",
name="example",
cluster=example_aws_ecs_cluster["id"],
deployment_configuration={
"strategy": "CANARY",
"bake_time_in_minutes": "15",
"canary_configuration": {
"canary_percent": 10,
"canary_bake_time_in_minutes": "5",
},
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ecs"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ecs.NewService(ctx, "example", &ecs.ServiceArgs{
Name: pulumi.String("example"),
Cluster: pulumi.Any(exampleAwsEcsCluster.Id),
DeploymentConfiguration: &ecs.ServiceDeploymentConfigurationArgs{
Strategy: pulumi.String("CANARY"),
BakeTimeInMinutes: pulumi.String("15"),
CanaryConfiguration: &ecs.ServiceDeploymentConfigurationCanaryConfigurationArgs{
CanaryPercent: pulumi.Float64(10),
CanaryBakeTimeInMinutes: pulumi.String("5"),
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Ecs.Service("example", new()
{
Name = "example",
Cluster = exampleAwsEcsCluster.Id,
DeploymentConfiguration = new Aws.Ecs.Inputs.ServiceDeploymentConfigurationArgs
{
Strategy = "CANARY",
BakeTimeInMinutes = "15",
CanaryConfiguration = new Aws.Ecs.Inputs.ServiceDeploymentConfigurationCanaryConfigurationArgs
{
CanaryPercent = 10,
CanaryBakeTimeInMinutes = "5",
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ecs.Service;
import com.pulumi.aws.ecs.ServiceArgs;
import com.pulumi.aws.ecs.inputs.ServiceDeploymentConfigurationArgs;
import com.pulumi.aws.ecs.inputs.ServiceDeploymentConfigurationCanaryConfigurationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new Service("example", ServiceArgs.builder()
.name("example")
.cluster(exampleAwsEcsCluster.id())
.deploymentConfiguration(ServiceDeploymentConfigurationArgs.builder()
.strategy("CANARY")
.bakeTimeInMinutes("15")
.canaryConfiguration(ServiceDeploymentConfigurationCanaryConfigurationArgs.builder()
.canaryPercent(10.0)
.canaryBakeTimeInMinutes("5")
.build())
.build())
.build());
}
}
resources:
example:
type: aws:ecs:Service
properties:
name: example
cluster: ${exampleAwsEcsCluster.id}
deploymentConfiguration:
strategy: CANARY
bakeTimeInMinutes: 15
canaryConfiguration:
canaryPercent: 10
canaryBakeTimeInMinutes: 5
The deploymentConfiguration block with strategy set to CANARY enables canary testing. The canaryConfiguration specifies canaryPercent (what percentage deploys first) and canaryBakeTimeInMinutes (how long to observe the canary). After the canary period, the remaining tasks update. The top-level bakeTimeInMinutes sets an initial observation period.
Beyond these examples
These snippets focus on specific service-level features: load balancer integration and task placement, scheduling strategies (REPLICA and DAEMON), deployment strategies (LINEAR, CANARY, EXTERNAL), and CloudWatch alarm integration for rollbacks. They’re intentionally minimal rather than full application deployments.
The examples reference pre-existing infrastructure such as ECS clusters and task definitions, load balancer target groups, IAM roles for ECS service execution, and CloudWatch alarms for deployment monitoring. They focus on configuring the service rather than provisioning everything around it.
To keep things focused, common service patterns are omitted, including:
- VPC networking configuration (networkConfiguration for awsvpc mode)
- Capacity provider strategies vs launch types
- Service discovery and Service Connect
- Blue/Green deployments and SIGINT rollback
- Force redeployment with triggers
- ECS Exec and managed tags
These omissions are intentional: the goal is to illustrate how each service feature is wired, not provide drop-in container orchestration modules. See the ECS Service resource reference for all available configuration options.
Let's deploy AWS ECS Services
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Deployment & Updates
dependsOn to the related aws.iam.RolePolicy to ensure the policy isn’t destroyed too soon.forceNewDeployment = true and configure the triggers property. For example, use triggers = { redeployment = plantimestamp() } to redeploy on every apply.orderedPlacementStrategies and placementConstraints only apply on the next task deployment. Set forceNewDeployment = true to apply them immediately.sigintRollback = true and waitForSteadyState = true. This only works with the ECS deployment controller and allows safe cancellation with automatic rollback.true, Pulumi waits for the service to reach a steady state (like aws ecs wait services-stable) before continuing. It defaults to false.IAM & Permissions
iamRole if using a load balancer with your service, but only if your task definition does NOT use awsvpc network mode. With awsvpc network mode, don’t specify this role.Scheduling & Scaling
desiredCount), while DAEMON runs exactly one task per container instance. Don’t specify desiredCount when using DAEMON scheduling.ignoreChanges on desiredCount to set an initial task count, then let Application Autoscaling manage the count externally without Pulumi overwriting it.Configuration & Limits
cluster, iamRole, launchType, name, and schedulingStrategy. Changing these requires recreating the service.orderedPlacementStrategy blocks, up to 10 placementConstraints blocks, and only 1 serviceRegistries block.EC2. You can also use FARGATE or EXTERNAL. Note that launchType conflicts with capacityProviderStrategy.Using a different cloud?
Explore containers guides for other cloud providers: