Guide · AWS CodePipeline · CI/CD Automation
AWS CodePipeline for MCP Servers — CI/CD Pipeline Automation, GitHub Source, ECS Deploy
AWS CodePipeline is a fully managed continuous delivery service that orchestrates build, test, and deploy stages every time you push code — no Jenkins, no CircleCI, no infrastructure to babysit. For MCP server developers, CodePipeline solves the "how do I ship a new version of my MCP server without touching the production cluster by hand" problem: a push to the main branch triggers the pipeline, CodeBuild compiles and packages the server, and CodeDeploy (or the ECS deploy action) swaps in the new task definition with zero-downtime traffic shifting. The pipeline runs inside your VPC boundary and IAM permission model — no third-party CI system has access to your AWS environment. Critical CodePipeline decisions: choosing between V1 (JSON-based, console-centric) and V2 (YAML-based, supports pipeline-level variables and triggers), structuring the artifact store correctly, configuring the pipeline service role with least-privilege, and picking the right execution mode (SUPERSEDED, QUEUED, or PARALLEL) to avoid race conditions during rapid pushes.
TL;DR
Create a V2 CodePipeline with three stages: Source (GitHub v2 CodeStarConnections action), Build (CodeBuild project action), Deploy (ECS deploy action or CodeDeploy blue-green action). Set executionMode: SUPERSEDED so that a second push cancels the in-flight run from the first. Create a dedicated S3 bucket as the artifact store with versioning enabled. Attach a pipeline service role that has codestar-connections:UseConnection, codebuild:StartBuild, ecs:RegisterTaskDefinition, and iam:PassRole. See the pipeline stage design guide for action type configuration and the blue-green ECS deployment guide for zero-downtime traffic shifting.
Pipeline structure and artifact flow
A CodePipeline pipeline is a sequence of stages. Each stage contains one or more actions. Actions within a stage can run in parallel (same runOrder) or sequentially (different runOrder values). Artifacts pass between stages through S3 — the pipeline writes the output artifact of one action to S3 and reads it as input to the next action. You never copy files manually; the pipeline service role handles all artifact reads and writes.
# Minimal V2 pipeline structure (JSON)
{
"pipeline": {
"name": "mcp-server-pipeline",
"pipelineType": "V2",
"roleArn": "arn:aws:iam::123456789012:role/CodePipelineServiceRole",
"executionMode": "SUPERSEDED",
"artifactStore": {
"type": "S3",
"location": "my-codepipeline-artifacts-us-east-1"
},
"stages": [
{
"name": "Source",
"actions": [
{
"name": "GitHub",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "CodeStarSourceConnection",
"version": "1"
},
"configuration": {
"ConnectionArn": "arn:aws:codestar-connections:us-east-1:123456789012:connection/abc123",
"FullRepositoryId": "my-org/mcp-server",
"BranchName": "main",
"DetectChanges": "true",
"OutputArtifactFormat": "CODEBUILD_CLONE_REF"
},
"outputArtifacts": [{ "name": "SourceOutput" }]
}
]
},
{
"name": "Build",
"actions": [
{
"name": "BuildAndPush",
"actionTypeId": {
"category": "Build",
"owner": "AWS",
"provider": "CodeBuild",
"version": "1"
},
"configuration": {
"ProjectName": "mcp-server-build",
"EnvironmentVariables": "[{\"name\":\"IMAGE_TAG\",\"value\":\"#{codepipeline.PipelineExecutionId}\",\"type\":\"PLAINTEXT\"}]"
},
"inputArtifacts": [{ "name": "SourceOutput" }],
"outputArtifacts": [{ "name": "BuildOutput" }]
}
]
},
{
"name": "Deploy",
"actions": [
{
"name": "DeployToECS",
"actionTypeId": {
"category": "Deploy",
"owner": "AWS",
"provider": "ECS",
"version": "1"
},
"configuration": {
"ClusterName": "mcp-cluster",
"ServiceName": "mcp-server-service",
"FileName": "imagedefinitions.json"
},
"inputArtifacts": [{ "name": "BuildOutput" }]
}
]
}
]
}
}
The key artifact handoff is the imagedefinitions.json file that the Build stage writes and the ECS Deploy action reads. This file maps container names to ECR image URIs so the deploy action knows which image to place into the task definition update:
# imagedefinitions.json — written by buildspec.yml post_build phase
[
{
"name": "mcp-server",
"imageUri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/mcp-server:abc123def456"
}
]
# In buildspec.yml post_build:
post_build:
commands:
- IMAGE_URI="$ECR_REGISTRY/$ECR_REPO:$CODEBUILD_RESOLVED_SOURCE_VERSION"
- echo "[{\"name\":\"mcp-server\",\"imageUri\":\"$IMAGE_URI\"}]" > imagedefinitions.json
artifacts:
files:
- imagedefinitions.json
- appspec.yaml # needed only for CodeDeploy blue-green deploy
If you use CodeDeploy blue-green deployment instead of the ECS rolling deploy action, replace the ECS deploy action with a CodeDeploy action and output both imagedefinitions.json and appspec.yaml from CodeBuild. The two deploy strategies differ: the ECS rolling deploy action modifies the existing service in-place (older tasks replaced gradually); the CodeDeploy blue-green deploy action creates a second target group, routes traffic to it, and only terminates the original tasks after a configurable wait period — enabling one-click rollback during that window.
GitHub connection (CodeStar Connections)
The GitHub V2 source action uses AWS CodeStar Connections, not OAuth tokens stored in Secrets Manager. A Connection is a resource you create once in the console (AWS CodePipeline → Settings → Connections → Create connection → GitHub), authorize via the GitHub OAuth flow, and then reference by ARN. The connection is region-scoped and account-scoped — you cannot share a connection across accounts directly.
# Check connection status — must be AVAILABLE, not PENDING
aws codestar-connections list-connections \
--provider-type GitHub \
--query 'Connections[*].{Name:ConnectionName,Status:ConnectionStatus,Arn:ConnectionArn}'
# Output shows ConnectionStatus: "AVAILABLE" once GitHub authorization is complete
# A PENDING connection causes the source action to fail with "Pending connection"
# The pipeline service role needs this permission to use the connection:
{
"Effect": "Allow",
"Action": "codestar-connections:UseConnection",
"Resource": "arn:aws:codestar-connections:us-east-1:123456789012:connection/abc123"
}
The OutputArtifactFormat setting on the source action controls what the Build stage receives. CODEBUILD_CLONE_REF provides a full Git clone reference — CodeBuild clones the repo with full history, which lets CODEBUILD_RESOLVED_SOURCE_VERSION resolve to the full commit SHA. CODE_ZIP (the default) provides a ZIP archive without Git metadata. Use CODEBUILD_CLONE_REF if your build tags Docker images with the Git SHA — that requires the CodeBuild project's service role to have codestar-connections:UseConnection as well.
For monorepos, use the V2 pipeline trigger feature to filter which file paths cause an execution. Without a trigger filter, every push to the branch triggers the pipeline regardless of which files changed:
# V2 pipeline trigger filter — only trigger on changes in mcp-server/
"triggers": [
{
"providerType": "CodeStarSourceConnection",
"gitConfiguration": {
"sourceActionName": "GitHub",
"push": [
{
"branches": {
"includes": ["main"]
},
"filePaths": {
"includes": ["mcp-server/**"]
}
}
]
}
}
]
Pipeline service role
The pipeline service role is assumed by CodePipeline itself — not by CodeBuild or CodeDeploy — to orchestrate the pipeline: reading source artifacts, starting build jobs, passing artifacts between stages, and invoking deploy actions. Misconfiguring this role is the most common reason pipelines fail to start or get stuck at a stage transition.
# Minimum pipeline service role policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ArtifactStore",
"Effect": "Allow",
"Action": [
"s3:GetObject", "s3:PutObject", "s3:GetBucketVersioning",
"s3:GetObjectVersion"
],
"Resource": [
"arn:aws:s3:::my-codepipeline-artifacts-us-east-1",
"arn:aws:s3:::my-codepipeline-artifacts-us-east-1/*"
]
},
{
"Sid": "Connection",
"Effect": "Allow",
"Action": "codestar-connections:UseConnection",
"Resource": "arn:aws:codestar-connections:us-east-1:123456789012:connection/abc123"
},
{
"Sid": "CodeBuild",
"Effect": "Allow",
"Action": ["codebuild:StartBuild", "codebuild:BatchGetBuilds"],
"Resource": "arn:aws:codebuild:us-east-1:123456789012:project/mcp-server-build"
},
{
"Sid": "ECS",
"Effect": "Allow",
"Action": [
"ecs:DescribeServices", "ecs:DescribeTaskDefinition",
"ecs:RegisterTaskDefinition", "ecs:UpdateService"
],
"Resource": "*"
},
{
"Sid": "PassRole",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"Condition": {
"StringEquals": {
"iam:PassedToService": "ecs-tasks.amazonaws.com"
}
}
},
{
"Sid": "KMS",
"Effect": "Allow",
"Action": ["kms:GenerateDataKey", "kms:Decrypt"],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/your-cmk-key-id"
}
]
}
The iam:PassRole permission is required so CodePipeline can pass the ECS task execution role when registering new task definitions. Without it, the ECS deploy action fails with AccessDenied: User is not authorized to perform iam:PassRole even though the underlying ECS permission is in place. Scope iam:PassRole to the specific task execution role ARN using the iam:PassedToService: ecs-tasks.amazonaws.com condition to limit blast radius.
If you encrypt the artifact store S3 bucket with a customer-managed KMS key (recommended for production), the pipeline role needs kms:GenerateDataKey and kms:Decrypt on that key. The CodeBuild project role also needs the same KMS permissions — artifacts written by the pipeline role are encrypted, and CodeBuild must decrypt them to read the source artifact.
Execution modes
CodePipeline V2 pipelines support three execution modes that control behavior when a new push arrives while an execution is already running:
| Mode | Behavior | Use case |
|---|---|---|
SUPERSEDED | New push cancels the in-flight execution; the newest revision wins | Most MCP deployments — ship the latest commit, discard intermediate ones |
QUEUED | New push queues behind the current execution; they run in order | Compliance-sensitive deployments where every commit must be deployed in sequence |
PARALLEL | Multiple executions run simultaneously; no coordination | Multi-tenant pipelines running independent deployments triggered by different parameters |
SUPERSEDED is correct for most MCP server pipelines: if you push twice in 30 seconds, you only care about deploying the latest commit. QUEUED prevents you from skipping intermediate commits but can cause a pipeline backlog if you push frequently during a hot fix session. PARALLEL is rarely correct for application deployments — two ECS deploys running simultaneously on the same service will conflict.
# Update execution mode on an existing V1 pipeline (upgrade to V2 first)
aws codepipeline update-pipeline --cli-input-json file://pipeline.json
# pipeline.json must include "pipelineType": "V2" and "executionMode": "SUPERSEDED"
# Check current execution mode
aws codepipeline get-pipeline \
--name mcp-server-pipeline \
--query 'pipeline.executionMode'
Artifact store configuration
The artifact store is an S3 bucket that CodePipeline uses to pass artifacts between stages. Every stage transition reads from and writes to this bucket. Misconfiguring the bucket (no versioning, wrong region, missing encryption) causes cryptic pipeline failures.
# Create artifact store bucket — must be in same region as pipeline
aws s3api create-bucket \
--bucket my-codepipeline-artifacts-us-east-1 \
--region us-east-1
# Enable versioning (required by CodePipeline)
aws s3api put-bucket-versioning \
--bucket my-codepipeline-artifacts-us-east-1 \
--versioning-configuration Status=Enabled
# Block public access
aws s3api put-public-access-block \
--bucket my-codepipeline-artifacts-us-east-1 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
# Enable server-side encryption (SSE-S3 is fine; use KMS for HIPAA/PCI workloads)
aws s3api put-bucket-encryption \
--bucket my-codepipeline-artifacts-us-east-1 \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/your-cmk-key-id"
},
"BucketKeyEnabled": true
}]
}'
# Add lifecycle rule to expire old artifacts (keep 30 days)
aws s3api put-bucket-lifecycle-configuration \
--bucket my-codepipeline-artifacts-us-east-1 \
--lifecycle-configuration '{
"Rules": [{
"ID": "expire-artifacts",
"Status": "Enabled",
"Filter": {},
"Expiration": { "Days": 30 },
"NoncurrentVersionExpiration": { "NoncurrentDays": 7 }
}]
}'
The artifact store bucket must be in the same AWS region as the pipeline. If you need to deploy to multiple regions from a single pipeline, use cross-region action configurations — each stage action can specify a different region and a separate per-region artifact bucket. The pipeline replicates artifacts to regional buckets before passing them to cross-region actions.
Failure modes reference
| Failure | Symptom | Fix |
|---|---|---|
| Source action stuck on PENDING connection | Pipeline never starts; source action stays "InProgress" indefinitely | CodeStar Connection is in PENDING state — go to AWS CodePipeline → Settings → Connections → select connection → Complete handshake via GitHub OAuth flow |
| Build action fails: no such artifact | CodeBuild action error: "Artifact not found" | OutputArtifactFormat: CODEBUILD_CLONE_REF requires the CodeBuild project service role to have codestar-connections:UseConnection — missing this causes Git clone to fail silently and present as artifact error |
| Deploy fails: AccessDenied iam:PassRole | ECS deploy action fails with IAM error | Pipeline service role lacks iam:PassRole on the ECS task execution role — add the PassRole permission scoped to the specific execution role ARN |
| KMS decrypt error on artifact | Build or deploy action fails immediately with KMS error | CodeBuild project role or CodeDeploy role lacks kms:Decrypt on the artifact store CMK — each role that reads artifacts needs KMS decrypt permission |
| Pipeline not triggered on push | Push to main branch does not start pipeline | For V2 pipelines with file path filters, verify the changed file paths match the filePaths.includes pattern; an overly specific filter silently drops triggers that don't match |
| PARALLEL mode ECS conflict | Two simultaneous ECS deployments fail or produce inconsistent state | PARALLEL mode is wrong for same-service ECS deployments; change to SUPERSEDED — two concurrent ECS UpdateService calls on the same service interfere |