AWS CodePipeline (CI/CD)
AWS CodePipeline is a fully managed service that automates the steps your code goes through from a commit to a live release. CI/CD stands for Continuous Integration and Continuous Delivery — the practice of automatically building, testing, and shipping every change so you never deploy by hand. CodePipeline is the orchestrator: it does not build or deploy anything itself, it just wires together other services (like CodeBuild and CodeDeploy) into a repeatable, visual workflow. If you have ever copied a build artifact to a server by hand and forgotten a step, this is the service that removes that risk.
How CodePipeline is structured
A pipeline is a sequence of stages, and each stage contains one or more actions. An action does a single unit of work — pull source code, run a build, deploy to production. Stages run in order, top to bottom. Within one stage, actions can run in parallel (great for fanning out tests) or in sequence (using “run order”).
The most common shape is three stages:
| Stage | What it does | Typical action provider |
|---|---|---|
| Source | Detects a new commit and grabs the code | GitHub, CodeCommit, S3, ECR |
| Build | Compiles, runs unit tests, packages an artifact | CodeBuild |
| Deploy | Pushes the artifact to your environment | CodeDeploy, CloudFormation, ECS, Elastic Beanstalk |
You can add as many stages as you like — for example a separate Test, Approval, and DeployProd stage.
Why this matters: Each stage hands its results to the next as an artifact (a zipped bundle of files stored in S3). The Source stage produces an output artifact; the Build stage takes that as its input and produces a new one; the Deploy stage consumes that. If those names don’t line up, the pipeline breaks — more on that gotcha below.
When to use CodePipeline (and when not)
Use it when you want a managed, AWS-native CI/CD flow that deploys to AWS targets (ECS, Lambda, EC2, CloudFormation) and you value tight IAM and CloudWatch integration. Do not reach for it if your team already lives in GitHub Actions or GitLab CI and only occasionally touches AWS — running two CI systems adds confusion. CodePipeline shines when AWS is your platform.
Manual approvals
A manual approval action pauses the pipeline until a human clicks “Approve” in the console (or via the CLI). Use it before a production deploy so a person signs off after staging looks healthy. It is a free, zero-config gate. When NOT to use it: in a fully automated continuous deployment flow where speed matters more than a human checkpoint.
A simple pipeline (CloudFormation)
This template defines a three-stage pipeline that pulls from GitHub (via a CodeStar connection), builds with CodeBuild, and deploys a CloudFormation stack — with an approval gate before deploy.
Resources:
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: my-app-pipeline
RoleArn: arn:aws:iam::123456789012:role/CodePipelineServiceRole
ArtifactStore:
Type: S3
Location: my-pipeline-artifacts-123456789012
Stages:
- Name: Source
Actions:
- Name: GitHubSource
ActionTypeId: { Category: Source, Owner: AWS, Provider: CodeStarSourceConnection, Version: "1" }
Configuration:
ConnectionArn: arn:aws:codestar-connections:us-east-1:123456789012:connection/abcd1234
FullRepositoryId: my-org/my-app
BranchName: main
OutputArtifacts: [{ Name: SourceOut }]
- Name: Build
Actions:
- Name: BuildAndTest
ActionTypeId: { Category: Build, Owner: AWS, Provider: CodeBuild, Version: "1" }
Configuration: { ProjectName: my-app-build }
InputArtifacts: [{ Name: SourceOut }]
OutputArtifacts: [{ Name: BuildOut }]
- Name: Deploy
Actions:
- Name: ApproveDeploy
ActionTypeId: { Category: Approval, Owner: AWS, Provider: Manual, Version: "1" }
RunOrder: 1
- Name: DeployStack
ActionTypeId: { Category: Deploy, Owner: AWS, Provider: CloudFormation, Version: "1" }
RunOrder: 2
InputArtifacts: [{ Name: BuildOut }]
Configuration:
ActionMode: CREATE_UPDATE
StackName: my-app-stack
TemplatePath: BuildOut::template.yaml
Capabilities: CAPABILITY_IAM
RoleArn: arn:aws:iam::123456789012:role/CfnDeployRole
Notice how SourceOut flows into Build, and BuildOut flows into Deploy. Those names are the contract between stages.
Creating a pipeline
Console steps
- Open the CodePipeline console and choose Create pipeline.
- Name it (e.g.
my-app-pipeline), let AWS create a new service role, and accept the default S3 artifact store. Click Next. - Source provider: pick GitHub (via Connect to GitHub), CodeCommit, or S3. Select the repo and branch. Click Next.
- Build provider: choose AWS CodeBuild and select (or create) a build project. Click Next.
- Deploy provider: choose CodeDeploy, CloudFormation, ECS, or Elastic Beanstalk and fill in the target. Click Next.
- Review and click Create pipeline. It runs immediately on the latest commit.
CLI equivalent
Define the pipeline structure in a JSON file and create it:
aws codepipeline create-pipeline --cli-input-json file://pipeline.json
Output:
{
"pipeline": {
"name": "my-app-pipeline",
"roleArn": "arn:aws:iam::123456789012:role/CodePipelineServiceRole",
"artifactStore": { "type": "S3", "location": "my-pipeline-artifacts-123456789012" },
"stages": [ ... ],
"version": 1
}
}
Manually trigger a run, then check its state:
aws codepipeline start-pipeline-execution --name my-app-pipeline
aws codepipeline get-pipeline-state --name my-app-pipeline
Output:
{
"stageStates": [
{ "stageName": "Source", "latestExecution": { "status": "Succeeded" } },
{ "stageName": "Build", "latestExecution": { "status": "InProgress" } },
{ "stageName": "Deploy", "latestExecution": { "status": "Failed" } }
]
}
Approve a waiting manual-approval action from the CLI:
aws codepipeline put-approval-result \
--pipeline-name my-app-pipeline \
--stage-name Deploy --action-name ApproveDeploy \
--result summary="staging looks good",status=Approved \
--token <token-from-get-pipeline-state>
The gotcha: roles, buckets, and artifact names
Two failure patterns cause the overwhelming majority of “it worked locally but the pipeline is red” tickets, and neither is a code problem.
1. The service role is missing a permission. CodePipeline assumes a single IAM role to orchestrate every action. That role needs permission for each thing it touches — codebuild:StartBuild, cloudformation:CreateStack, codedeploy:CreateDeployment, plus s3:GetObject/PutObject on the artifact bucket and kms:Decrypt if the bucket is encrypted with a customer-managed key. Add a deploy provider and forget the IAM line, and the stage fails with an access-denied error that has nothing to do with your application.
2. The artifact handoff doesn’t line up. When a stage reports Cannot find input artifact (or a stage simply has no files), it is almost always because the input artifact name does not exactly match an earlier stage’s output name, or the role can’t read the S3 artifact bucket / KMS key. Check three things in order: the OutputArtifacts/InputArtifacts names match, the role has S3 access to the artifact bucket, and the role has KMS decrypt rights. The code is fine.
Cost note: Your first V1 pipeline per account is free; additional active V1 pipelines are about $1/month each. The real cost is the work CodePipeline triggers — CodeBuild compute minutes and the resources you deploy. The S3 artifact bucket is pennies, but turn on a lifecycle rule to expire old artifacts so it doesn’t grow forever.
Best practices
- Give the pipeline service role least-privilege permissions — scope S3 and KMS actions to the specific artifact bucket and key, not
*. - Put a manual approval before any production stage, and notify approvers via an SNS topic.
- Enable encryption on the artifact bucket and add an S3 lifecycle rule to delete artifacts older than 30 days.
- Keep build logic in a
buildspec.ymlinside your repo, not in the pipeline — so the build is versioned with the code. - Use CloudWatch Events / EventBridge triggers for source changes instead of polling; it’s faster and avoids API throttling.
- Define the pipeline as infrastructure as code (CloudFormation or CDK) so it is reviewable and reproducible across accounts.