CoursesAWS DevOps Engineer ProfessionalCodePipeline: orchestrating delivery

CodePipeline: orchestrating delivery

Stages, actions, artifacts, approvals, secure roles.

Beginner30 min · lesson 1 of 15

A car factory does not build a car in one room. Raw steel goes in at one end, each station bolts on one more part and hands the shell down the line, and an inspected car rolls out the far end. If a station finds a cracked weld, the belt stops. AWS CodePipeline (Amazon Web Services' managed release orchestration service) is that belt for your code. You describe a release as an ordered list of stages, CodePipeline watches your source repository for changes, and every change gets walked through build, test, approval and deployment on its own. CodePipeline compiles nothing and deploys nothing itself. It is the foreman calling out the order of work, invoking CodeBuild, CodeDeploy, CloudFormation, ECS (Elastic Container Service) and Lambda at the right moment, and halting the line the second one of them reports a failure. Stopping fast, on an event rather than on a timer, is what continuous delivery on AWS is built on.

Stages, actions, and where the artifacts actually live

A pipeline is a list of stages, and each stage holds one or more actions. An action is one unit of work. It has a category (Source, Build, Test, Approval, Deploy or Invoke), an owner (AWS, ThirdParty or Custom), and a provider, which is the concrete service doing the work, such as CodeBuild. Each action names the artifacts it takes in and the artifacts it puts out, where an artifact is a named bundle of files. Each action also carries a runOrder number. Actions sharing a runOrder start together, and higher numbers wait for lower ones to finish, so you get fan-out and sequencing inside a single stage without inventing more stages. Between stages, nothing is handed over in memory. Every pipeline owns an artifact store, an S3 (Simple Storage Service) bucket sitting in the pipeline's Region. Each output artifact is zipped, written to that bucket, and read back out by the next action. A stage only hands over to the next one when every action in it has succeeded. One failed action stops the run cold and leaves later stages untouched, which is the fail-fast behaviour you want. It also means a deleted bucket, a bucket policy somebody tightened, or an artifact encrypted with the wrong key will break a pipeline that looks perfectly healthy on screen.

inspect live stage/action status
$ aws codepipeline get-pipeline-state --name web-prod \
--query 'stageStates[].{stage:stageName,status:latestExecution.status}' \
--output table
------------------------------------
| GetPipelineState |
+-----------+----------------------+
| stage | status |
+-----------+----------------------+
| Source | Succeeded |
| Build | Succeeded |
| Approve | Succeeded |
| Deploy | InProgress |
+-----------+----------------------+
# Deploy is the only stage still running; every earlier stage passed and
# handed its output artifact to the next through the S3 artifact store.

V2 pipelines and the triggers that start them

CodePipeline comes in two versions. V1 is the original. V2 arrived in late 2023 and carries the features production teams keep asking for: git-push triggers you can filter by branch, tag and file path; pipeline-level variables you can pass into actions; stage-level conditions and rollback; and the QUEUED and PARALLEL execution modes. How a source change reaches the pipeline depends on the provider. Changes in CodeCommit and S3 arrive over EventBridge, the AWS event bus that routes events between services, and the pipeline starts within seconds. GitHub, GitLab and Bitbucket come in through AWS CodeConnections (the service formerly called CodeStar Connections) on a managed webhook, a callback URL the git host pings on every push. Old-style polling still works, but it adds latency and burns API calls, so reach for the event-driven path. A tight trigger filter pays for itself fast: one pipeline can react only to pushes on main that touch src/**, so a typo fix in the README never spends a build minute. Pipeline variables let a single definition serve dev, staging and prod without you copying the same actions three times.

confirm pipeline type and read the trigger filter
$ aws codepipeline get-pipeline --name web-prod \
--query 'pipeline.{type:pipelineType,mode:executionMode}'
{
"type": "V2",
"mode": "SUPERSEDED"
}
$ aws codepipeline get-pipeline --name web-prod --query 'pipeline.triggers'
[
{
"providerType": "CodeStarSourceConnection",
"gitConfiguration": {
"sourceActionName": "Source",
"push": [
{
"branches": { "includes": ["main"] },
"filePaths": { "includes": ["src/**"] }
}
]
}
}
]
# Only a push to main that changes files under src/ starts this pipeline.

Execution modes: when commits arrive faster than you can deploy

Execution mode is the rule for what happens when changes land faster than a run can finish. It is a V2-only control, and it catches out teams who assume every commit they push reaches production. SUPERSEDED is the default. A newer execution overtakes an older one still sitting outside a stage waiting to get in, and the older run is stopped with the status Superseded, so only the newest change lands and the commits in between may never deploy at all. QUEUED behaves like a single-file queue at a bank teller: executions run one at a time in the order they arrived, and every change gets processed. PARALLEL lets executions run side by side and independently, which suits a pipeline that fans out per feature branch. The mode is a property of the pipeline, so pick it on purpose. This is a correctness decision about which of your commits reach production, not a performance knob.

A new commit lands while a run is still in flight
New commit mid-execution
the V2 executionMode decides what happens next
SUPERSEDED
default: latest wins
the newer run overtakes the older one waiting between stages; the older stops as Superseded, so commits in the middle may never deploy
QUEUED
one at a time
executions run in arrival order; every change gets processed
PARALLEL
independent
runs proceed side by side; fits per-branch or fan-out pipelines
Pick SUPERSEDED for fast trunk deploys where only the latest matters, QUEUED when every change must ship, PARALLEL for independent branches.

Least privilege, and deploying into another account

A pipeline that can push to production is, in effect, a key to production, so its identity deserves the same care you would give a human administrator. Three kinds of role are in play. The pipeline service role, trusted by codepipeline.amazonaws.com, is what CodePipeline assumes to read the artifact bucket, invoke actions and send approval notifications. Any individual action can also carry its own roleArn, and that role can live in a different AWS account, which is how one central tooling account deploys into isolated dev and prod accounts without a single credential being copied anywhere. Then the services the pipeline calls, CodeBuild among them, run under roles of their own. Scope each one to exactly what its stage touches. A wildcard on any of the three turns one compromised build step into account-wide access. Keep long-lived keys out of this entirely: use CodeConnections for git, use OIDC federation (OpenID Connect, which mints a short-lived token per run instead of storing a secret) for outside CI systems, and pull secrets from Secrets Manager or SSM Parameter Store (the config and secret store inside AWS Systems Manager) while the action runs, rather than baking them into an artifact. For cross-account work, encrypt the artifact store with a customer-managed KMS key (Key Management Service, where AWS holds your encryption keys) and grant that key to the remote account's roles. A forgotten key grant is the most common way a cross-account deploy fails.

audit the service role and artifact-store encryption
$ aws codepipeline get-pipeline --name web-prod \
--query 'pipeline.{role:roleArn,store:artifactStore}'
{
"role": "arn:aws:iam::111122223333:role/service-role/AWSCodePipelineServiceRole-us-east-1-web-prod",
"store": {
"type": "S3",
"location": "codepipeline-us-east-1-8f3a2c9b41d7",
"encryptionKey": {
"id": "arn:aws:kms:us-east-1:111122223333:key/9b2c7e10-4a1d-4f83-b0c5-2d7e9f1a6b04",
"type": "KMS"
}
}
}
# A customer-managed KMS key on the artifact store is what lets a role in
# another account decrypt artifacts during a cross-account deploy.
One wildcard role and your pipeline becomes a back door into production
CodePipeline, the roles attached to its individual actions, and the services it calls (CodeBuild, CodeDeploy) each run under an IAM role (Identity and Access Management, the AWS permission system) that can reach production. Put Action: "*" on any of them and one compromised build step, one poisoned dependency or one leaked script can do anything in that account: read every secret, delete data, assume other roles. Scope every role to the exact resources and actions its stage needs. Use CodeConnections and OIDC in place of long-lived keys. And when you deploy cross-account, grant the artifact-store KMS key to the remote roles instead of widening the pipeline role until things start working.

What it costs, and where the limits bite

Price and quotas quietly shape how you carve up pipelines. V1 charges a flat 1.00 USD per active pipeline per month, where active means it ran at least once that month; idle pipelines cost nothing, and one active pipeline per account is free. V2 drops the per-pipeline fee and bills by the minute an action spends executing, 0.002 USD per action-execution-minute, with 100 free action-execution minutes per account per month. So a busy pipeline full of long actions can cost more on V2, while a shelf of pipelines that rarely run gets cheaper. Model your real usage before you migrate everything. On quotas, the default is 1000 pipelines per Region per account and you can raise it through Service Quotas. A separate fixed cap of 300 applies only to pipelines still using source polling, and that one does not move. Inside a stage, runOrder is your unit of parallelism. Manual approvals bring one limit worth memorising: an approval nobody clicks expires after seven days by default and fails the stage, which is how a release quietly dies over a long holiday weekend.

trigger a run and read execution history
$ aws codepipeline start-pipeline-execution --name web-prod
{
"pipelineExecutionId": "b3d5e1a2-4f6c-4a1e-9c2d-7e8f0a1b2c3d"
}
$ aws codepipeline list-pipeline-executions --name web-prod --max-items 3 \
--query 'pipelineExecutionSummaries[].{id:pipelineExecutionId,status:status,trigger:trigger.triggerType}' \
--output table
-------------------------------------------------------------
| ListPipelineExecutions |
+---------------+-------------+---------------------------+
| id | status | trigger |
+---------------+-------------+---------------------------+
| b3d5e1a2... | InProgress | StartPipelineExecution |
| a1c47f90... | Superseded | WebhookV2 |
| 90fbe233... | Succeeded | WebhookV2 |
+---------------+-------------+---------------------------+
# The Superseded row: a newer push overtook that run before it could deploy.
# WebhookV2 is the trigger type for a CodeConnections git push.

CodePipeline orchestrates; it does not build. Stages fail closed, so a red CodeBuild action means nothing gets promoted to prod. The artifacts in S3, or the container images referenced by digest in ECR (Elastic Container Registry), are the baton passed from stage to stage. If that baton can be swapped after the tests ran, what you call prod is not what you tested.

V2 gives you sharper triggers and parallel actions, and the execution mode decides whether a burst of commits supersedes or queues. A manual approval is still a human standing at a gate. Put those gates where the risk genuinely jumps, not in front of every README typo.

A cross-account deploy role should be assumable by exactly one principal: the pipeline role in the tooling account. Hand the pipeline role AdministratorAccess instead, and a single compromised GitHub OIDC trust relationship burns three environments at once.

Try this

List your pipelines, then read the stage states of one execution. Every command here is read-only, and that is enough to watch fail-fast orchestration with your own eyes.

terminal
aws codepipeline list-pipelines --query 'pipelines[].name' --output table
aws codepipeline list-pipeline-executions --pipeline-name app-delivery --max-items 3 \
--query 'pipelineExecutionSummaries[].{Id:pipelineExecutionId,Status:status,Started:startTime}' --output table
aws codepipeline get-pipeline-state --name app-delivery \
--query 'stageStates[].{Stage:stageName,Status:latestExecution.status}' --output table
output
app-delivery
---------------------------------------------
| ListPipelineExecutions |
+----------+----------+---------------------+
| Id | Status | Started |
+----------+----------+---------------------+
| exe-123 | Succeeded| 2026-07-23T18:01:00Z|
+----------+----------+---------------------+
Source | Succeeded
Build | Succeeded
Deploy | InProgress

Takeaway

The pipeline is the foreman, not the worker. Artifacts that cannot change after the tests pass, roles scoped to one stage each, and stages that stop dead on the first failure are what make an automated line safe to point at production.

Next thing to try on your own pipeline: put one gate on the road to prod, either a manual approval or an automatic rollback, then deliberately break a build and prove it cannot slip past.

Quick check
01Your V2 pipeline is running in the default SUPERSEDED execution mode. Someone has temporarily disabled the transition into the Deploy stage, so commit A has cleared Source and Build and is parked outside Deploy, waiting. While the transition is still off, a developer pushes commit B, which sails through Source and Build and lines up behind A. You re-enable the transition. What most likely happens?
Correct — A newer execution supersedes an older one waiting to enter a stage. The moment the transition opens, B (the newest) goes into Deploy and A stops as Superseded, so A never deploys.
Incorrect — That is PARALLEL mode, and it is not the default. SUPERSEDED never pushes two executions through the same stage at once.
Incorrect — That describes QUEUED mode, which keeps arrival order and ships every change. Under SUPERSEDED the newer run overtakes the older waiting one.
Incorrect — CodePipeline never turns B away. The newer execution supersedes the waiting A and takes its slot.
02An action in one stage produces an output artifact. In AWS CodePipeline, how does that artifact actually reach the action in the next stage that needs it?
Incorrect — No. Artifacts are not held in memory between stages; they are written to storage first.
Correct — Every pipeline has an S3 artifact store in its own Region, and artifacts are zipped in there and read back downstream.
Incorrect — No. Artifacts do not travel over the wire between stages; the S3 bucket sits in the middle.
Incorrect — No. Elastic Container Registry holds container images, not general pipeline artifacts, which live in the S3 artifact store.
03A central tooling account runs a CodePipeline pipeline that has to deploy into a separate, locked-down production account. Your security team will not allow any long-lived credential to be stored anywhere. What is the MOST secure way to let the pipeline deploy across that account boundary?
Incorrect — No. Long-lived access keys are exactly the stored credential the policy rules out, and they leak.
Incorrect — No. A wildcard role makes the pipeline a back door into production, and on its own it grants no cross-account access at all.
Correct — A per-action cross-account role plus a customer-managed KMS key granted to the remote role is how a tooling account deploys without sharing any credential.
Incorrect — No. A public artifact bucket hands your build output to anyone who finds it, and is never an acceptable posture.

The busiest station on this line is the build, where a git commit becomes the tested, packaged artifact everything downstream trusts. That is where the next lesson goes. CodeBuild: fast, secure builds covers buildspec phases, caching dependencies and image layers, custom build images, and how to keep a build both quick and hard for anyone to tamper with.

Related