AWS Proton: Standardized Infrastructure Templates for Platform Teams (2026)
AWS Proton solves one of the hardest problems in platform engineering: how do you give 50 application teams consistent, secure, production-ready infrastructure without becoming a bottleneck yourself? Proton lets platform teams publish versioned environment and service templates, then hands developers a self-service interface to deploy compliant infrastructure in minutes — no YAML spelunking required.
AWS Proton vs Service Catalog vs CDK vs Backstage
Before committing to Proton, it's worth understanding the landscape. Platform engineering tools have proliferated in the last few years, and each has a distinct sweet spot. Choosing the wrong one means either too much friction for developers or too little guardrails for the platform team.
| Tool | Who operates it | Abstraction level | Developer UX | Best for |
|---|---|---|---|---|
| AWS Proton | Platform team | Environment + Service templates (CloudFormation/Terraform) | Self-service via console/CLI; picks template, fills schema params | Standardized microservice platforms on ECS or EKS; managed CI/CD pipelines per service |
| AWS Service Catalog | Cloud governance / IT | CloudFormation products | Launch products from a portfolio; limited customization | Compliance-first environments; shared AMIs, approved databases, security baselines |
| AWS CDK | Individual dev or infra team | Constructs (TypeScript/Python/Go) | Write code, run cdk deploy; full flexibility |
Teams that want code-first IaC; composable constructs; CDK Pipelines |
| Backstage (CNCF) | Platform engineering team | Software catalog + scaffolding plugins | Developer portal: create services, track ownership, run scaffolders | Large orgs that need a developer portal layer on top of multiple IaC tools (Proton, CDK, Terraform) |
The key insight: these tools are complementary, not competing. A mature platform engineering org typically runs Backstage as the developer portal, Proton for day-2 infrastructure lifecycle management, CDK for bespoke infrastructure that doesn't fit a Proton template, and Service Catalog for governance artifacts like approved IAM roles and VPC configurations. Proton's unique advantage is its deep AWS integration — it manages deployment pipelines, tracks drift, sends update notifications to service owners, and enforces template version progression. No other tool in this list does all four.
Core Concepts: Environments, Services, Components, Bundles
AWS Proton has five core abstractions. Understanding them before writing any YAML will save you hours of confusion later.
Environment Templates define shared infrastructure that multiple services share. Think of an environment as a "landing zone" for services: a VPC, an ECS cluster, an RDS database, shared IAM roles, and CloudWatch log groups. You publish an environment template once, and then teams deploy many environments from it (dev, staging, prod — or one per team).
Service Templates define the infrastructure for a single microservice or function. A service template includes the service's compute resources (ECS task definition, Lambda function, EKS deployment), its IAM role, auto-scaling configuration, and optionally a CI/CD pipeline definition. Crucially, a service template declares which environment templates it is compatible with — Proton enforces this pairing.
Template Bundles are the ZIP or Git-synced directory that contains a template. A bundle always has this structure:
my-template-bundle/
├── infrastructure/
│ ├── cloudformation.yaml # or main.tf for Terraform
│ └── manifest.yaml # describes provisioning engine
└── schema/
└── schema.yaml # JSON Schema for input parameters
Components are addons — CloudFormation extensions that developers can attach to an existing service instance without modifying the service template. A platform team defines a component template (e.g., "add an SQS queue"), and a developer can attach it to their running service to get new resources provisioned and automatically wired in.
Template Versions use a major.minor scheme. A minor version update is backward-compatible (adding optional parameters, patching security groups). A major version update is a breaking change. Proton surfaces which service instances are running outdated template versions, and can notify service owners automatically.
Creating an Environment Template
An environment template provisions the shared infrastructure layer — VPC, ECS cluster, security groups, IAM roles — that all services within that environment will use. Here's how to build one from scratch.
Step 1: Write the schema
The schema defines what parameters operators fill in when deploying an environment. Save this as schema/schema.yaml:
schema:
format:
openapi: "3.0.0"
environment_input_type: "EnvironmentInput"
types:
EnvironmentInput:
type: object
description: "Input parameters for the shared ECS environment"
required:
- vpc_cidr
- container_insights
- log_retention_days
properties:
vpc_cidr:
type: string
description: "CIDR block for the VPC"
default: "10.0.0.0/16"
pattern: "^([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$"
container_insights:
type: boolean
description: "Enable ECS Container Insights (extra cost)"
default: false
log_retention_days:
type: integer
description: "CloudWatch log retention in days"
default: 30
enum: [7, 14, 30, 60, 90, 180, 365]
nat_gateway_count:
type: integer
description: "Number of NAT Gateways (1 = cost-optimized, 3 = HA)"
default: 1
enum: [1, 2, 3]
Step 2: Write the CloudFormation template
This is your actual infrastructure. Proton injects parameter values using Jinja2 templating. Save as infrastructure/cloudformation.yaml:
AWSTemplateFormatVersion: "2010-09-09"
Description: "Proton-managed shared ECS environment"
Parameters:
ProtonEnvironmentName:
Type: String
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: "{{ environment.inputs.vpc_cidr }}"
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${ProtonEnvironmentName}-vpc"
- Key: ManagedBy
Value: AWSProton
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [0, !Cidr [!GetAtt VPC.CidrBlock, 6, 8]]
AvailabilityZone: !Select [0, !GetAZs ""]
MapPublicIpOnLaunch: true
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [1, !Cidr [!GetAtt VPC.CidrBlock, 6, 8]]
AvailabilityZone: !Select [1, !GetAZs ""]
MapPublicIpOnLaunch: true
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [2, !Cidr [!GetAtt VPC.CidrBlock, 6, 8]]
AvailabilityZone: !Select [0, !GetAZs ""]
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [3, !Cidr [!GetAtt VPC.CidrBlock, 6, 8]]
AvailabilityZone: !Select [1, !GetAZs ""]
# ECS Cluster
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Sub "${ProtonEnvironmentName}-cluster"
ClusterSettings:
- Name: containerInsights
Value: "{{ 'enabled' if environment.inputs.container_insights else 'disabled' }}"
# Shared task execution role
TaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal: { Service: ecs-tasks.amazonaws.com }
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
SharedLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/proton/${ProtonEnvironmentName}/services"
RetentionInDays: {{ environment.inputs.log_retention_days }}
Outputs:
VpcId:
Value: !Ref VPC
PublicSubnetIds:
Value: !Join [",", [!Ref PublicSubnet1, !Ref PublicSubnet2]]
PrivateSubnetIds:
Value: !Join [",", [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
ECSClusterArn:
Value: !GetAtt ECSCluster.Arn
TaskExecutionRoleArn:
Value: !GetAtt TaskExecutionRole.Arn
SharedLogGroupName:
Value: !Ref SharedLogGroup
Step 3: Write the manifest
The manifest tells Proton which provisioning engine to use. Save as infrastructure/manifest.yaml:
infrastructure:
templates:
- file: cloudformation.yaml
rendering_engine: jinja
template_language: cloudformation
Step 4: Publish the template
# Create the template (registers the name, no content yet)
aws proton create-environment-template \
--name ecs-shared-env \
--display-name "Shared ECS Environment" \
--description "VPC + ECS cluster + shared IAM roles for microservices"
# Package the bundle into a ZIP
zip -r ecs-env-bundle.zip schema/ infrastructure/
# Upload to S3 (Proton reads from S3)
aws s3 cp ecs-env-bundle.zip s3://my-proton-templates/ecs-env/v1.0/bundle.zip
# Create version 1.0
aws proton create-environment-template-version \
--template-name ecs-shared-env \
--source s3="{bucket=my-proton-templates,key=ecs-env/v1.0/bundle.zip}" \
--major-version 1
# Publish the version (makes it available for deployments)
aws proton update-environment-template-version \
--template-name ecs-shared-env \
--major-version 1 \
--minor-version 0 \
--status PUBLISHED
main branch of your template repository should trigger a new minor version. Use template sync (covered in section 7) to automate this entirely.Creating a Service Template
A service template defines the infrastructure for one microservice. Unlike an environment template (deployed once per environment), a service template is deployed per-service and per-environment — the same service can have a staging instance and a prod instance, each tied to its respective environment.
Service templates have one extra wrinkle: they can optionally define a pipeline. When a service template includes a pipeline definition, Proton creates a CodePipeline CI/CD pipeline for the service at deploy time, wired to the service's environment and parameters automatically.
Schema for a service template (schema/schema.yaml):
schema:
format:
openapi: "3.0.0"
service_input_type: "ServiceInput"
pipeline_input_type: "PipelineInput"
types:
ServiceInput:
type: object
description: "Per-instance parameters (filled per environment)"
required:
- task_cpu
- task_memory
- desired_count
- container_port
properties:
task_cpu:
type: integer
description: "ECS task CPU units (256, 512, 1024, 2048, 4096)"
default: 256
enum: [256, 512, 1024, 2048, 4096]
task_memory:
type: integer
description: "ECS task memory in MiB"
default: 512
enum: [512, 1024, 2048, 4096]
desired_count:
type: integer
description: "Desired number of running tasks"
default: 1
minimum: 1
maximum: 50
container_port:
type: integer
description: "Port the container listens on"
default: 8080
health_check_path:
type: string
description: "HTTP path for ALB health checks"
default: "/health"
PipelineInput:
type: object
description: "Pipeline parameters (shared across all instances)"
required:
- source_repo
- source_branch
properties:
source_repo:
type: string
description: "GitHub owner/repo for the source code"
source_branch:
type: string
description: "Branch to build from"
default: "main"
buildspec_path:
type: string
description: "Path to buildspec.yml in source repo"
default: "buildspec.yml"
CloudFormation for the service instance (instance_infrastructure/cloudformation.yaml):
AWSTemplateFormatVersion: "2010-09-09"
Description: "Proton-managed ECS Fargate service"
Resources:
TaskRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal: { Service: ecs-tasks.amazonaws.com }
Action: sts:AssumeRole
Tags:
- Key: ProtonService
Value: "{{ service.name }}"
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: "{{ service.name }}-{{ service_instance.name }}"
Cpu: "{{ service_instance.inputs.task_cpu }}"
Memory: "{{ service_instance.inputs.task_memory }}"
NetworkMode: awsvpc
RequiresCompatibilities: [FARGATE]
ExecutionRoleArn: "{{ environment.outputs.TaskExecutionRoleArn }}"
TaskRoleArn: !GetAtt TaskRole.Arn
ContainerDefinitions:
- Name: app
Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/{{ service.name }}:latest"
PortMappings:
- ContainerPort: {{ service_instance.inputs.container_port }}
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: "{{ environment.outputs.SharedLogGroupName }}"
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: "{{ service.name }}/{{ service_instance.name }}"
ServiceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "SG for {{ service.name }} in {{ service_instance.name }}"
VpcId: "{{ environment.outputs.VpcId }}"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: {{ service_instance.inputs.container_port }}
ToPort: {{ service_instance.inputs.container_port }}
CidrIp: "0.0.0.0/0"
ECSService:
Type: AWS::ECS::Service
Properties:
ServiceName: "{{ service.name }}-{{ service_instance.name }}"
Cluster: "{{ environment.outputs.ECSClusterArn }}"
TaskDefinition: !Ref TaskDefinition
DesiredCount: {{ service_instance.inputs.desired_count }}
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
Subnets: !Split [",", "{{ environment.outputs.PrivateSubnetIds }}"]
SecurityGroups: [!Ref ServiceSecurityGroup]
AssignPublicIp: DISABLED
{{ service_instance.inputs.task_cpu }} are evaluated by Proton before the CloudFormation template is submitted. The final template CloudFormation sees contains only static values. You can use Jinja2 conditionals ({% if %}) and loops ({% for %}) for advanced templating.Register the service template:
# Create template
aws proton create-service-template \
--name ecs-fargate-service \
--display-name "ECS Fargate Service" \
--description "Fargate service with ALB and optional CodePipeline" \
--pipeline-provisioning CUSTOMER_MANAGED
# Zip and upload
zip -r ecs-svc-bundle.zip schema/ instance_infrastructure/ pipeline_infrastructure/
aws s3 cp ecs-svc-bundle.zip s3://my-proton-templates/ecs-svc/v1.0/bundle.zip
# Create version — specify compatible environment templates
aws proton create-service-template-version \
--template-name ecs-fargate-service \
--source s3="{bucket=my-proton-templates,key=ecs-svc/v1.0/bundle.zip}" \
--compatible-environment-templates '[{"templateName":"ecs-shared-env","majorVersion":"1"}]'
aws proton update-service-template-version \
--template-name ecs-fargate-service \
--major-version 1 \
--minor-version 0 \
--status PUBLISHED
Deploying Environments with the CLI
With templates published, deploying an environment is a single CLI call. Proton uses CodeBuild under the hood to execute the CloudFormation provisioning, which means you get full CloudTrail audit logs and can watch the deployment in real time.
# Create a dev environment
aws proton create-environment \
--name dev \
--template-name ecs-shared-env \
--template-major-version 1 \
--template-minor-version 0 \
--spec '{
"proton": "EnvironmentSpec",
"spec": {
"vpc_cidr": "10.10.0.0/16",
"container_insights": false,
"log_retention_days": 7,
"nat_gateway_count": 1
}
}' \
--proton-service-role-arn arn:aws:iam::123456789012:role/ProtonServiceRole
# Watch deployment status
aws proton get-environment --name dev \
--query 'environment.deploymentStatus'
# Wait for completion (poll)
aws proton wait environment-deployed --name dev
# Get outputs (VPC ID, cluster ARN, etc.)
aws proton get-environment --name dev \
--query 'environment.outputs'
You need a Proton service role with permissions to call CloudFormation, create EC2/ECS/IAM resources, and write to CloudWatch. Here's the minimal IAM trust policy for the role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "proton.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Attach the AWS managed policy AWSProtonServiceRolePolicy to the role, then add any resource-specific permissions your CloudFormation templates require (e.g., rds:CreateDBCluster if your environment template creates RDS).
To deploy a production environment with higher-reliability settings:
aws proton create-environment \
--name prod \
--template-name ecs-shared-env \
--template-major-version 1 \
--template-minor-version 0 \
--spec '{
"proton": "EnvironmentSpec",
"spec": {
"vpc_cidr": "10.20.0.0/16",
"container_insights": true,
"log_retention_days": 90,
"nat_gateway_count": 2
}
}' \
--proton-service-role-arn arn:aws:iam::123456789012:role/ProtonServiceRole
--environment-account-connection-id when creating the environment. This is the recommended pattern for prod/dev account separation.Deploying Services and Instances
A Proton service groups one or more service instances — each instance runs in a specific environment. When you create a service, you specify the service template, pipeline inputs (once, for the whole service), and the first service instance's inputs (per-environment parameters). You can add more instances later to deploy the same service to additional environments.
# Create a service with one instance in dev
aws proton create-service \
--name payment-api \
--template-name ecs-fargate-service \
--template-major-version 1 \
--spec '{
"proton": "ServiceSpec",
"pipeline": {
"source_repo": "myorg/payment-api",
"source_branch": "main",
"buildspec_path": "buildspec.yml"
},
"instances": [
{
"name": "dev",
"environment": "dev",
"spec": {
"task_cpu": 256,
"task_memory": 512,
"desired_count": 1,
"container_port": 8080,
"health_check_path": "/health"
}
}
]
}'
# Watch service creation
aws proton get-service --name payment-api \
--query 'service.status'
# Add a prod instance to the same service
aws proton create-service-instance \
--name prod \
--service-name payment-api \
--environment-name prod \
--spec '{
"proton": "ServiceInstanceSpec",
"spec": {
"task_cpu": 512,
"task_memory": 1024,
"desired_count": 2,
"container_port": 8080,
"health_check_path": "/health"
}
}'
# List all instances for a service
aws proton list-service-instances \
--service-name payment-api \
--query 'serviceInstances[*].{name:name,env:environmentName,status:deploymentStatus}'
When a service includes a pipeline, Proton creates a CodePipeline that builds the Docker image, pushes it to ECR, and triggers ECS service updates. The pipeline is parameterized by the service's pipeline inputs and automatically references the correct ECR repository and ECS cluster for each environment instance.
aws proton update-service-instance --deployment-type CURRENT_VERSION to re-deploy the current template without a code change.Template Syncing from Git
Manually zipping and uploading bundles gets old fast. Proton's template sync feature watches a Git repository and automatically creates new template versions whenever you push to the configured branch. This transforms your template repository into a GitOps-style source of truth for all platform infrastructure.
Repository structure for template sync:
proton-templates/
├── environment-templates/
│ └── ecs-shared-env/
│ └── v1/
│ ├── .proton/
│ │ └── manifest.yaml # version metadata
│ ├── schema/
│ │ └── schema.yaml
│ └── infrastructure/
│ ├── cloudformation.yaml
│ └── manifest.yaml
└── service-templates/
└── ecs-fargate-service/
└── v1/
├── .proton/
│ └── manifest.yaml
├── schema/
│ └── schema.yaml
└── instance_infrastructure/
├── cloudformation.yaml
└── manifest.yaml
The .proton/manifest.yaml (version metadata) looks like this:
template_name: ecs-shared-env
template_version: "1.0"
description: "Shared ECS environment — VPC, cluster, shared roles"
Set up template sync:
# 1. Link the repository to Proton (one-time setup)
aws proton create-repository \
--name myorg/proton-templates \
--provider GITHUB \
--connection-arn arn:aws:codestar-connections:us-east-1:123456789012:connection/abc123 \
--encryption-key arn:aws:kms:us-east-1:123456789012:key/key-id
# 2. Create a template sync config for the environment template
aws proton create-template-sync-config \
--template-name ecs-shared-env \
--template-type ENVIRONMENT \
--repository-name myorg/proton-templates \
--repository-provider GITHUB \
--branch main \
--subdirectory environment-templates/ecs-shared-env
# 3. Create a template sync config for the service template
aws proton create-template-sync-config \
--template-name ecs-fargate-service \
--template-type SERVICE \
--repository-name myorg/proton-templates \
--repository-provider GITHUB \
--branch main \
--subdirectory service-templates/ecs-fargate-service
# 4. Check sync status
aws proton get-template-sync-config \
--template-name ecs-shared-env \
--template-type ENVIRONMENT
After setup, any push to the main branch that modifies files under environment-templates/ecs-shared-env/v1/ automatically creates a new minor version (e.g., 1.1) and publishes it. If you add a new directory v2/, Proton creates a new major version. The platform team's release workflow becomes: open PR in proton-templates, review, merge — no manual CLI commands needed.
Custom Components: Extending Existing Services
Custom components let developers extend a running service with additional AWS resources — without modifying the service template or going through the platform team for every new resource type. The platform team defines reusable component templates (e.g., "SQS queue", "ElastiCache cluster", "DynamoDB table"), and developers self-serve from that catalog.
A component is a CloudFormation template that has access to the service instance's environment outputs and service parameters via the same Jinja2 variables. Here's a component that adds an SQS queue to an existing ECS service:
Component CloudFormation template (cloudformation.yaml):
AWSTemplateFormatVersion: "2010-09-09"
Description: "Proton component — SQS queue attached to ECS service"
Resources:
Queue:
Type: AWS::SQS::Queue
Properties:
QueueName: "{{ service.name }}-{{ service_instance.name }}-jobs"
VisibilityTimeoutSeconds: 300
MessageRetentionPeriod: 86400
RedrivePolicy:
deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
maxReceiveCount: 3
DeadLetterQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: "{{ service.name }}-{{ service_instance.name }}-jobs-dlq"
MessageRetentionPeriod: 1209600 # 14 days
# Grant the service's task role access to the queue
QueuePolicy:
Type: AWS::SQS::QueuePolicy
Properties:
Queues: [!Ref Queue]
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: "{{ service_instance.components.default.outputs.TaskRoleArn }}"
Action:
- sqs:SendMessage
- sqs:ReceiveMessage
- sqs:DeleteMessage
- sqs:GetQueueAttributes
Resource: !GetAtt Queue.Arn
Outputs:
QueueUrl:
Value: !Ref Queue
QueueArn:
Value: !GetAtt Queue.Arn
DlqUrl:
Value: !Ref DeadLetterQueue
Deploy the component to an existing service instance:
# Create the component (attaches to existing service instance)
aws proton create-component \
--name payment-api-job-queue \
--service-name payment-api \
--service-instance-name dev \
--template-file file://cloudformation.yaml \
--manifest file://manifest.yaml
# Check component status
aws proton get-component \
--name payment-api-job-queue \
--query 'component.deploymentStatus'
# List components for a service instance
aws proton list-components \
--service-instance-name dev \
--service-name payment-api
# Get component outputs (queue URL, ARN)
aws proton get-component \
--name payment-api-job-queue \
--query 'component.outputs'
# Delete component when no longer needed
aws proton delete-component \
--name payment-api-job-queue
Proton-Managed Deployment Pipelines
When a service template defines a pipeline, Proton provisions a CodePipeline on your behalf and manages it for the lifetime of the service. The pipeline is Jinja2-rendered just like the service instance — meaning the build environment, CodeBuild compute type, ECR repository, and deployment target are all injected from your template parameters and environment outputs.
Pipeline CloudFormation template (pipeline_infrastructure/cloudformation.yaml):
AWSTemplateFormatVersion: "2010-09-09"
Description: "Proton-managed CodePipeline for {{ service.name }}"
Resources:
PipelineArtifactBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "proton-{{ service.name }}-artifacts-${AWS::AccountId}"
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Status: Enabled
ExpirationInDays: 30
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: "{{ service.name }}-build"
ServiceRole: !GetAtt CodeBuildRole.Arn
Artifacts:
Type: CODEPIPELINE
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:7.0
PrivilegedMode: true # required for Docker builds
EnvironmentVariables:
- Name: SERVICE_NAME
Value: "{{ service.name }}"
- Name: AWS_ACCOUNT_ID
Value: !Ref AWS::AccountId
- Name: IMAGE_REPO_NAME
Value: "{{ service.name }}"
Source:
Type: CODEPIPELINE
BuildSpec: "{{ pipeline.inputs.buildspec_path }}"
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: "{{ service.name }}-pipeline"
RoleArn: !GetAtt PipelineRole.Arn
ArtifactStore:
Type: S3
Location: !Ref PipelineArtifactBucket
Stages:
- Name: Source
Actions:
- Name: Source
ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeStarSourceConnection
Version: "1"
Configuration:
ConnectionArn: "{{ pipeline.inputs.codestar_connection_arn }}"
FullRepositoryId: "{{ pipeline.inputs.source_repo }}"
BranchName: "{{ pipeline.inputs.source_branch }}"
OutputArtifacts: [{Name: SourceOutput}]
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: "1"
Configuration:
ProjectName: !Ref CodeBuildProject
InputArtifacts: [{Name: SourceOutput}]
OutputArtifacts: [{Name: BuildOutput}]
# Deploy to each service instance in order
{% for instance in service_instances %}
- Name: "Deploy-{{ instance.name }}"
Actions:
- Name: "Deploy-{{ instance.name }}"
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: ECS
Version: "1"
Configuration:
ClusterName: "{{ instance.environment.outputs.ECSClusterArn }}"
ServiceName: "{{ service.name }}-{{ instance.name }}"
FileName: imagedefinitions.json
InputArtifacts: [{Name: BuildOutput}]
{% endfor %}
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal: { Service: codebuild.amazonaws.com }
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser
PipelineRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal: { Service: codepipeline.amazonaws.com }
Action: sts:AssumeRole
The {% for instance in service_instances %} loop is key — Proton automatically iterates over all instances of the service and injects them into the pipeline template. When you add a new instance (e.g., adding a prod instance to a service that previously only had dev), Proton updates the pipeline to include a new deploy stage for the new instance.
Platform Team Workflow: Versioning, Drift, and Self-Service
The real value of Proton emerges at scale — when you have tens of services and need to roll out an infrastructure change (patching a security group rule, updating the base AMI, adding a new required tag) across all of them without manually coordinating with every team. Proton's versioning and notification system is designed exactly for this.
Template versioning strategy:
- Minor versions (1.0 → 1.1): backward-compatible changes. Existing services can be upgraded in place. Examples: adding an optional parameter with a default value, patching a CloudFormation resource property, adding a new output.
- Major versions (1.x → 2.0): breaking changes. Services running on v1 are not automatically migrated. Examples: removing a parameter, changing a resource's logical ID (which destroys and recreates it), swapping compute type (ECS → EKS).
Notify service owners of available updates:
# List all service instances NOT on the latest minor version
aws proton list-service-instances \
--query 'serviceInstances[?templateMinorVersion!=`1`].{service:serviceName,instance:name,version:templateMinorVersion}'
# Check for drift on a specific service instance
aws proton get-service-instance \
--service-name payment-api \
--name prod \
--query 'serviceInstance.{status:deploymentStatus,driftStatus:lastDeploymentAttemptedAt}'
# Update a service instance to latest minor version
aws proton update-service-instance \
--service-name payment-api \
--name dev \
--deployment-type MINOR_VERSION \
--spec file://dev-instance-spec.yaml
# Bulk-update all instances of a service to latest
aws proton list-service-instances \
--service-name payment-api \
--query 'serviceInstances[*].name' \
--output text | \
xargs -I{} aws proton update-service-instance \
--service-name payment-api \
--name {} \
--deployment-type MINOR_VERSION \
--spec file://instance-spec.yaml
Drift detection: Proton detects when a deployed environment or service instance has drifted from its template — meaning someone made a manual change to the underlying CloudFormation stack. You can query drift status and trigger a re-deployment from the template to restore desired state:
# Notify Proton to check for drift (triggers CloudFormation drift detection)
aws proton notify-resource-deployment-status-change \
--resource-arn arn:aws:proton:us-east-1:123456789012:environment/prod \
--status IN_PROGRESS
# Re-deploy to restore template state
aws proton update-environment \
--name prod \
--deployment-type CURRENT_VERSION
Self-service flow from the developer perspective:
- Developer opens the Proton console (or Backstage plugin backed by Proton APIs).
- Browses available service templates. Each template shows its compatible environments, required parameters, and version history.
- Creates a new service: fills in the pipeline inputs once (source repo, branch), and fills in per-instance parameters (CPU, memory, port).
- Proton provisions the ECS task definition, service, security group, IAM role, and CodePipeline — all within the guardrails defined by the platform team.
- Developer pushes code. The Proton-managed pipeline builds the Docker image, pushes to ECR, and deploys to ECS. Developer never touches CloudFormation, IAM, or CodePipeline configuration.
- When the platform team releases a new minor version (e.g., adds a required X-Ray sidecar), developer receives a Proton notification. They approve the upgrade through the console or CLI with a single update command.
Proton Service Instance State Change and Proton Environment State Change events. You can route these to Slack (via Lambda + Incoming Webhook), PagerDuty, or your internal developer portal to give teams real-time deployment visibility without polling the Proton API.Example EventBridge rule for Proton deployment notifications:
{
"source": ["aws.proton"],
"detail-type": ["Proton Service Instance State Change"],
"detail": {
"previousDeploymentStatus": ["IN_PROGRESS"],
"deploymentStatus": ["SUCCEEDED", "FAILED"]
}
}
Route matches to a Lambda that posts to Slack: include the service name, instance name, template version deployed, and a deep link to the Proton console. Teams get instant feedback on every infrastructure change without anyone needing to poll the console manually.
Recommended platform team operating model:
- Maintain all templates in a single Git repository with branch protection. All changes go through pull request review.
- Use template sync so every merge to
mainautomatically publishes a new minor version — no manual CLI operations. - Keep major versions backward compatible for at least 90 days after a new major version is published. Give service owners a migration guide and a deadline.
- Automate drift reports: a weekly Lambda that calls
list-service-instancesand sends a digest of stale instances and drifted resources. - Use components for the long tail of resource needs. When a developer requests a new resource type (Redis, DynamoDB), build it as a component rather than forking the service template — this keeps the template surface area manageable.
Related Articles
Quick Reference
CLI: publish template
aws proton update-environment-template-version \
--status PUBLISHED
CLI: deploy environment
aws proton create-environment \
--name dev \
--template-name ecs-shared-env \
--template-major-version 1
CLI: upgrade service instance
aws proton update-service-instance \
--deployment-type MINOR_VERSION