
Build Your First CI/CD Pipeline on AWS with CodePipeline: CB Speed Run
Why CI/CD, and what we're building
In this guide you'll build a pipeline that tests and deploys your code on its own every time you push to GitHub. It takes about 30 minutes, and you never have to upload files by hand again.
Many of us start on AWS by dragging files into the S3 console. That works until the day you upload the wrong version and break your site. CI/CD (continuous integration and continuous delivery) fixes that: every change goes through the same build, the same tests and the same deploy, every time.
By the end you'll have:
- A GitHub repo connected to AWS CodePipeline
- An AWS CodeBuild stage that installs dependencies, runs tests and builds the site
- A deploy stage that publishes the build to an Amazon S3 static website
- A pipeline that stops the deploy if any test fails
Architecture

A push to
main starts the pipeline. CodeBuild runs the tests and builds the site; only a passing build is copied to S3.Prerequisites
- An AWS account with permission to create IAM roles, S3 buckets, CodeBuild projects and pipelines
- A GitHub account
- Node.js 20 or later installed locally (only to test before you push)
- Basic Git:
clone,commit,push
Use one AWS Region for everything. I use
us-east-1 in this guide; swap in your own.Step 1: Prepare the sample app and buildspec.yml
Create a new GitHub repo named
cicd-demo. The app is a small static site with one test and no outside dependencies, so the focus stays on the pipeline.1
2
3
4
5
6
cicd-demo/
├── src/index.html
├── test/site.test.js
├── build.js
├── package.json
└── buildspec.ymlsrc/index.html
1
2
3
4
5
6
7
8
9
10
<html lang="en">
<head>
<meta charset="utf-8">
<title>CI/CD Demo</title>
</head>
<body>
<h1>Deployed by CodePipeline - v1</h1>
</body>
</html>test/site.test.js uses Node's built-in test runner, so there's nothing to install:
1
2
3
4
5
6
7
8
import { test } from 'node:test';
import assert from 'node:assert';
import { readFileSync } from 'node:fs';
test('home page has a title', () => {
const html = readFileSync('src/index.html', 'utf8');
assert.match(html, /<title>.+<\/title>/);
});build.js copies the site into a
dist folder:1
2
3
4
5
import { cpSync, rmSync } from 'node:fs';
rmSync('dist', { recursive: true, force: true });
cpSync('src', 'dist', { recursive: true });
console.log('Build complete: dist/');package.json
1
2
3
4
5
6
7
8
9
{
"name": "cicd-demo",
"version": "1.0.0",
"type": "module",
"scripts": {
"test": "node --test",
"build": "node build.js"
}
}buildspec.yml tells CodeBuild what to run. It must sit at the root of the repo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: 0.2
phases:
install:
runtime-versions:
nodejs: 20
build:
commands:
- npm test
- npm run build
artifacts:
base-directory: dist
files:
- '**/*'If
npm test fails, CodeBuild stops and the deploy never runs. base-directory: dist makes sure only the built site is deployed, not your tests or config.Run
npm test && npm run build locally once, then commit and push to the main branch.Step 2: Create the S3 bucket for hosting
- Open the S3 console and choose Create bucket.
- Name it something unique, like
cicd-demo-site-<your-initials>. - Under Block Public Access settings, clear Block all public access and confirm the warning. This is needed only because this demo serves a public website.
- Create the bucket, open it and go to Properties.
- Scroll to Static website hosting, choose Edit, enable it and set the index document to
index.html. Save. - Go to Permissions → Bucket policy and paste this, with your bucket name:
1
2
3
4
5
6
7
8
9
10
11
12
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadForWebsite",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::cicd-demo-site-<your-initials>/*"
}
]
}Copy the Bucket website endpoint from Properties. You'll open it in Step 4.
Production tip: for a real site, keep the bucket private and serve it through Amazon CloudFront with Origin Access Control. That gives you HTTPS and keeps the bucket locked down.
Step 3: Create the pipeline
Open the CodePipeline console and choose Create pipeline. If you're offered templates, pick Build custom pipeline.
Pipeline settings
- Name:
cicd-demo-pipeline. - Pipeline type: V2.
- Service role: New service role. CodePipeline creates the IAM role and an artifact bucket for you.
Source stage
- Source provider: GitHub (via GitHub App).
- Choose Connect to GitHub, name the connection and install the AWS Connector app on your GitHub account. Give it access to the
cicd-demorepo only. - Pick the repo and the
mainbranch. - Trigger: start the pipeline on push to
main.
Build stage
- Build provider: AWS CodeBuild, then choose Create project.
- Project name:
cicd-demo-build. - Environment: Managed image, operating system Amazon Linux, runtime Standard, latest image. It includes Node.js 20.
- Service role: New service role.
- Buildspec: Use a buildspec file. CodeBuild finds
buildspec.ymlat the repo root. - Choose Continue to CodePipeline, then Next.
Deploy stage
- Deploy provider: Amazon S3.
- Bucket: the bucket from Step 2.
- Tick Extract file before deploy. Without it, S3 receives one zip file instead of your website files.
Review everything and choose Create pipeline. It runs right away on your latest commit.
Step 4: Test it with a push
Wait for all three stages to turn green, then open your bucket website endpoint. You should see Deployed by CodePipeline - v1.
Now prove the pipeline does the work for you:
- In
src/index.html, changev1tov2. - Commit and push to
main. - Watch the pipeline start on its own. Refresh the site after the deploy stage finishes and you'll see v2.
Then prove the safety net works:
- Delete the
<title>line fromsrc/index.htmland push. - The Build stage fails at
npm test, and the Deploy stage never runs. - Your live site still shows v2. Put the title back, push, and the pipeline goes green again.
That last test is the whole point of CI/CD: broken code never reaches your users.
Troubleshooting, costs and cleanup
| Problem | Likely cause | Fix |
|---|---|---|
| Build fails with YAML_FILE_ERROR | buildspec.yml is missing, misnamed or not at the repo root | Check the file name and location, then push again |
| Site shows 403 Forbidden | Bucket policy missing, or Block Public Access still on | Repeat Step 2, items 3 and 6 |
| Site shows 404 Not Found | Extract file before deploy was not ticked | Edit the Deploy action, tick it and release the change |
| Pipeline doesn't start on push | The trigger or connection points to the wrong branch or repo | Edit the Source stage and confirm repo, main branch and push trigger |
| Source stage fails | The GitHub connection is still Pending | In Settings → Connections, finish the connection setup |
Costs. For a small demo like this, cost is usually a few cents or nothing. CodePipeline and CodeBuild both have free tiers, and S3 charges very little for a tiny site. Check the CodePipeline pricing and CodeBuild pricing pages for current rates in your Region.
Cleanup. When you're done, delete these so nothing keeps running:
- The pipeline
cicd-demo-pipeline - The CodeBuild project
cicd-demo-build - Both S3 buckets: your website bucket and the
codepipeline-<region>-...artifact bucket (empty them first) - The GitHub connection, under Developer Tools → Settings → Connections
- The IAM service roles the wizard created
What's next
You now have a real CI/CD pipeline: push code, and AWS tests and ships it for you, and stops a broken build before it reaches anyone. The same pattern of source, build and deploy works for Lambda functions, containers and full web apps.
Some ways to take it further:
- Put CloudFront in front of the bucket for HTTPS and caching
- Add a Manual approval action before deploy, so a person signs off on production releases
- Add Amazon SNS notifications so you get an email when a pipeline fails
- Define the whole pipeline in AWS CloudFormation or AWS CDK so you can rebuild it with one command
Help me choose my next build! Vote in my poll and tell me what you want to see next: https://builder.aws.com/poll/3K9PT3lEBpIisR7BGNK95WFzI89_po
If this helped you, leave a comment with what you're deploying. I read every one.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article