Deploy AWS ECS Services

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 FREE

Frequently Asked Questions

Deployment & Updates
Why is my service stuck in DRAINING state during deletion?
This happens when the IAM role policy is destroyed before the ECS service. Set dependsOn to the related aws.iam.RolePolicy to ensure the policy isn’t destroyed too soon.
How do I force a redeployment of my service?
Set forceNewDeployment = true and configure the triggers property. For example, use triggers = { redeployment = plantimestamp() } to redeploy on every apply.
Why aren't my placement strategy or constraint updates taking effect?
Updates to orderedPlacementStrategies and placementConstraints only apply on the next task deployment. Set forceNewDeployment = true to apply them immediately.
How do I enable automatic rollback when canceling a deployment?
Set sigintRollback = true and waitForSteadyState = true. This only works with the ECS deployment controller and allows safe cancellation with automatic rollback.
What does waitForSteadyState do?
When set to 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
When do I need to specify an iamRole for my service?
You need 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
What's the difference between DAEMON and REPLICA scheduling?
REPLICA runs a specified number of tasks (desiredCount), while DAEMON runs exactly one task per container instance. Don’t specify desiredCount when using DAEMON scheduling.
Can I use DAEMON scheduling with Fargate?
No, DAEMON scheduling is not supported with Fargate launch type, CODE_DEPLOY deployment controller, or EXTERNAL deployment controller.
How do I use my service with Application Autoscaling?
Use ignoreChanges on desiredCount to set an initial task count, then let Application Autoscaling manage the count externally without Pulumi overwriting it.
Configuration & Limits
What properties can't be changed after creating a service?
The following properties are immutable: cluster, iamRole, launchType, name, and schedulingStrategy. Changing these requires recreating the service.
What are the limits for placement strategies and constraints?
You can configure up to 5 orderedPlacementStrategy blocks, up to 10 placementConstraints blocks, and only 1 serviceRegistries block.
What's the default launch type for an ECS service?
The default launch type is 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: