Build a CI/CD Pipeline with CodePipeline

Automate code deployments from GitHub to EC2 using AWS CodePipeline.

Introduction

30 Second Summary

Every time a developer changes code, someone has to get that change onto the live server. Doing this manually is slow, error-prone, and one of the first things companies automate.

In this project, you will build a fully automated CI/CD pipeline using AWS CodePipeline that watches your GitHub repository for code changes, builds your application with CodeBuild, and deploys it to a live EC2 instance using CodeDeploy.

What You'll Build

A 3-stage V2 CodePipeline (Source, Build, Deploy) that automatically builds and deploys a Node.js web application to a live EC2 instance every time you push code to GitHub.

By the end of this project, you'll have:

  • A V2 CodePipeline with Source, Build, and Deploy stages connected to your GitHub repository.
  • A buildspec.yml and appspec.yml that define your build and deployment processes from scratch.
  • A live Node.js web application on EC2 that updates automatically on every code push.
  • Secret Mission: Add a manual approval gate between Build and Deploy to simulate a production release workflow.

Are there any prerequisites?

You need an existing AWS account, a GitHub account, and Git installed on your Mac. Basic familiarity with EC2 (like knowing what an instance is) helps but is not required.

Create Your Application and GitHub Repository

Every CI/CD pipeline needs source code to build and deploy. Before you can automate anything, you need an application in a version-controlled repository that your pipeline can watch for changes.

In this step, you'll create a simple Node.js web app along with the configuration files that tell AWS how to build and deploy it. Then you'll push everything to GitHub so CodePipeline can access it.

In this step, get ready to:
  • Create a Node.js application with build and deployment configuration files.
  • Push your code to a GitHub repository.
  • Verify the repository structure matches what CodeDeploy expects.
Create the Node.js application files locally

Your pipeline will build and deploy a Node.js web server. You'll also create two key configuration files: buildspec.yml tells CodeBuild how to build your app, and appspec.yml tells CodeDeploy how to deploy it.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir cicd-pipeline-app
cd cicd-pipeline-app
  • Create the scripts subdirectory for your deployment scripts by running:
mkdir scripts
  • Confirm your folder structure by running:
ls

You should see the scripts directory listed in the output.

Don't see the scripts folder?

Make sure you ran mkdir scripts from inside the cicd-pipeline-app folder. Run pwd to check your current location.

help me figure out why my scripts folder wasn't created

Now you'll create the application files. Open the cicd-pipeline-app folder in your code editor.

  • Create a new file called index.js in the root of your cicd-pipeline-app folder and add this code:
const express = require('express');
const app = express();
const port = 80;

app.get('/', (req, res) => {
  res.send(`
    <!DOCTYPE html>
    <html>
    <head>
      <title>My CI/CD App</title>
      <style>
        body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f0f4f8; }
        h1 { color: #232f3e; }
        p { color: #545b64; font-size: 1.2em; }
        .pipeline-badge { background: #00a86b; color: white; padding: 8px 16px; border-radius: 4px; display: inline-block; margin-top: 20px; }
      </style>
    </head>
    <body>
      <h1>Hello from CodePipeline!</h1>
      <p>This app was deployed automatically via AWS CodePipeline.</p>
      <div class="pipeline-badge">Pipeline Version 1.0</div>
    </body>
    </html>
  `);
});

app.listen(port, () => {
  console.log(`App running on port ${port}`);
});

What does this code do?

This is a minimal Express web server that listens on port 80 (the default HTTP port).

When someone visits your server's IP address, it responds with an HTML page showing a styled message confirming the app was deployed via CodePipeline. You'll see this page in your browser after the pipeline finishes deploying.

  • Save index.js.
  • Create a new file called package.json in the root of your cicd-pipeline-app folder and add this code:
{
  "name": "cicd-pipeline-app",
  "version": "1.0.0",
  "description": "A simple Node.js app deployed via AWS CodePipeline",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "^4.21.0"
  }
}

What does this file do?

This file declares your project's metadata and dependencies. CodeBuild reads this file and runs npm install to download Express (version ^4.21.0) before deploying your app.

The start script tells Node.js how to launch your app. Your deployment scripts will call npm start to run it on the server.

  • Save package.json.
  • Create a new file called buildspec.yml in the root of your cicd-pipeline-app folder and add this code:
version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 18
    commands:
      - echo Installing dependencies...
      - npm install
  build:
    commands:
      - echo Build completed on `date`
artifacts:
  files:
    - '**/*'

What does buildspec.yml do?

This file is your build instructions for CodeBuild. It tells CodeBuild exactly what to do when it receives your source code.

  • The install phase sets up the Node.js 18 runtime and runs npm install to download dependencies.
  • The build phase logs a completion timestamp.
  • The artifacts section tells CodeBuild to package ALL files ('**/*') and pass them to the next pipeline stage.
  • Save buildspec.yml.

Getting YAML syntax errors later?

YAML is sensitive to indentation. Make sure you use spaces (not tabs) and that each level is indented by exactly 2 spaces. Copy the code block above exactly as shown.

help me fix YAML indentation in my buildspec.yml

  • Create a new file called appspec.yml in the root of your cicd-pipeline-app folder and add this code:
version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/app
hooks:
  ApplicationStop:
    - location: scripts/stop_server.sh
      timeout: 300
      runas: root
  AfterInstall:
    - location: scripts/install_dependencies.sh
      timeout: 300
      runas: root
  ApplicationStart:
    - location: scripts/start_server.sh
      timeout: 300
      runas: root

What does appspec.yml do?

This file is your deployment instructions for CodeDeploy. It controls how your code lands on the EC2 instance.

  • The files section copies everything from the build output into /home/ec2-user/app on the server.
  • The hooks section defines lifecycle events: stop the old app, install dependencies, then start the new app.
  • Each hook runs as root because the app listens on port 80, which requires elevated permissions.

Why version 0.0?

The version: 0.0 value is required by AWS for EC2/On-Premises deployments. It's not a version you choose. It must be exactly 0.0 for CodeDeploy to process the file.

  • Save appspec.yml.

Now you'll create the three shell scripts that appspec.yml references. These scripts run at different stages of each deployment.

  • Create a new file called install_dependencies.sh inside the scripts folder and add this code:
#!/bin/bash
cd /home/ec2-user/app
npm install
  • Save scripts/install_dependencies.sh.
  • Create a new file called start_server.sh inside the scripts folder and add this code:
#!/bin/bash
cd /home/ec2-user/app
npm start > /dev/null 2>&1 &
  • Save scripts/start_server.sh.
  • Create a new file called stop_server.sh inside the scripts folder and add this code:
#!/bin/bash
pkill -f "node index.js" || true

What do these scripts do?

  • install_dependencies.sh runs npm install on the server after CodeDeploy copies your files.
  • start_server.sh launches your app in the background. The > /dev/null 2>&1 & part redirects output and detaches the process so it keeps running.
  • stop_server.sh kills the running app before a new deployment. The || true prevents an error if the process is not running (like on the first ever deployment).
  • Save scripts/stop_server.sh.
  • Confirm all your files exist by running this in your terminal:
ls
ls scripts/

You should see index.js, package.json, buildspec.yml, appspec.yml, and scripts in the first output. The second output should show install_dependencies.sh, start_server.sh, and stop_server.sh.

Missing a file?

Double check you saved each file in the correct location. The scripts must be inside the scripts/ subdirectory, not in the project root. All other files must be in the project root.

help me fix my project file structure

✔️ Awesome, I've got everything!

Great. Double check you've saved all your files before moving on.

ⓧ I'd like to double check the full code

Here are all 7 files for comparison:

const express = require('express');
const app = express();
const port = 80;

app.get('/', (req, res) => {
  res.send(`
    <!DOCTYPE html>
    <html>
    <head>
      <title>My CI/CD App</title>
      <style>
        body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f0f4f8; }
        h1 { color: #232f3e; }
        p { color: #545b64; font-size: 1.2em; }
        .pipeline-badge { background: #00a86b; color: white; padding: 8px 16px; border-radius: 4px; display: inline-block; margin-top: 20px; }
      </style>
    </head>
    <body>
      <h1>Hello from CodePipeline!</h1>
      <p>This app was deployed automatically via AWS CodePipeline.</p>
      <div class="pipeline-badge">Pipeline Version 1.0</div>
    </body>
    </html>
  `);
});

app.listen(port, () => {
  console.log(`App running on port ${port}`);
});
{
  "name": "cicd-pipeline-app",
  "version": "1.0.0",
  "description": "A simple Node.js app deployed via AWS CodePipeline",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "^4.21.0"
  }
}
version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 18
    commands:
      - echo Installing dependencies...
      - npm install
  build:
    commands:
      - echo Build completed on `date`
artifacts:
  files:
    - '**/*'
version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/app
hooks:
  ApplicationStop:
    - location: scripts/stop_server.sh
      timeout: 300
      runas: root
  AfterInstall:
    - location: scripts/install_dependencies.sh
      timeout: 300
      runas: root
  ApplicationStart:
    - location: scripts/start_server.sh
      timeout: 300
      runas: root
#!/bin/bash
cd /home/ec2-user/app
npm install
#!/bin/bash
cd /home/ec2-user/app
npm start > /dev/null 2>&1 &
#!/bin/bash
pkill -f "node index.js" || true
Create a GitHub repository and push your code

CodePipeline watches a GitHub repository for changes. Every time you push new code, the pipeline triggers automatically. You need to create a remote repository and push your local files to it.

  • Open your browser and go to github.com.
  • Click the + icon in the top-right corner.
  • Click New repository.
  • Enter cicd-pipeline-app as the Repository name.
  • Leave the repository set to Public.
  • Do NOT check any boxes for README, .gitignore, or license. The repository must be completely empty.
  • Click Create repository.

GitHub shows a page with setup instructions. You'll use the commands for pushing an existing repository.

  • Back in your terminal (make sure you're still inside cicd-pipeline-app), initialize your local Git repository and push to GitHub by running these commands:
git init
git add .
git commit -m "Initial commit - CI/CD pipeline app"
git branch -M main
git remote add origin https://github.com/[[GITHUB_USERNAME="your-github-username"]]/cicd-pipeline-app.git
git push -u origin main

What do these commands do?

  • git init turns your folder into a Git repository.
  • git add . stages all 7 files for commit.
  • git commit saves a snapshot of your project locally.
  • git branch -M main ensures your default branch is called main.
  • git remote add origin connects your local repo to the GitHub repo you just created.
  • git push uploads all your files to GitHub.

Git may ask you to authenticate with GitHub. If prompted, follow the login flow in your browser or enter your credentials.

Push failed or authentication error?

If you see a permission denied or authentication error, GitHub likely needs a personal access token instead of a password. Go to GitHub Settings > Developer settings > Personal access tokens and generate one with repo scope. Use this token as your password when prompted.

help me authenticate with GitHub from my terminal

Verify your repository structure

CodeDeploy requires appspec.yml to be at the root of your repository. If it's nested in a subdirectory, deployments will fail. Let's confirm everything is in the right place.

  • In your browser, refresh your GitHub repository page.
  • Confirm that appspec.yml appears at the root level of the repository (not inside any subfolder).
  • Confirm that buildspec.yml also appears at the root level.
  • Click on the scripts folder and confirm it contains install_dependencies.sh, start_server.sh, and stop_server.sh.

Why does file placement matter?

CodeBuild looks for buildspec.yml at the repository root by default. CodeDeploy looks for appspec.yml at the root of the deployment artifact. If either file is missing or in the wrong place, the pipeline stage will fail with a cryptic error.

Files not showing up on GitHub?

If your repository is empty or missing files, run git status locally. If files show as untracked, you may have missed the git add . step. Run git add ., then git commit -m "Add missing files", then git push.

help me fix my GitHub repository so all files are visible

Your application code is ready and live on GitHub. Next up, you'll launch an EC2 instance that will serve as the deployment target for your pipeline.

Launch an EC2 Instance with CodeDeploy Agent

Your code is on GitHub and ready to flow through a pipeline. The next piece you need is the deployment target: a live server where your application will actually run.

In this step, you'll launch an EC2 instance configured to receive automated deployments from CodeDeploy. This involves creating an IAM role for permissions, launching the instance with the CodeDeploy agent pre-installed, and verifying everything is connected.

In this step, get ready to:
  • Create an IAM role that lets EC2 communicate with CodeDeploy.
  • Launch an Amazon Linux 2023 EC2 instance with the CodeDeploy agent installed.
  • Verify the agent is running and ready for deployments.
Create an IAM role for EC2

Your EC2 instance needs permission to download deployment artifacts from S3 and communicate with CodeDeploy. An IAM role grants these permissions without storing credentials on the instance itself.

  • Open the AWS Management Console and navigate to the IAM service by searching IAM in the top search bar.
  • In the left navigation pane, click Roles.
  • Click Create role.
  • Under Trusted entity type, select AWS service.
  • Under Use case, select EC2.
  • Click Next.

Now you need to attach two managed policies that give the instance the right permissions.

  • In the search bar on the Add permissions page, type AmazonEC2RoleforAWSCodeDeploy and select the checkbox next to it.
  • Clear the search bar, then type AmazonSSMManagedInstanceCore and select the checkbox next to it.
  • Click Next.
  • For Role name, enter EC2CodeDeployRole.
  • Click Create role.

You should see a success banner confirming the role was created.

What do these policies do?

AmazonEC2RoleforAWSCodeDeploy allows the instance to download deployment artifacts (your code) from S3 buckets that CodePipeline creates automatically.

AmazonSSMManagedInstanceCore allows AWS Systems Manager to communicate with the instance. This is useful for troubleshooting and management without needing SSH access.

What is an instance profile?

When you create a role for EC2 via the console, AWS automatically creates an instance profile with the same name. The instance profile is the container that attaches the role to your EC2 instance. You will select this profile when launching your instance.

Can't find the policy?

Make sure you are searching for the exact policy name. The search is case-sensitive. If you still see no results, check that you are in the correct AWS account.

Help me find the AmazonEC2RoleforAWSCodeDeploy policy in my IAM console.

Launch an Amazon Linux 2023 EC2 instance

Now you'll launch the server that your pipeline will deploy to. You'll configure it with a user data script that installs the CodeDeploy agent automatically when the instance boots up.

  • Navigate to the EC2 service by searching EC2 in the top search bar.
  • Click Launch instance from the EC2 dashboard.
  • Under Name and tags, enter cicd-demo for the Name field.
  • Under Application and OS Images, keep the default Amazon Linux 2023 AMI selected. It should be marked Free tier eligible.
  • Under Instance type, select t2.micro (free tier eligible).
  • Under Key pair (login), click Create new key pair.
  • Enter cicd-keypair as the key pair name and click Create key pair.

Where did the .pem file go?

Your browser automatically downloads a cicd-keypair.pem file. This is your private key for SSH access. Keep it safe and remember where it downloaded (usually your Downloads folder).

Next, configure the security group to allow web traffic and SSH access.

  • Under Network settings, click Edit.
  • Keep the default SSH rule (port 22) that is already listed.
  • Click Add security group rule.
  • Set Type to HTTP. This automatically sets the port to 80.
  • Set Source type to Anywhere.

Now scroll down to attach your IAM role and add the user data script.

  • Expand the Advanced details section at the bottom of the page.
  • Under IAM instance profile, select EC2CodeDeployRole from the dropdown.
  • Scroll down to the User data text box and paste the following script:
#!/bin/bash
# Update the instance and install CodeDeploy agent dependencies
yum update -y
yum install -y ruby wget

# Download and install the CodeDeploy agent from the regional S3 bucket
cd /home/ec2-user
wget https://aws-codedeploy-[[AWS_REGION="your AWS region e.g. us-east-1"]].s3.[[AWS_REGION="your AWS region e.g. us-east-1"]].amazonaws.com/latest/install
chmod +x ./install
./install auto

What does this user data script do?

User data runs automatically when your instance first boots. This script installs the CodeDeploy agent, which is the software that listens for deployment instructions from AWS CodeDeploy.

The agent downloads from a region-specific S3 bucket. Replace the region with whatever region you see in the top-right corner of your AWS console (e.g. us-east-1, us-west-2, eu-west-1).

  • Click Launch instance in the Summary panel on the right.

You should see a success banner confirming your instance is launching. Click View all instances to go to the Instances page.

  • Wait for the Instance state column to show Running. This takes about 1-2 minutes.
  • Click on your cicd-demo instance to open its details.
  • Note the Public IPv4 address displayed on the details page. You will need this later.

Don't see EC2CodeDeployRole in the dropdown?

The instance profile can take a few seconds to propagate after role creation. Try refreshing the dropdown by clicking the refresh icon next to the IAM instance profile field.

If it still doesn't appear, confirm that you selected EC2 as the use case when creating the role. Only roles created for EC2 generate an instance profile.

Help me troubleshoot why my IAM instance profile isn't showing up in the EC2 launch wizard.

Verify the CodeDeploy agent is running

The user data script runs in the background after boot. You need to confirm the CodeDeploy agent installed correctly and is actively running on your instance.

  • Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows) and searching for Terminal.
  • First, set the correct permissions on your key file by running:
chmod 400 [[PEMPATH="path to your .pem file e.g. ~/Downloads/cicd-keypair.pem"]]
  • SSH into your instance by running:
ssh -i [[PEMPATH="path to your .pem file e.g. ~/Downloads/cicd-keypair.pem"]] ec2-user@[[EC2_PUBLIC_IP="your EC2 instance public IPv4 address"]]

Seeing a fingerprint prompt?

The first time you SSH into a new instance, your terminal asks you to confirm the host fingerprint. Type yes and press Enter to continue connecting.

Once connected, you'll see a prompt that includes ec2-user@. Now check the CodeDeploy agent status.

  • Check the CodeDeploy agent status by running:
sudo systemctl status codedeploy-agent

You should see output that includes active (running) in green text. This confirms the agent is installed and ready to receive deployments.

Agent not running or command not found?

The user data script can take 2-3 minutes to finish after the instance enters the Running state. Wait a moment and try the command again.

If the agent is still not running after waiting, check the user data script log for errors by running:

cat /var/log/cloud-init-output.log | tail -30

The most common issue is using the wrong region in the S3 URL. The region in the user data script must match the region where your EC2 instance is running.

Help me troubleshoot why the CodeDeploy agent isn't running on my EC2 instance.

  • Type exit to disconnect from the instance and return to your local terminal.

Your EC2 instance is live, secured with the right permissions, and running the CodeDeploy agent. Next up, you'll create the CodeDeploy application and deployment group that tell AWS how to deploy your code to this instance.

Set Up CodeDeploy Application and Deployment Group

Your EC2 instance is running with the CodeDeploy agent active and ready to receive deployments.

Now you need to tell CodeDeploy where to deploy your code. You do this by creating a CodeDeploy application (a named container for your deployment settings) and a deployment group (which targets your specific EC2 instance using its Name tag).

In this step, get ready to:
  • Create an IAM service role that grants CodeDeploy permission to deploy to your EC2 instance.
  • Create a CodeDeploy application with the EC2/On-Premises compute platform.
  • Create a deployment group that targets your EC2 instance by tag.
Create an IAM service role for CodeDeploy

CodeDeploy needs its own IAM role to interact with your EC2 instance during deployments. This is separate from the EC2 instance role you created earlier.

  • In the AWS Console, search for IAM in the top search bar and select IAM.
  • In the left navigation pane, click Roles.
  • Click Create role.
  • Under Trusted entity type, select AWS service.
  • In the Use case dropdown, select CodeDeploy.
  • Select CodeDeploy (the default option, not CodeDeploy - ECS or CodeDeploy - Lambda).
  • Click Next.
  • The AWSCodeDeployRole policy is pre-selected. Click Next.
  • In Role name, enter CodeDeployServiceRole.
  • Click Create role.

What does this role do?

The AWSCodeDeployRole policy gives CodeDeploy permission to read your EC2 instance tags, manage deployments, and publish status information. The trust policy for codedeploy.amazonaws.com allows the CodeDeploy service to assume this role on your behalf.

You should see a green "Role CodeDeployServiceRole created" banner at the top of the page.

Don't see the AWSCodeDeployRole policy?

Make sure you selected CodeDeploy as the use case in the previous step. If you selected a different service, the correct policy will not appear automatically.

help me create the CodeDeploy service role

Create a CodeDeploy application

A CodeDeploy application is a named container that groups your deployment settings together. It tells CodeDeploy which compute platform you are targeting.

  • In the AWS Console, search for CodeDeploy in the top search bar and select CodeDeploy.
  • In the left navigation pane, expand Deploy and click Applications.
  • Click Create application.
  • In Application name, enter cicd-pipeline-app.
  • In Compute platform, select EC2/On-premises.
  • Click Create application.

You should see a success banner and your application page with the Deployment groups tab visible.

Why EC2/On-Premises?

CodeDeploy supports three compute platforms: EC2/On-Premises, Lambda, and ECS. Since you are deploying to an EC2 instance, you choose EC2/On-Premises. This platform uses your appspec.yml lifecycle hooks to stop, install, and start your application on the instance.

Seeing an error when creating the application?

Double check that the application name cicd-pipeline-app does not already exist in your account. Application names must be unique within a region.

help me troubleshoot my CodeDeploy application creation

Create a deployment group

A deployment group defines which EC2 instances receive your deployments. You target instances using their tags, so CodeDeploy knows exactly where to send your code.

  • On your cicd-pipeline-app application page, click the Deployment groups tab.
  • Click Create deployment group.
  • In Deployment group name, enter cicd-deployment-group.
  • In Service role, select the CodeDeployServiceRole role you just created.
  • In Deployment type, select In-place.
  • In Environment configuration, select Amazon EC2 instances.
  • In the Key field, select Name.
  • In the Value field, enter cicd-demo.
  • You should see "1 unique matched instance" appear below the tag fields.
  • In Deployment settings, select CodeDeployDefault.OneAtATime.
  • In Load balancer, uncheck Enable load balancing.
  • Click Create deployment group.

You should see a success banner and your deployment group listed on the application page.

How does tag matching work?

CodeDeploy uses EC2 tags to find your instance. You set the tag key to Name and value to cicd-demo. This must exactly match the Name tag on your EC2 instance. Tag matching is case-sensitive, so cicd-demo and CICD-Demo would be treated as different values.

The "1 unique matched instance" confirmation tells you CodeDeploy found your instance successfully.

Why CodeDeployDefault.OneAtATime?

This deployment configuration deploys to one instance at a time. Since you only have one instance, it makes no practical difference here. In production with multiple instances, OneAtATime minimizes downtime by keeping other instances running while one is being updated.

Not seeing 1 matched instance?

Make sure the tag key is Name (capital N) and the value is cicd-demo (all lowercase with a hyphen). This must match the Name tag you assigned when launching your EC2 instance in the previous step.

To verify, go to the EC2 console, select your instance, and check its Tags tab. The Name tag should show cicd-demo.

help me fix my CodeDeploy deployment group tag matching

Your CodeDeploy application and deployment group are configured. Next up, you will create the CodePipeline that connects your GitHub repository, CodeBuild, and CodeDeploy into a single automated workflow.

Create the CodePipeline

Your CodeDeploy application and deployment group are ready to receive deployments. Now it is time to connect all the pieces into a single automated workflow.

AWS CodePipeline is the orchestration layer that ties your Source, Build, and Deploy stages together. When you push code to GitHub, CodePipeline detects the change, hands the code to CodeBuild, and then passes the build output to CodeDeploy for deployment to your EC2 instance.

In this step, get ready to:
  • Create a V2 CodePipeline with a new service role.
  • Connect your GitHub repository as the Source stage.
  • Configure CodeBuild and CodeDeploy as the Build and Deploy stages.
Create a new V2 pipeline

The pipeline creation wizard walks you through each stage one at a time. You will start by naming the pipeline and letting AWS create a service role for it.

  • In the AWS Management Console search bar, type CodePipeline and select the CodePipeline service.
  • Click Create pipeline.
  • On the creation option screen, select Build custom pipeline.
  • Click Next.

You are now on the Pipeline settings page.

  • In the Pipeline name field, enter cicd-pipeline.
  • Under Service role, keep New service role selected. This lets CodePipeline create a role with the permissions it needs to orchestrate your stages.
  • Leave all other settings as their defaults.
  • Click Next.

Why does CodePipeline need its own service role?

CodePipeline needs permissions to pull code from GitHub, start CodeBuild builds, trigger CodeDeploy deployments, and read/write artifacts in S3. The auto-created service role bundles all of these permissions together so the pipeline can operate across multiple AWS services.

Configure the Source stage

The Source stage tells CodePipeline where your code lives. You will connect your GitHub account and point the pipeline at your repository.

  • Under Source provider, select GitHub (via GitHub App).
  • Click Connect to GitHub. A connection wizard opens.

What is a GitHub connection?

AWS uses a CodeStar Connection to securely communicate with GitHub. You only need to set this up once. After connecting, CodePipeline can detect pushes to your repository and pull your source code automatically.

  • In the Connection name field, enter github-connection.
  • Click Connect to GitHub.
  • A GitHub authorization window appears. Click Authorize to grant AWS access to your GitHub account.
  • If prompted to install the AWS Connector for GitHub app, click Install a new app, select your GitHub account, and click Install.
  • Back in the connection wizard, click Connect.

You should see a green Ready to connect badge next to your connection name.

  • Under Repository name, select your cicd-pipeline-app repository.
  • Under Default branch, select main.
  • Leave the trigger settings as their defaults.
  • Click Next.

Don't see your repository in the dropdown?

Make sure you pushed your code to GitHub in Step 1. The repository must exist and contain at least one commit for it to appear in the list.

If you installed the GitHub App to only specific repositories, make sure cicd-pipeline-app is included. You can update this in your GitHub Settings under Applications.

Help me connect my GitHub repository to CodePipeline

Configure the Build and Deploy stages

The Build stage uses CodeBuild to install dependencies and package your application. The Deploy stage uses CodeDeploy to push the built artifacts to your EC2 instance.

  • Under Build provider, select AWS CodeBuild.
  • Click Create project. A new window opens for CodeBuild project configuration.

In the CodeBuild project creation window:

  • In the Project name field, enter cicd-build-project.
  • Under Environment image, select Managed image.
  • Under Operating system, select Amazon Linux.
  • Under Runtime, select Standard.
  • Under Image, select the latest available image.
  • Under Compute, select EC2.
  • Under Fleet, select On-demand and choose general1.small as the instance type.
  • Under Buildspec, keep Use a buildspec file selected. CodeBuild will automatically look for buildspec.yml in your repository root.
  • Click Continue to CodePipeline to return to the pipeline wizard.

Why general1.small?

The general1.small compute type is eligible for the CodeBuild free tier, which gives you 100 free build minutes per month. This free tier does not expire after 12 months.

  • Back in the pipeline wizard, click Next to move past the Build stage.
  • If you see a Test stage page, click Skip test stage and confirm by clicking Skip.

You are now on the Deploy stage page.

  • Under Deploy provider, select AWS CodeDeploy.
  • Under Application name, select cicd-pipeline-app.
  • Under Deployment group, select cicd-deployment-group.
  • Click Next.

You are now on the Review page. Take a moment to confirm your Source, Build, and Deploy stages look correct.

  • Click Create pipeline.

What happens when you create the pipeline?

CodePipeline automatically triggers its first execution immediately after creation. It pulls your latest code from the main branch, runs it through CodeBuild, and deploys it to your EC2 instance via CodeDeploy.

CodePipeline also creates an S3 bucket automatically to store artifacts (your built code) as they pass between stages.

You should see the pipeline dashboard with your three stages (Source, Build, Deploy) displayed. The pipeline begins executing immediately.

Pipeline creation failed?

If you see a permissions error, make sure you allowed CodePipeline to create a new service role (not selecting an existing one). The auto-created role has all the required permissions.

If the Deploy stage shows your CodeDeploy application or deployment group as unavailable, double-check that you created them in the same AWS region where you are creating the pipeline.

Help me troubleshoot my CodePipeline creation

Your pipeline is live and running its first execution. In the next step, you will watch it complete and then push a code change to see the full automated cycle in action.

Trigger the Pipeline and Watch It Deploy

Your pipeline is built and all three stages are connected. When you saved the pipeline in the previous step, CodePipeline immediately triggered its first execution using the latest code from your GitHub repository.

In this step, you will monitor that first execution, confirm your app is live, then make a real code change and watch the pipeline deploy it automatically. This is the moment where everything comes together.

In this step, get ready to:
  • Monitor the initial pipeline execution through all three stages.
  • Push a code change and watch the pipeline trigger automatically.
  • Verify your updated application is live on EC2.
Watch the initial pipeline execution

When a V2 pipeline is first created, it immediately runs with the latest source code from your repository. Your pipeline should already be in progress or completed.

  • In the CodePipeline console, click on your pipeline name cicd-pipeline to open the pipeline detail view.
  • You will see three stages displayed vertically: Source, Build, and Deploy.
  • Each stage shows a status indicator. A green checkmark means the stage succeeded. A blue spinner means it is still in progress.
  • Wait for all three stages to show green checkmarks. The full execution typically takes 3-5 minutes.

What is happening in each stage?

The Source stage pulls your latest code from GitHub. The Build stage uses CodeBuild to run your buildspec.yml (installing Node.js dependencies). The Deploy stage uses CodeDeploy to push your built application to your EC2 instance using the lifecycle hooks in appspec.yml.

  • If the Build stage fails, click Details on the Build action to open the CodeBuild logs.
  • Review the log output to identify the specific error.

Pipeline stage failed?

  • Build stage failure: Check that your buildspec.yml is at the root of your repository and uses version: 0.2. Also confirm the indentation is correct YAML.
  • Deploy stage failure: Confirm the CodeDeploy agent is running on your EC2 instance. SSH in and run systemctl status codedeploy-agent. Also verify that the Name tag on your instance is exactly cicd-demo.
  • Source stage failure: Your GitHub connection may have lost authorization. Go to Settings > Connections in the CodePipeline console and verify the connection status is Available.

Help me debug my pipeline failure

Once all three stages show green checkmarks, your application is deployed and running on your EC2 instance.

  • Open a new browser tab.
  • Navigate to the EC2 console and find your cicd-demo instance.
  • Copy the Public IPv4 address from the instance details.
  • Paste the IP address into your browser's address bar and press Enter. Make sure you use http:// (not https://).

You should see your web page with the heading "Hello from CodePipeline!" and a green badge that says "Pipeline Version 1.0".

Page not loading in the browser?

  • Make sure you are using http:// not https://. Your instance does not have an SSL certificate.
  • Verify your security group allows inbound traffic on port 80. Check the Security tab on your EC2 instance details.
  • If the Deploy stage succeeded but the page is blank, the Node.js process may not have started. SSH into your instance and check with ps aux | grep node.

My pipeline succeeded but the app is not loading in my browser

Make a code change and push to GitHub

The real power of a CI/CD pipeline is that it deploys automatically when you push code. You are about to prove this by making a visible change to your application and watching the pipeline pick it up without any manual intervention.

  • Open index.js in your local cicd-pipeline-app folder.
  • Find this line:
<div class="pipeline-badge">Pipeline Version 1.0</div>
  • Change Pipeline Version 1.0 to Pipeline Version 2.0, so the line looks like this:
<div class="pipeline-badge">Pipeline Version 2.0</div>
  • Save index.js.
  • Commit and push your change by running these commands in your terminal:
git add .
git commit -m "Update version badge to 2.0"
git push origin main

What happens after the push?

Your V2 pipeline is configured to watch the main branch for changes. The moment GitHub receives your push, it notifies CodePipeline through the GitHub App connection. CodePipeline then starts a new execution automatically.

  • Go back to the CodePipeline console and click on cicd-pipeline.
  • You should see a new execution in progress within a minute of your push. The Source stage will turn blue first as it pulls the latest code.
  • Wait for all three stages to complete with green checkmarks again.

Pipeline not triggering after your push?

  • Confirm you pushed to the main branch (not a different branch). Run git branch to check which branch you are on.
  • Wait up to 60 seconds. The trigger is near-instant but can occasionally take a moment to propagate.
  • If it still does not trigger, click Release change at the top of the pipeline page to manually start an execution while you debug.

My pipeline is not triggering when I push to GitHub

Verify the updated deployment

Once all stages are green again, your updated application is live on the same EC2 instance.

  • Go back to your browser tab with your EC2 public IP.
  • Refresh the page.
  • You should see the green badge now reads "Pipeline Version 2.0" instead of "Pipeline Version 1.0".

That confirms it. You pushed code to GitHub and your pipeline built, tested, and deployed it to a live server without you clicking a single deploy button.

Why this matters for interviews

You just demonstrated continuous deployment. Every push to main automatically reaches production. This is the exact workflow AWS DevOps interviewers expect you to describe and build.

Still seeing Version 1.0?

  • Hard refresh the page with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows) to bypass your browser cache.
  • Confirm the second pipeline execution completed all three stages with green checkmarks.
  • Verify you saved index.js before committing. Run git diff to check if there are unsaved changes that were not included in the commit.

My app still shows the old version after a successful pipeline run

Secret mission

Add a Manual Approval Gate

In production environments, teams don't deploy code straight to live servers without someone reviewing the change first. Your challenge is to add a manual approval stage between Build and Deploy, so the pipeline pauses and waits for a human to approve before the code goes live.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Some of the AWS resources in this project may incur costs depending on your account's free tier eligibility.

Cost warning

Your EC2 instance (t2.micro) is free tier eligible for 12 months from when you created your AWS account. If you are past that 12-month window, the instance costs approximately $0.01/hr while running.

CodePipeline V2 and CodeBuild both have Always Free tiers (100 free action execution minutes and 100 free build minutes per month respectively). These do not expire. CodeDeploy is free for EC2 deployments. If you are past EC2 free tier, choose Pause or Delete to avoid charges.

Resources you used:

  • EC2 instance (t2.micro) with CodeDeploy agent.
  • CodePipeline V2 (3-stage pipeline).
  • CodeBuild project.
  • CodeDeploy application and deployment group.
  • IAM roles (EC2 instance profile role and CodeDeploy service role).
  • S3 artifact bucket (created automatically by CodePipeline).
  • GitHub repository.

Keep everything running

No action needed. Choose this if you are still actively building or want to practice pushing more changes through your pipeline.

  • Your pipeline will continue to trigger on every push to the main branch.
  • Monitor your EC2 usage in the AWS Billing dashboard if you are past the 12-month free tier.
  • CodePipeline and CodeBuild usage stays within the Always Free tier for light usage.

Pause - I'll come back to this later

Stop your EC2 instance to avoid compute charges while keeping all your configuration intact. EBS storage charges (~$0.08/GB/month) continue while the instance is stopped.

  • Open the EC2 console and select your instance.
  • Click Instance state, then Stop instance.
  • Your pipeline will still trigger on pushes, but the Deploy stage will fail since the instance is stopped. This is expected.
  • When you are ready to resume, start the instance again from the EC2 console and re-run the pipeline.

Delete - I don't want to use this again

Remove all project resources to ensure $0 ongoing charges. Follow these steps in order.

  • Open the CodePipeline console, select your pipeline, and click Delete pipeline.
  • Open the CodeBuild console, select your build project, and click Delete build project.
  • Open the CodeDeploy console, select your application, and click Delete application. This removes the deployment group as well.
  • Open the EC2 console, select your instance, click Instance state, then Terminate instance.
  • Open the IAM console. Delete both the EC2 instance profile role and the CodeDeploy service role you created.
  • Open the S3 console. Find the artifact bucket created by CodePipeline (its name starts with codepipeline-). Empty the bucket first, then delete it.
  • Optionally delete your GitHub repository if you no longer need it.

Nice Work!

Nice Work!

You just built a real CI/CD pipeline from scratch using the same AWS services that power production deployments at companies worldwide.

You've learned how to:

  • Built a 3-stage V2 CodePipeline (Source, Build, Deploy) that triggers automatically on every GitHub push.
  • Wrote a buildspec.yml and appspec.yml from scratch to define your build process in CodeBuild and deployment lifecycle hooks in CodeDeploy.
  • Deployed a live Node.js web application to an EC2 instance via a fully automated pipeline.
  • Secret Mission: Added a manual approval gate between Build and Deploy to simulate a production release workflow.

Ready to quiz yourself?