CI/CD Pipeline to a Linux Server
Build a GitHub Actions pipeline that auto-deploys to an AWS EC2 server.
Introduction
30 Second Summary
Every time a developer fixes a bug or adds a feature, that code needs to get from their laptop to a live server where users can see it. Doing this manually is slow, error-prone, and tedious.
In this project, you will build an automated deployment pipeline that takes your code from GitHub and deploys it to a live Linux server every time you push a change.
What You'll Build
A complete CI/CD pipeline that automatically deploys a website to an AWS EC2 instance running Nginx, triggered by GitHub Actions on every push to the main branch.
By the end of this project, you'll have:
- A live Nginx web server running on an EC2 instance you launched and configured yourself.
- A working GitHub Actions workflow that automatically deploys code changes to your server on every push.
- Hands-on experience with essential Linux commands for navigation, package management, service control, and file permissions.
- Secret Mission: Add a health check step to the pipeline that verifies the deployment succeeded before marking it as complete.
Are there any prerequisites?
You will need an AWS account (free tier eligible), a GitHub account, Git installed on your machine, and VS Code. All infrastructure costs are covered by the AWS Free Tier.
Launch Your EC2 Instance
Every CI/CD pipeline needs a destination. Your code needs somewhere to land when it gets deployed.
In this step, you'll create that destination by launching a Linux server on AWS EC2. You'll also create the SSH key that lets you (and later, your pipeline) connect securely.
In this step, get ready to:
- Create an SSH key pair for secure server access.
- Launch a Linux server on AWS EC2.
- Note the server's public IP address for later use.
Create your SSH key pair
A key pair is like a lock-and-key system for your server. AWS keeps the lock (public key) on your instance, and you keep the key (private key) on your computer. Without the key, nobody can get in.
- Open your browser and navigate to the EC2 console:
- https://console.aws.amazon.com/ec2/
- Sign in with your AWS account if prompted.
- In the left navigation pane, under Network & Security, click Key Pairs.
- Click Create key pair.
- For Name, enter deploy-key.
- For Key pair type, select ED25519.
- For Private key file format, choose .pem.
- Click Create key pair.
The file deploy-key.pem downloads automatically to your Downloads folder. This is your private key. You will not be able to download it again.
Why ED25519?
AWS recommends ED25519 for Linux instances. It is more secure than RSA and produces shorter keys, making SSH connections slightly faster.
Now you need to lock down the permissions on your key file so only you can read it. SSH refuses to use a key file that other users can access.
- Open Terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), typing Terminal, and pressing Enter.
- Set the key file permissions by running this command:
chmod 400 ~/Downloads/deploy-key.pem
What does this command do?
The chmod 400 command sets the file so that only you (the owner) can read it. No one else on your computer can read, write, or execute it. SSH requires this level of restriction before it will use the key.
- Verify the permissions are set correctly by running:
ls -la ~/Downloads/deploy-key.pem
You should see -r-------- at the beginning of the output. This confirms only you have read access.
Seeing 'No such file or directory'?
Your browser may have saved the file with a different name or in a different location. Check your Downloads folder for a file ending in .pem and update the path in the command accordingly.
If your browser added a number to the filename (like deploy-key (1).pem), rename it to deploy-key.pem before running the command.
help me fix the chmod command for my key file
Launch the EC2 instance
With your key pair ready, you can now launch the actual server. This is a virtual machine running Amazon Linux in AWS's data centers.
- In the EC2 console, click Instances in the left navigation pane.
- Click Launch instances.
- Under Name and tags, enter web-server in the Name field.
- Under Application and OS Images (Amazon Machine Image), click Quick Start.
- Select Amazon Linux.
- From the Amazon Machine Image (AMI) dropdown, pick an AMI marked Free Tier eligible.
- Under Instance type, select an instance type marked Free Tier eligible.
- Under Key pair (login), select deploy-key from the dropdown.
- Under Network settings, keep the default VPC and subnet.
- Under Firewall (security groups), check the box for Allow SSH traffic from and leave the source set to Anywhere.
- Also check the box for Allow HTTP traffic from the internet.
- Click Launch instance.
What is a security group?
A security group acts as a virtual firewall for your instance. It controls which traffic is allowed in and out. You enabled two inbound rules: SSH (port 22) so you can connect from your terminal, and HTTP (port 80) so visitors can see your website.
Cost awareness
EC2 Free Tier covers 750 hours per month for eligible instance types during your first 12 months. This is more than enough to run one instance 24/7 for an entire month. Make sure to terminate the instance when you finish this project to avoid charges after your free tier expires.
Note your public IP address
Your instance is now launching. You need its public IP address to connect via SSH and to view your deployed website later.
- Click Instances in the left navigation pane.
- Wait for the Instance state column to show Running. This may take a minute.
- Click on your web-server instance to select it.
- In the Details tab below, find the Public IPv4 address field.
- Copy this IP address and save it somewhere you can access easily (a sticky note, a text file, etc.). You will use it throughout the project.
Keep this IP handy
You will use this public IP address in every remaining step of this project. Paste it into a note or keep the EC2 console tab open.
Instance stuck in 'Pending'?
Give it up to 2 minutes. If the state still does not change to Running, refresh the page by clicking the refresh icon near the top of the instances list.
If the instance moves to Terminated instead, you may have hit a service quota. Try launching in a different region.
help me troubleshoot my EC2 instance that won't start
Your server is live and accessible. Next up, you'll connect to it via SSH and set up Nginx to serve your website.
Connect and Configure Your Linux Server
Your EC2 instance is running, but right now it is just an empty server sitting in the cloud. To turn it into a web server, you need to connect to it and install the software that serves web pages.
In this step, you will use SSH to connect to your Linux server, explore some basic commands, and install Nginx to serve your first web page.
In this step, get ready to:
- SSH into your EC2 instance from your local terminal.
- Run basic Linux commands to explore the server.
- Install and start Nginx so your server can serve web pages.
SSH into the instance
SSH is a secure protocol that gives you a command-line session on a remote server. You will use the key pair file you downloaded in the previous step to authenticate.
- Return to your Terminal (the same one you used for chmod in the previous step).
- Connect to your EC2 instance by running this command:
ssh -i ~/Downloads/deploy-key.pem ec2-user@[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]
What does this command do?
- ssh starts a secure shell connection to a remote machine.
- -i ~/Downloads/deploy-key.pem tells SSH to authenticate using your private key file instead of a password.
- ec2-user is the default username on Amazon Linux instances.
- The IP address at the end is the public address of your EC2 instance from Step 1.
- SSH will display a message saying it cannot verify the host's authenticity and ask: Are you sure you want to continue connecting (yes/no/[fingerprint])? This is normal for a first connection.
- Type yes and press Enter.
You should now see a prompt like [ec2-user@ip-... ~]$ which means you are on the Linux server.
Connection timed out or refused?
- Double check that your EC2 instance's state is Running in the EC2 console.
- Make sure you are using the correct public IPv4 address (not the private one).
- Confirm your security group allows SSH (port 22) from your IP.
- If you see a permission denied error, verify the path to deploy-key.pem and that you ran chmod 400 on it in the previous step.
Why can't I connect to my EC2 instance via SSH?
Explore Linux basics
Before installing anything, take a moment to look around. These commands are the foundation of every Linux interaction you will have.
- Run these commands one at a time to explore your server:
pwd
ls -la
whoami
cat /etc/os-release
What do these commands do?
- pwd prints your current directory (you should see /home/ec2-user).
- ls -la lists all files, including hidden ones, with details like permissions and ownership.
- whoami confirms you are logged in as ec2-user.
- cat /etc/os-release shows information about the operating system. You should see Amazon Linux 2023.
- Try navigating to a different directory and back by running:
cd /var
cd ~
The cd /var command moves you into the /var directory (where logs and variable data live). The cd ~ shortcut takes you back to your home directory.
Install and start Nginx
Nginx is a high-performance web server that will serve your website files to anyone who visits your public IP address.
- Install Nginx by running this command:
sudo dnf install -y nginx
What does this command do?
- sudo runs the command with administrator privileges. Installing software requires elevated access.
- dnf is the package manager on Amazon Linux 2023. It downloads and installs software from Amazon's repositories.
- -y automatically answers yes to all prompts so the install runs without pausing.
You should see output showing packages being downloaded and installed, ending with a Complete! message.
Seeing a 'No match for argument: nginx' error?
- Make sure you are running the command on your EC2 instance (inside the SSH session), not on your local machine.
- Confirm your instance has internet access. The default VPC and security group settings should allow outbound traffic.
Why is dnf unable to find the nginx package on my Amazon Linux instance?
- Start Nginx and enable it to run on boot by running these commands:
sudo systemctl start nginx
sudo systemctl enable nginx
What do these commands do?
- sudo systemctl start nginx starts the Nginx process right now.
- sudo systemctl enable nginx tells the system to start Nginx automatically every time the server reboots.
- Verify that Nginx is running by checking its status:
sudo systemctl status nginx
Look for the line that says Active: active (running) in the output. This confirms Nginx is up and serving traffic.
- Open a browser on your local machine and navigate to http:// followed by your EC2 public IPv4 address.
You should see the default Nginx welcome page. This confirms your server is live and reachable from the internet.
Not seeing the Nginx welcome page?
- Make sure you typed http:// (not https://). Your server does not have an SSL certificate yet.
- Double check your security group allows inbound HTTP traffic on port 80.
- Run sudo systemctl status nginx again to confirm the service is still active.
Why can't I see the Nginx welcome page when I visit my EC2 public IP?
Your Linux server is live and serving web pages. Next up, you will create a GitHub repository to store your website code so the CI/CD pipeline has something to deploy.
Create Your GitHub Repository
Your Linux server is running and Nginx is serving web pages. Now you need a place to store your website code that a CI/CD pipeline can access.
In this step, you'll create a GitHub repository, add your website's HTML, and push it to the main branch. This repository becomes the source of truth that your pipeline will deploy from.
In this step, get ready to:
- Create a public GitHub repository for your website.
- Add your website HTML code locally.
- Push your code to the main branch on GitHub.
Create a new repository on GitHub
A GitHub repository is where your project files live online. Your CI/CD pipeline will pull code from this repository whenever you push a change.
- Go to github.com and sign in to your account.
- In the upper-right corner, click the + icon, then click New repository.
- In the Repository name box, type my-devops-site.
- Select Public for the repository visibility.
- Select Add a README file.
- Click Create repository.
Why initialize with a README?
Adding a README creates an initial commit on the main branch. Without it, the repository would be empty and you would need extra steps to set up a default branch before cloning.
Clone the repository and add your website files
Now you'll download the repository to your computer and create the HTML file that your pipeline will eventually deploy to the server.
- Open a new Terminal window on your Mac. Make sure you are in a local terminal, not the SSH session connected to your EC2 instance.
How to tell which terminal is which
Your SSH session shows ec2-user@ in the prompt. Your local terminal shows your Mac username. If you see ec2-user, type exit to disconnect, or open a new terminal tab.
- Move to your Desktop by running this command:
cd ~/Desktop
- Clone your repository by running this command:
git clone https://github.com/[[GITHUB_USERNAME="your GitHub username"]]/my-devops-site.git
- Move into the cloned repository folder by running:
cd my-devops-site
- Confirm you are inside the repository by running:
ls
You should see README.md in the output. This confirms the clone worked.
Clone failed or repository not found?
Double-check that you replaced the username in the URL with your actual GitHub username. The repository URL is case-sensitive.
If you see a credentials prompt, make sure you are signed in to GitHub on your Mac (via git credential helper or a personal access token).
Help me debug my git clone error.
Now you'll create the website file that your pipeline will deploy. Open the my-devops-site folder in VS Code.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to Desktop, select the my-devops-site folder, and click Open.
- In the Explorer sidebar on the left, hover over the file list area and click the New File icon.
- Type index.html and press Enter.
- Paste the following HTML into index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My DevOps Site</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
background-color: #1a1a2e;
color: #eaeaea;
}
h1 {
color: #00d4aa;
}
.pipeline-status {
background: #16213e;
padding: 1rem;
border-radius: 8px;
border-left: 4px solid #00d4aa;
}
</style>
</head>
<body>
<h1>Deployed via CI/CD Pipeline</h1>
<div class="pipeline-status">
<p>This site was deployed automatically using GitHub Actions.</p>
<p>Every push to the main branch triggers a new deployment.</p>
</div>
<h2>What this proves:</h2>
<ul>
<li>I can provision cloud infrastructure (AWS EC2)</li>
<li>I can manage a Linux server (Amazon Linux 2023 + Nginx)</li>
<li>I can build CI/CD pipelines (GitHub Actions)</li>
<li>I can manage secrets securely (SSH keys in GitHub Secrets)</li>
</ul>
</body>
</html>
What does this HTML do?
This page is a styled portfolio piece that describes what your pipeline proves you can do. It uses inline CSS for a dark theme with an accent color.
The .pipeline-status div highlights the key message: this site deploys itself automatically. The bulleted list summarizes the DevOps skills this project demonstrates.
- Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Double check that you have saved index.html in VS Code. You should see README.md and index.html in the Explorer sidebar.
ⓧ I'd like to double check the full code
Here is the complete index.html file. Make sure your file matches this exactly:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My DevOps Site</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
background-color: #1a1a2e;
color: #eaeaea;
}
h1 {
color: #00d4aa;
}
.pipeline-status {
background: #16213e;
padding: 1rem;
border-radius: 8px;
border-left: 4px solid #00d4aa;
}
</style>
</head>
<body>
<h1>Deployed via CI/CD Pipeline</h1>
<div class="pipeline-status">
<p>This site was deployed automatically using GitHub Actions.</p>
<p>Every push to the main branch triggers a new deployment.</p>
</div>
<h2>What this proves:</h2>
<ul>
<li>I can provision cloud infrastructure (AWS EC2)</li>
<li>I can manage a Linux server (Amazon Linux 2023 + Nginx)</li>
<li>I can build CI/CD pipelines (GitHub Actions)</li>
<li>I can manage secrets securely (SSH keys in GitHub Secrets)</li>
</ul>
</body>
</html>
Push your code to GitHub
Your file exists locally but GitHub does not know about it yet. You need to commit the file and push it to the remote repository.
- Switch back to your local Terminal. Make sure you are inside the my-devops-site folder.
- Stage, commit, and push your code by running these commands:
git add .
git commit -m "Add website"
git push origin main
What do these commands do?
- git add . stages all new and changed files for the next commit.
- git commit -m "Add website" saves a snapshot of your staged files with a descriptive message.
- git push origin main uploads your commit to the main branch on GitHub.
- Go back to your repository page on GitHub in your browser.
- Refresh the page. You should see index.html listed alongside README.md in the file list.
Don't see index.html on GitHub?
Make sure all three commands completed without errors. Check that git push did not ask for credentials or show a rejection message.
If you see "failed to push", confirm you are pushing to a repository you own and that the remote URL matches your GitHub username.
Help me fix my git push error.
Your website code is on GitHub and ready to be deployed automatically. Next up, you'll build the CI/CD pipeline that connects this repository to your live server.
Build the CI/CD Pipeline
Your GitHub repository has the website code, and your EC2 instance is serving web pages. The missing piece is the connection between them.
Right now, if you want to update your live site, you would have to manually copy files to the server every time. A CI/CD pipeline automates this entirely. In this step, you will build a GitHub Actions workflow that deploys your code to the server every time you push to the main branch.
In this step, get ready to:
- Store your server credentials as GitHub Secrets.
- Create a GitHub Actions workflow file that deploys on push.
- Trigger your first automated pipeline run.
Add secrets to your GitHub repository
Your workflow needs to connect to your EC2 instance via SSH. You cannot hardcode credentials into a public file. GitHub Secrets lets you store sensitive values that your workflow can access at runtime without exposing them in your code.
First, you need the contents of your private key file. Switch back to your local Terminal (not the SSH session).
- Display the contents of your key file by running this command:
cat ~/Downloads/deploy-key.pem
- Select and copy the entire output, including the -----BEGIN OPENSSH PRIVATE KEY----- and -----END OPENSSH PRIVATE KEY----- lines.
Now head to GitHub to add your secrets.
- Open your my-devops-site repository on GitHub.
- Click Settings in the top navigation bar of the repository.
- In the left sidebar under the Security section, click Secrets and variables, then click Actions.
- Click the Secrets tab.
- Click New repository secret.
- In the Name field, type SSH_PRIVATE_KEY.
- In the Secret field, paste the entire contents of your key file that you just copied.
- Click Add secret.
Repeat the process to add two more secrets.
- Click New repository secret again.
- In the Name field, type SSH_HOST.
- In the Secret field, paste your EC2 instance's public IPv4 address.
- Click Add secret.
- Click New repository secret one more time.
- In the Name field, type SSH_USERNAME.
- In the Secret field, type ec2-user.
- Click Add secret.
You should now see three secrets listed on the page: SSH_PRIVATE_KEY, SSH_HOST, and SSH_USERNAME.
Why use secrets instead of hardcoding values?
Your repository is public. If you pasted your SSH key or IP address directly into the workflow file, anyone could see them and access your server.
GitHub Secrets encrypts these values and only exposes them to the workflow at runtime. They never appear in logs or in your code.
Don't see the Settings tab?
The Settings tab only appears if you are the repository owner. Make sure you are signed into the GitHub account that created the repository.
Help me find the Settings tab on my GitHub repository.
Create the workflow file
GitHub Actions looks for workflow files inside the .github/workflows/ directory of your repository. You will create this directory and add a deploy.yml file that defines your deployment pipeline.
- In your local terminal, make sure you are inside the my-devops-site folder. Create the workflow directory by running this command:
mkdir -p .github/workflows
- Confirm the directory was created by running:
ls -la .github/workflows
You should see an empty directory listing (just . and .. entries).
- In VS Code, open the .github/workflows/ folder in the Explorer sidebar.
- Create a new file called deploy.yml inside the workflows folder.
- Paste the following workflow configuration into deploy.yml:
# Workflow name displayed in the Actions tab
name: Deploy to EC2
# Trigger: run on every push to the main branch
on:
push:
branches:
- main
jobs:
deploy:
# Use a GitHub-hosted Ubuntu runner
runs-on: ubuntu-latest
steps:
# Pull the latest code from your repository
- name: Checkout code
uses: actions/checkout@v6
# Copy index.html to the server via SCP
- name: Deploy to server
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "index.html"
target: "/usr/share/nginx/html/"
overwrite: true
What does this workflow do?
The on: push: branches: [main] block tells GitHub to run this workflow every time code is pushed to the main branch.
The actions/checkout@v6 step pulls your latest repository code onto the runner. Then appleboy/scp-action@v1 uses SCP (secure copy) to transfer index.html to your EC2 instance's Nginx web root at /usr/share/nginx/html/.
The ${{ secrets.SSH_HOST }} syntax reads the encrypted secrets you just stored. GitHub injects these values at runtime without ever exposing them in your code or logs.
- Save deploy.yml.
YAML indentation errors?
YAML is sensitive to spacing. Make sure you use spaces (not tabs) for indentation. Each nested level should be indented by exactly 2 spaces.
If VS Code shows a squiggly underline, check that your with: block is indented one level deeper than uses:.
Help me fix YAML indentation in my deploy.yml file.
✔️ Awesome, I've got everything!
Great. Make sure you have saved deploy.yml before moving on.
ⓧ I'd like to double check the full code
Here is the complete deploy.yml file for comparison:
name: Deploy to EC2
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Deploy to server
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "index.html"
target: "/usr/share/nginx/html/"
overwrite: true
Push the workflow and trigger the pipeline
Pushing this file to the main branch will trigger the workflow for the very first time. This is your CI/CD pipeline in action.
- Stage, commit, and push your workflow file by running these commands:
git add .
git commit -m "Add CI/CD pipeline"
git push origin main
- Open your my-devops-site repository on GitHub.
- Click the Actions tab at the top of the repository.
You should see a workflow run listed. It will either be in progress (with a yellow spinner) or completed with a green checkmark.
Why does the first run likely fail?
The pipeline will attempt to SCP your index.html to /usr/share/nginx/html/ on your server. This directory is owned by root by default, so the ec2-user account does not have write permission yet.
You will fix this in the next step by changing the directory ownership. For now, a failed run is expected and confirms that your pipeline triggered correctly.
Don't see the Actions tab?
GitHub Actions is enabled by default on public repositories. If you do not see the Actions tab, go to Settings then Actions then General, and make sure Allow all actions and reusable workflows is selected.
If you see the tab but no workflow runs appear, confirm that your deploy.yml file is at .github/workflows/deploy.yml (not inside a different folder path). The directory name must be exactly .github with a leading dot.
Help me troubleshoot why my GitHub Actions workflow isn't showing up.
Your CI/CD pipeline is live. Next up, you will fix the server permissions and verify the full end-to-end deployment.
Deploy and Verify
Your pipeline is built and the first workflow run has been triggered. If you check the Actions tab, you will likely see a failed run. The Nginx web root directory (/usr/share/nginx/html/) is owned by root, so your pipeline cannot write files there.
In this step, you will fix the server permissions, confirm your workflow targets the correct directory, then push a code change and watch it deploy automatically to your live site.
In this step, get ready to:
- Fix server file permissions so the pipeline can write to the Nginx web root.
- Confirm the workflow deploys to the correct directory.
- Push a code change and watch it deploy automatically.
Prepare the server to receive deployments
Your GitHub Actions workflow authenticates as ec2-user when it copies files to your server. By default, /usr/share/nginx/html/ is owned by root. Without changing ownership, the SCP action fails with a permission denied error.
- SSH into your EC2 instance by running this command:
ssh -i ~/Downloads/deploy-key.pem ec2-user@[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]
- Type yes if prompted about the host fingerprint.
- Change ownership of the Nginx web root so ec2-user can write files there by running:
sudo chown -R ec2-user:ec2-user /usr/share/nginx/html
What does this command do?
sudo chown -R ec2-user:ec2-user /usr/share/nginx/html changes the owner of the /usr/share/nginx/html directory (and everything inside it) from root to ec2-user.
Now the GitHub Actions SCP step (which authenticates as ec2-user) has permission to write files into this folder on every deploy.
- Verify the ownership changed by running:
ls -la /usr/share/nginx/html
- You should see ec2-user ec2-user listed as the owner and group for the files in that directory.
Still seeing root as the owner?
Make sure you included sudo at the start of the chown command. Only root can change file ownership, so sudo is required.
Also confirm you used the -R flag, which makes the change recursive (applies to all files and subdirectories inside the folder).
Help me fix file ownership on my EC2 instance.
Confirm the workflow target
Before triggering a new deploy, confirm that your workflow file already targets the correct directory on the server.
- Switch to your local Terminal (not the SSH session) and open .github/workflows/deploy.yml in VS Code.
- Verify the target value under the Deploy to server step is set to /usr/share/nginx/html/.
- Verify that overwrite: true is present so existing files are replaced on each deploy.
Why no changes needed?
When your source is a single file (index.html), the SCP action places it directly into the target directory. No strip_components setting is required.
Your workflow already has the correct target path. The only thing missing was the server-side permission fix you just applied.
Make a change and watch it deploy
Now for the moment of truth. You will edit your site, push to GitHub, and the pipeline will deploy the change to your live server automatically.
- In VS Code, open index.html from your my-devops-site folder.
- Find the <h1> tag and change the text to something visibly different, for example:
<h1>Deployed via CI/CD Pipeline</h1>
What should I change?
Pick any heading text you like. The goal is to make a visible change that proves your pipeline deployed fresh code. You could change it to Hello from my pipeline or add your name.
- Save index.html.
- In your local Terminal, commit and push your change by running these commands:
git add .
git commit -m "Update heading to test pipeline"
git push origin main
- Open your repository on GitHub.
- Click the Actions tab.
- Click the latest workflow run (it will have your commit message as the title).
- Wait for the run to complete. A green checkmark next to the run means it succeeded.
What should I see?
On the Actions tab, you will see a list of workflow runs. Your latest push triggers a new run. When it finishes successfully, a green checkmark appears next to the run name.
Click into the run to see details. Under Jobs, the deploy job should show a green checkmark next to each step (Checkout code, Deploy to server).
- Once the workflow shows a green checkmark, open your browser and navigate to:
http://[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]
- You should see your updated heading text displayed on the page. Your code was deployed automatically from GitHub to your live server.
Pipeline still failing with permission denied?
Confirm you ran the chown command in your SSH session. Check with ls -la /usr/share/nginx/html and verify the directory is owned by ec2-user, not root.
If the Actions tab shows an authentication error instead, double-check that your SSH_PRIVATE_KEY secret contains the full contents of your deploy-key.pem file (including the -----BEGIN and -----END lines).
Help me debug my GitHub Actions deployment pipeline.
Page still showing the old Nginx welcome page?
Try a hard refresh in your browser (Cmd+Shift+R on macOS, Ctrl+Shift+R on Windows) to clear the cache.
If that does not help, SSH into your server and run cat /usr/share/nginx/html/index.html to check whether your file actually arrived. If it still shows the default Nginx HTML, the deploy step did not succeed. Check the workflow logs for errors.
Help me figure out why my deployed site is not showing my updated content.
Secret mission
Add a Health Check
Your pipeline deploys code automatically. But how do you know the deployment actually worked? In production, teams add health checks that verify the site responds after every deploy. If the check fails, the pipeline fails too, alerting you immediately.
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. This project uses an EC2 instance that consumes Free Tier hours while running.
Cost warning
Your EC2 instance uses Free Tier hours (750 hours/month for accounts created before July 15, 2025). If your Free Tier has expired or you exceed the monthly limit, compute charges will apply.
If you are not actively using the pipeline, stop or terminate the instance to avoid unexpected charges.
Resources you used:
- 1 EC2 instance (web-server) with attached EBS volume.
- 1 key pair (deploy-key).
- 1 security group allowing SSH and HTTP.
- Local key file (deploy-key.pem) and cloned repository folder (my-devops-site).
- GitHub repository (my-devops-site) with stored secrets.
Keep everything running
No action needed. Choose this if you are still actively building or want to keep your pipeline running.
- Your EC2 instance continues consuming Free Tier hours (750 hours/month).
- Your CI/CD pipeline remains fully functional. Every push to main deploys automatically.
- The key file deploy-key.pem remains in your Downloads folder. Keep it safe and do not share it.
Pause - I'll come back to this later
Stop the EC2 instance to halt compute charges while keeping everything intact for later.
- Return to the EC2 console at https://console.aws.amazon.com/ec2/.
- Click Instances in the left pane.
- Select your web-server instance.
- Click Instance state, then Stop instance.
- Confirm by clicking Stop.
What happens when you stop?
No compute charges apply while the instance is stopped. Minimal EBS storage charges still apply but are covered by Free Tier.
Your public IP address will change when you restart. You will need to update the SSH_HOST secret in GitHub with the new IP before your pipeline can deploy again.
Delete - I don't want to use this again
Remove all project resources permanently. Your GitHub repository and pipeline config remain unless you delete them separately.
Terminate the EC2 instance
- Return to the EC2 console at https://console.aws.amazon.com/ec2/.
- Click Instances in the left pane.
- Select your web-server instance.
- Click Instance state, then Terminate instance.
- Confirm termination. The attached EBS volume is deleted automatically with the instance.
Delete the key pair
- In the EC2 console left pane, click Key Pairs under Network & Security.
- Select deploy-key.
- Click Actions, then Delete.
- Confirm the deletion.
Delete the security group
- In the EC2 console left pane, click Security Groups under Network & Security.
- Select the security group that was created with your instance (it allows SSH and HTTP inbound).
- Click Actions, then Delete security groups.
- Confirm the deletion. This only works after the instance is terminated.
Delete local files
- Delete the key file and the project folder by running these commands in Terminal:
rm ~/Downloads/deploy-key.pem
rm -rf ~/Desktop/my-devops-site
Delete the GitHub repository
- Go to your my-devops-site repository on GitHub.
- Click Settings in the top navigation bar.
- Scroll to the bottom of the page to the Danger Zone section.
- Click Delete this repository and follow the confirmation prompts.
That's a Wrap!
That's a Wrap!
Nice work! You just built a complete automated deployment pipeline from scratch.
You went from an empty cloud account to a live website that updates itself every time you push code. That is the core loop of modern DevOps.
You've learned how to:
- Launch and configure a Linux EC2 instance with Nginx serving a custom web page.
- Build a GitHub Actions CI/CD pipeline that automatically deploys code on every push to the main branch.
- Use secrets management to securely store SSH credentials and connect your pipeline to a live server.
- Secret Mission: Added an automated health check step to verify deployments succeeded before marking the workflow as complete.
Ready to quiz yourself?