This guide deploys a small HTTP container on Amazon ECS on AWS Fargate. It builds the image from the included app/ directory, pushes it to Amazon ECR, stores one example setting in AWS Secrets Manager, and exports a public URL after the service is ready.
Architecture
The stack creates four pieces:
- A local container image built from
app/Dockerfile. - A private image repository in Amazon ECR.
- A managed secret that becomes the
APP_MESSAGEenvironment variable. - A public Amazon ECS on AWS Fargate service with an ALB target group health check on
/healthand Application Auto Scaling for ECS desired count.
The app listens on port 80, returns JSON from /, and returns ok from /health.
AWS Fargate is the right choice when you want ECS scheduling, IAM integration, and ALB routing without managing EC2 container hosts. This variant creates the ECS cluster, ECR repository, task definition, Fargate service, ALB listener, target group health check, Secrets Manager value, and desired-count autoscaling target.
Prerequisites
Before you start, install:
- a Pulumi account and the Pulumi CLI
- Docker running locally so Pulumi can build and push the container image
- Node.js 20 or newer and npm
- an AWS account where you can create ECR, ECS, IAM, Secrets Manager, Application Load Balancer, and Application Auto Scaling resources
What you get in the download
The downloadable example zip includes:
index.tsas the Pulumi entrypointcomponents/container-service.tsas the reusable Amazon ECS on AWS Fargate componentapp/with a tiny HTTP service and Dockerfilepackage.jsonandtsconfig.jsonfor the Pulumi project
__main__.pyas the Pulumi entrypointcomponents/container_service.pyas the reusable Amazon ECS on AWS Fargate componentapp/with a tiny HTTP service and Dockerfilerequirements.txtfor the Pulumi project
main.goas the Pulumi entrypointcontainerservice/service.goas the reusable Amazon ECS on AWS Fargate componentapp/with a tiny HTTP service and Dockerfilego.modfor the Pulumi project
Quickstart
Start from the downloaded example and run these commands from the project root.
pulumi stack init dev
pulumi config set aws:region us-west-2
pulumi config set --secret appMessage 'hello from Secrets Manager'
Install language dependencies:
npm install
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
go mod tidy
Then deploy:
pulumi up
App shape
The app is small so you can focus on the infrastructure first. app/server.js exposes /health for an ALB target group health check on /health and / for a JSON response that includes the secret-backed APP_MESSAGE. Replace the app/ directory with your own container when the first deployment works.
Application code
app/server.js
The container runs this tiny Node.js HTTP server. It returns JSON from /, echoes the APP_MESSAGE secret value, and answers the platform health check at /health. The same app/ directory ships in every language starter.
const http = require("http");
const port = Number(process.env.PORT || 80);
const message = process.env.APP_MESSAGE || "hello from Pulumi";
const server = http.createServer((req, res) => {
if (req.url === "/health") {
res.writeHead(200, { "content-type": "text/plain" });
res.end("ok");
return;
}
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ message, path: req.url }));
});
server.listen(port, "0.0.0.0", () => {
console.log(`listening on ${port}`);
});
Deploy and iterate
Run pulumi up whenever you change the Pulumi program or the app image. The image tag is stack-scoped, so a new build updates the running task definition and rolls the service forward through Amazon ECS on AWS Fargate.
Outputs
After deployment, the stack exports:
url- the load balancer DNS name over HTTP port 80imageName- the pushed image reference in Amazon ECRsecretName- the managed secret that backsAPP_MESSAGE
Open url in a browser or run curl $(pulumi stack output url)/health to verify the service.
Operations and cleanup
Use aws ecs describe-services for deployment state, aws logs tail for container logs once you add a log driver, and ALB target health for /health failures.
The ALB has an hourly baseline even when the service is idle. Fargate task cost follows desired task count and CPU/memory settings.
Destroy everything with:
pulumi destroy
pulumi stack rm
Blueprint Pulumi program
The entrypoint reads stack config, creates the reusable container service component, then exports the URL and registry coordinates produced by Amazon ECS on AWS Fargate.
import * as pulumi from "@pulumi/pulumi";
import { ServerlessContainerService } from "./components/container-service";
const config = new pulumi.Config();
const service = new ServerlessContainerService("app", {
appPath: "./app",
appMessage: config.requireSecret("appMessage"),
containerPort: 80,
minTasks: config.getNumber("minTasks") ?? 1,
maxTasks: config.getNumber("maxTasks") ?? 3,
});
export const url = service.url;
export const imageName = service.imageName;
export const secretName = service.secretName;
import pulumi
from components.container_service import ServerlessContainerService
config = pulumi.Config()
service = ServerlessContainerService(
"app",
app_path="./app",
app_message=config.require_secret("appMessage"),
container_port=80,
min_tasks=config.get_int("minTasks") or 1,
max_tasks=config.get_int("maxTasks") or 3,
)
pulumi.export("url", service.url)
pulumi.export("imageName", service.image_name)
pulumi.export("secretName", service.secret_name)
package main
import (
"fmt"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
"serverless-containers/containerservice"
)
func Program(ctx *pulumi.Context) error {
cfg := config.New(ctx, "")
appMessage := cfg.RequireSecret("appMessage")
minUnits := cfg.GetInt("minTasks")
if minUnits == 0 {
minUnits = 1
}
maxUnits := cfg.GetInt("maxTasks")
if maxUnits == 0 {
maxUnits = 3
}
service, err := containerservice.NewServerlessContainerService(ctx, "app", &containerservice.ServerlessContainerServiceArgs{
AppPath: "./app",
AppMessage: appMessage,
ContainerPort: 80,
MinTasks: minUnits,
MaxTasks: maxUnits,
})
if err != nil {
return fmt.Errorf("create service: %w", err)
}
ctx.Export("url", service.URL)
ctx.Export("imageName", service.ImageName)
ctx.Export("secretName", service.SecretName)
return nil
}
func main() {
pulumi.Run(Program)
}
Reusable component
The component owns the registry, image build, serverless container service, health check, secret injection, and autoscaling settings for AWS Fargate. The downloadable starter includes this same file.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as docker from "@pulumi/docker";
export interface ServerlessContainerServiceArgs {
appPath: string;
appMessage: pulumi.Input<string>;
containerPort: number;
minTasks: number;
maxTasks: number;
}
export class ServerlessContainerService extends pulumi.ComponentResource {
public readonly url: pulumi.Output<string>;
public readonly imageName: pulumi.Output<string>;
public readonly secretName: pulumi.Output<string>;
constructor(name: string, args: ServerlessContainerServiceArgs, opts?: pulumi.ComponentResourceOptions) {
super("guides:serverless:AwsFargateService", name, {}, opts);
const vpc = aws.ec2.getVpcOutput({ default: true });
const subnets = aws.ec2.getSubnetsOutput({ filters: [{ name: "vpc-id", values: [vpc.id] }] });
const repository = new aws.ecr.Repository(`${name}-repo`, { forceDelete: true }, { parent: this });
const auth = aws.ecr.getAuthorizationTokenOutput({ registryId: repository.registryId });
const image = new docker.Image(`${name}-image`, {
build: { context: args.appPath, platform: "linux/amd64" },
imageName: pulumi.interpolate`${repository.repositoryUrl}:latest`,
registry: { server: repository.repositoryUrl, username: auth.userName, password: auth.password },
}, { parent: this });
const secret = new aws.secretsmanager.Secret(`${name}-message`, {}, { parent: this });
new aws.secretsmanager.SecretVersion(`${name}-message-version`, { secretId: secret.id, secretString: args.appMessage }, { parent: this });
const cluster = new aws.ecs.Cluster(`${name}-cluster`, {}, { parent: this });
const albSg = new aws.ec2.SecurityGroup(`${name}-alb-sg`, {
vpcId: vpc.id,
ingress: [{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] }],
egress: [{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] }],
}, { parent: this });
const serviceSg = new aws.ec2.SecurityGroup(`${name}-service-sg`, {
vpcId: vpc.id,
ingress: [{ protocol: "tcp", fromPort: args.containerPort, toPort: args.containerPort, securityGroups: [albSg.id] }],
egress: [{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] }],
}, { parent: this });
const lb = new aws.lb.LoadBalancer(`${name}-alb`, { loadBalancerType: "application", securityGroups: [albSg.id], subnets: subnets.ids }, { parent: this });
const targetGroup = new aws.lb.TargetGroup(`${name}-tg`, {
port: args.containerPort,
protocol: "HTTP",
targetType: "ip",
vpcId: vpc.id,
healthCheck: { path: "/health", matcher: "200" },
}, { parent: this });
new aws.lb.Listener(`${name}-listener`, { loadBalancerArn: lb.arn, port: 80, defaultActions: [{ type: "forward", targetGroupArn: targetGroup.arn }] }, { parent: this });
const executionRole = new aws.iam.Role(`${name}-execution-role`, {
assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ Service: "ecs-tasks.amazonaws.com" }),
}, { parent: this });
new aws.iam.RolePolicyAttachment(`${name}-execution-policy`, { role: executionRole.name, policyArn: aws.iam.ManagedPolicy.AmazonECSTaskExecutionRolePolicy }, { parent: this });
new aws.iam.RolePolicy(`${name}-secret-policy`, {
role: executionRole.id,
policy: secret.arn.apply(arn => JSON.stringify({ Version: "2012-10-17", Statement: [{ Effect: "Allow", Action: ["secretsmanager:GetSecretValue"], Resource: arn }] })),
}, { parent: this });
const containerDefinitions = pulumi.all([image.imageName, secret.arn]).apply(([imageName, secretArn]) => JSON.stringify([{ name: "app", image: imageName, essential: true, portMappings: [{ containerPort: args.containerPort, protocol: "tcp" }], secrets: [{ name: "APP_MESSAGE", valueFrom: secretArn }] }]));
const task = new aws.ecs.TaskDefinition(`${name}-task`, { family: `${name}-task`, cpu: "256", memory: "512", networkMode: "awsvpc", requiresCompatibilities: ["FARGATE"], executionRoleArn: executionRole.arn, containerDefinitions }, { parent: this });
const service = new aws.ecs.Service(`${name}-service`, { cluster: cluster.arn, desiredCount: args.minTasks, launchType: "FARGATE", taskDefinition: task.arn, networkConfiguration: { assignPublicIp: true, subnets: subnets.ids, securityGroups: [serviceSg.id] }, loadBalancers: [{ targetGroupArn: targetGroup.arn, containerName: "app", containerPort: args.containerPort }] }, { parent: this });
const scalableTarget = new aws.appautoscaling.Target(`${name}-scale-target`, { maxCapacity: args.maxTasks, minCapacity: args.minTasks, resourceId: pulumi.interpolate`service/${cluster.name}/${service.name}`, scalableDimension: "ecs:service:DesiredCount", serviceNamespace: "ecs" }, { parent: this });
new aws.appautoscaling.Policy(`${name}-scale-policy`, { policyType: "TargetTrackingScaling", resourceId: scalableTarget.resourceId, scalableDimension: scalableTarget.scalableDimension, serviceNamespace: scalableTarget.serviceNamespace, targetTrackingScalingPolicyConfiguration: { predefinedMetricSpecification: { predefinedMetricType: "ECSServiceAverageCPUUtilization" }, targetValue: 60 } }, { parent: this });
this.url = pulumi.interpolate`http://${lb.dnsName}`;
this.imageName = image.imageName;
this.secretName = secret.name;
this.registerOutputs({ url: this.url, imageName: this.imageName, secretName: this.secretName });
}
}
import json
import pulumi
import pulumi_aws as aws
import pulumi_docker as docker
class ServerlessContainerService(pulumi.ComponentResource):
def __init__(self, name, app_path, app_message, container_port, min_tasks, max_tasks, opts=None):
super().__init__("guides:serverless:AwsFargateService", name, None, opts)
child = pulumi.ResourceOptions(parent=self)
vpc = aws.ec2.get_vpc(default=True)
subnets = aws.ec2.get_subnets(filters=[aws.ec2.GetSubnetsFilterArgs(name="vpc-id", values=[vpc.id])])
repo = aws.ecr.Repository(f"{name}-repo", force_delete=True, opts=child)
auth = aws.ecr.get_authorization_token_output(registry_id=repo.registry_id)
image = docker.Image(f"{name}-image", build=docker.DockerBuildArgs(context=app_path, platform="linux/amd64"), image_name=repo.repository_url.apply(lambda url: f"{url}:latest"), registry=docker.RegistryArgs(server=repo.repository_url, username=auth.user_name, password=auth.password), opts=child)
secret = aws.secretsmanager.Secret(f"{name}-message", opts=child)
aws.secretsmanager.SecretVersion(f"{name}-message-version", secret_id=secret.id, secret_string=app_message, opts=child)
cluster = aws.ecs.Cluster(f"{name}-cluster", opts=child)
alb_sg = aws.ec2.SecurityGroup(f"{name}-alb-sg", vpc_id=vpc.id, ingress=[aws.ec2.SecurityGroupIngressArgs(protocol="tcp", from_port=80, to_port=80, cidr_blocks=["0.0.0.0/0"])], egress=[aws.ec2.SecurityGroupEgressArgs(protocol="-1", from_port=0, to_port=0, cidr_blocks=["0.0.0.0/0"])], opts=child)
service_sg = aws.ec2.SecurityGroup(f"{name}-service-sg", vpc_id=vpc.id, ingress=[aws.ec2.SecurityGroupIngressArgs(protocol="tcp", from_port=container_port, to_port=container_port, security_groups=[alb_sg.id])], egress=[aws.ec2.SecurityGroupEgressArgs(protocol="-1", from_port=0, to_port=0, cidr_blocks=["0.0.0.0/0"])], opts=child)
lb = aws.lb.LoadBalancer(f"{name}-alb", load_balancer_type="application", security_groups=[alb_sg.id], subnets=subnets.ids, opts=child)
target_group = aws.lb.TargetGroup(f"{name}-tg", port=container_port, protocol="HTTP", target_type="ip", vpc_id=vpc.id, health_check=aws.lb.TargetGroupHealthCheckArgs(path="/health", matcher="200"), opts=child)
aws.lb.Listener(f"{name}-listener", load_balancer_arn=lb.arn, port=80, default_actions=[aws.lb.ListenerDefaultActionArgs(type="forward", target_group_arn=target_group.arn)], opts=child)
role = aws.iam.Role(f"{name}-execution-role", assume_role_policy=json.dumps({"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"sts:AssumeRole","Principal":{"Service":"ecs-tasks.amazonaws.com"}}]}), opts=child)
aws.iam.RolePolicyAttachment(f"{name}-execution-policy", role=role.name, policy_arn=aws.iam.ManagedPolicy.AMAZON_ECS_TASK_EXECUTION_ROLE_POLICY, opts=child)
aws.iam.RolePolicy(f"{name}-secret-policy", role=role.id, policy=secret.arn.apply(lambda arn: json.dumps({"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["secretsmanager:GetSecretValue"],"Resource":arn}]})), opts=child)
definitions = pulumi.Output.all(image.image_name, secret.arn).apply(lambda values: json.dumps([{"name":"app","image":values[0],"essential":True,"portMappings":[{"containerPort":container_port,"protocol":"tcp"}],"secrets":[{"name":"APP_MESSAGE","valueFrom":values[1]}]}]))
task = aws.ecs.TaskDefinition(f"{name}-task", family=f"{name}-task", cpu="256", memory="512", network_mode="awsvpc", requires_compatibilities=["FARGATE"], execution_role_arn=role.arn, container_definitions=definitions, opts=child)
service = aws.ecs.Service(f"{name}-service", cluster=cluster.arn, desired_count=min_tasks, launch_type="FARGATE", task_definition=task.arn, network_configuration=aws.ecs.ServiceNetworkConfigurationArgs(assign_public_ip=True, subnets=subnets.ids, security_groups=[service_sg.id]), load_balancers=[aws.ecs.ServiceLoadBalancerArgs(target_group_arn=target_group.arn, container_name="app", container_port=container_port)], opts=child)
target = aws.appautoscaling.Target(f"{name}-scale-target", max_capacity=max_tasks, min_capacity=min_tasks, resource_id=pulumi.Output.concat("service/", cluster.name, "/", service.name), scalable_dimension="ecs:service:DesiredCount", service_namespace="ecs", opts=child)
aws.appautoscaling.Policy(f"{name}-scale-policy", policy_type="TargetTrackingScaling", resource_id=target.resource_id, scalable_dimension=target.scalable_dimension, service_namespace=target.service_namespace, target_tracking_scaling_policy_configuration=aws.appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationArgs(predefined_metric_specification=aws.appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationPredefinedMetricSpecificationArgs(predefined_metric_type="ECSServiceAverageCPUUtilization"), target_value=60), opts=child)
self.url = pulumi.Output.concat("http://", lb.dns_name)
self.image_name = image.image_name
self.secret_name = secret.name
self.register_outputs({"url": self.url, "imageName": self.image_name, "secretName": self.secret_name})
package containerservice
import (
"encoding/json"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appautoscaling"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ecr"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ecs"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lb"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/secretsmanager"
"github.com/pulumi/pulumi-docker/sdk/v4/go/docker"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
type ServerlessContainerServiceArgs struct { AppPath string; AppMessage pulumi.StringOutput; ContainerPort int; MinTasks int; MaxTasks int }
type ServerlessContainerService struct { pulumi.ResourceState; URL pulumi.StringOutput; ImageName pulumi.StringOutput; SecretName pulumi.StringOutput }
func NewServerlessContainerService(ctx *pulumi.Context, name string, args *ServerlessContainerServiceArgs, opts ...pulumi.ResourceOption) (*ServerlessContainerService, error) {
component := &ServerlessContainerService{}
if err := ctx.RegisterComponentResource("guides:serverless:AwsFargateService", name, component, opts...); err != nil { return nil, err }
child := pulumi.Parent(component)
vpc, err := ec2.LookupVpc(ctx, &ec2.LookupVpcArgs{Default: pulumi.BoolRef(true)}, nil); if err != nil { return nil, err }
subnets, err := ec2.GetSubnets(ctx, &ec2.GetSubnetsArgs{Filters: []ec2.GetSubnetsFilter{{Name: "vpc-id", Values: []string{vpc.Id}}}}, nil); if err != nil { return nil, err }
repo, err := ecr.NewRepository(ctx, name+"-repo", &ecr.RepositoryArgs{ForceDelete: pulumi.Bool(true)}, child); if err != nil { return nil, err }
auth := ecr.GetAuthorizationTokenOutput(ctx, ecr.GetAuthorizationTokenOutputArgs{RegistryId: repo.RegistryId})
image, err := docker.NewImage(ctx, name+"-image", &docker.ImageArgs{Build: &docker.DockerBuildArgs{Context: pulumi.String(args.AppPath), Platform: pulumi.String("linux/amd64")}, ImageName: repo.RepositoryUrl.ApplyT(func(url string) string { return url + ":latest" }).(pulumi.StringOutput), Registry: &docker.RegistryArgs{Server: repo.RepositoryUrl, Username: auth.UserName(), Password: auth.Password()}}, child); if err != nil { return nil, err }
secret, err := secretsmanager.NewSecret(ctx, name+"-message", nil, child); if err != nil { return nil, err }
_, err = secretsmanager.NewSecretVersion(ctx, name+"-message-version", &secretsmanager.SecretVersionArgs{SecretId: secret.ID(), SecretString: args.AppMessage}, child); if err != nil { return nil, err }
cluster, err := ecs.NewCluster(ctx, name+"-cluster", nil, child); if err != nil { return nil, err }
albSg, err := ec2.NewSecurityGroup(ctx, name+"-alb-sg", &ec2.SecurityGroupArgs{VpcId: pulumi.String(vpc.Id), Ingress: ec2.SecurityGroupIngressArray{&ec2.SecurityGroupIngressArgs{Protocol: pulumi.String("tcp"), FromPort: pulumi.Int(80), ToPort: pulumi.Int(80), CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")}}}, Egress: ec2.SecurityGroupEgressArray{&ec2.SecurityGroupEgressArgs{Protocol: pulumi.String("-1"), FromPort: pulumi.Int(0), ToPort: pulumi.Int(0), CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")}}}}, child); if err != nil { return nil, err }
serviceSg, err := ec2.NewSecurityGroup(ctx, name+"-service-sg", &ec2.SecurityGroupArgs{VpcId: pulumi.String(vpc.Id), Ingress: ec2.SecurityGroupIngressArray{&ec2.SecurityGroupIngressArgs{Protocol: pulumi.String("tcp"), FromPort: pulumi.Int(args.ContainerPort), ToPort: pulumi.Int(args.ContainerPort), SecurityGroups: pulumi.StringArray{albSg.ID()}}}, Egress: ec2.SecurityGroupEgressArray{&ec2.SecurityGroupEgressArgs{Protocol: pulumi.String("-1"), FromPort: pulumi.Int(0), ToPort: pulumi.Int(0), CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")}}}}, child); if err != nil { return nil, err }
subnetIds := pulumi.ToStringArray(subnets.Ids)
loadBalancer, err := lb.NewLoadBalancer(ctx, name+"-alb", &lb.LoadBalancerArgs{LoadBalancerType: pulumi.String("application"), SecurityGroups: pulumi.StringArray{albSg.ID()}, Subnets: subnetIds}, child); if err != nil { return nil, err }
targetGroup, err := lb.NewTargetGroup(ctx, name+"-tg", &lb.TargetGroupArgs{Port: pulumi.Int(args.ContainerPort), Protocol: pulumi.String("HTTP"), TargetType: pulumi.String("ip"), VpcId: pulumi.String(vpc.Id), HealthCheck: &lb.TargetGroupHealthCheckArgs{Path: pulumi.String("/health"), Matcher: pulumi.String("200")}}, child); if err != nil { return nil, err }
_, err = lb.NewListener(ctx, name+"-listener", &lb.ListenerArgs{LoadBalancerArn: loadBalancer.Arn, Port: pulumi.Int(80), DefaultActions: lb.ListenerDefaultActionArray{&lb.ListenerDefaultActionArgs{Type: pulumi.String("forward"), TargetGroupArn: targetGroup.Arn}}}, child); if err != nil { return nil, err }
role, err := iam.NewRole(ctx, name+"-execution-role", &iam.RoleArgs{AssumeRolePolicy: pulumi.String(`{"Version":"2012-10-17","Statement":[{"Action":"sts:AssumeRole","Principal":{"Service":"ecs-tasks.amazonaws.com"},"Effect":"Allow"}]}`)}, child); if err != nil { return nil, err }
_, err = iam.NewRolePolicyAttachment(ctx, name+"-execution-policy", &iam.RolePolicyAttachmentArgs{Role: role.Name, PolicyArn: pulumi.String("arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy")}, child); if err != nil { return nil, err }
_, err = iam.NewRolePolicy(ctx, name+"-secret-policy", &iam.RolePolicyArgs{Role: role.ID(), Policy: secret.Arn.ApplyT(func(arn string) (string, error) { policy, err := json.Marshal(map[string]interface{}{"Version":"2012-10-17","Statement":[]map[string]interface{}{{"Effect":"Allow","Action":[]string{"secretsmanager:GetSecretValue"},"Resource":arn}}}); return string(policy), err }).(pulumi.StringOutput)}, child); if err != nil { return nil, err }
defs := pulumi.All(image.ImageName, secret.Arn).ApplyT(func(values []interface{}) (string, error) { raw, err := json.Marshal([]map[string]interface{}{{"name":"app","image":values[0].(string),"essential":true,"portMappings":[]map[string]interface{}{{"containerPort":args.ContainerPort,"protocol":"tcp"}},"secrets":[]map[string]interface{}{{"name":"APP_MESSAGE","valueFrom":values[1].(string)}}}}); return string(raw), err }).(pulumi.StringOutput)
task, err := ecs.NewTaskDefinition(ctx, name+"-task", &ecs.TaskDefinitionArgs{Family: pulumi.String(name+"-task"), Cpu: pulumi.String("256"), Memory: pulumi.String("512"), NetworkMode: pulumi.String("awsvpc"), RequiresCompatibilities: pulumi.StringArray{pulumi.String("FARGATE")}, ExecutionRoleArn: role.Arn, ContainerDefinitions: defs}, child); if err != nil { return nil, err }
service, err := ecs.NewService(ctx, name+"-service", &ecs.ServiceArgs{Cluster: cluster.Arn, DesiredCount: pulumi.Int(args.MinTasks), LaunchType: pulumi.String("FARGATE"), TaskDefinition: task.Arn, NetworkConfiguration: &ecs.ServiceNetworkConfigurationArgs{AssignPublicIp: pulumi.Bool(true), Subnets: subnetIds, SecurityGroups: pulumi.StringArray{serviceSg.ID()}}, LoadBalancers: ecs.ServiceLoadBalancerArray{&ecs.ServiceLoadBalancerArgs{TargetGroupArn: targetGroup.Arn, ContainerName: pulumi.String("app"), ContainerPort: pulumi.Int(args.ContainerPort)}}}, child); if err != nil { return nil, err }
target, err := appautoscaling.NewTarget(ctx, name+"-scale-target", &appautoscaling.TargetArgs{MaxCapacity: pulumi.Int(args.MaxTasks), MinCapacity: pulumi.Int(args.MinTasks), ResourceId: pulumi.Sprintf("service/%s/%s", cluster.Name, service.Name), ScalableDimension: pulumi.String("ecs:service:DesiredCount"), ServiceNamespace: pulumi.String("ecs")}, child); if err != nil { return nil, err }
_, err = appautoscaling.NewPolicy(ctx, name+"-scale-policy", &appautoscaling.PolicyArgs{PolicyType: pulumi.String("TargetTrackingScaling"), ResourceId: target.ResourceId, ScalableDimension: target.ScalableDimension, ServiceNamespace: target.ServiceNamespace, TargetTrackingScalingPolicyConfiguration: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationArgs{PredefinedMetricSpecification: &appautoscaling.PolicyTargetTrackingScalingPolicyConfigurationPredefinedMetricSpecificationArgs{PredefinedMetricType: pulumi.String("ECSServiceAverageCPUUtilization")}, TargetValue: pulumi.Float64(60)}}, child); if err != nil { return nil, err }
component.URL = pulumi.Sprintf("http://%s", loadBalancer.DnsName)
component.ImageName = image.ImageName
component.SecretName = secret.Name
return component, ctx.RegisterResourceOutputs(component, pulumi.Map{"url": component.URL, "imageName": component.ImageName, "secretName": component.SecretName})
}