---
title: "How to CI/CD AWS With Github using Jenkins"
description: "In this post, I will show you how to setup a CI/CD pipeline using Jenkins and Github to deploy a simple PHP application to Devlopment and Production environments on AWS. With this setup, you can deploy your application to AWS with a single click."
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/blog/post/how-to-ci-cd-aws-with-github-using-jenkins
---

# How to CI/CD AWS With Github using Jenkins

## Introduction

In previous posts, I have shown you how to setup Jenkins on AWS EC2 instance. You can check the post [here](/blog/post/install-jenkins-on-aws-ec2-instance).

In this post, I will show you how to setup a CI/CD pipeline using Jenkins and Github to deploy a simple PHP application to Devlopment and Production environments on AWS. With this setup, you can deploy your application to AWS with a single click.

## Prerequisites

- AWS CLI installed and configured
- IAM user with the following permissions:
  - AmazonVPCFullAccess
  - AmazonEC2FullAccess

## Setup AWS Infrastructure for Devlopment Environment

### Create VPC

#### Step 1: Create VPC

To create a VPC, run the following command:

```shell
# Create a VPC
AWS_VPC=$(aws ec2 create-vpc \
  --cidr-block 10.0.0.0/16 \
  --query 'Vpc.VpcId' \
  --output text)

# Add a name tag to the VPC
aws ec2 create-tags \
  --resources $AWS_VPC \
  --tags Key=Name,Value=environments-vpc
```

#### Step 2: Modify your custom VPC and enable DNS hostname support, and DNS support

To modify your custom VPC and enable DNS hostname support, and DNS support, run the following command:

```shell
# Modify your custom VPC and enable DNS hostname support, and DNS support
# Enable DNS hostnames
aws ec2 modify-vpc-attribute \
  --vpc-id $AWS_VPC \
  --enable-dns-hostnames "{\"Value\":true}"

# Enable DNS support
aws ec2 modify-vpc-attribute \
  --vpc-id $AWS_VPC \
  --enable-dns-support "{\"Value\":true}"
```

#### Step 3: Create a Public Subnet

To create a public subnet, run the following command:

```shell
# Create a public subnet
AWS_PUBLIC_SUBNET=$(aws ec2 create-subnet \
  --vpc-id $AWS_VPC \
  --cidr-block 10.0.1.0/24 \
  --query 'Subnet.SubnetId' \
  --output text)

# Add a name tag to the public subnet
aws ec2 create-tags \
  --resources $AWS_PUBLIC_SUBNET \
  --tags Key=Name,Value=environments-public-subnet
```

#### Step 4: Enable Auto-assign Public IP on the subnet

To enable auto-assign public IP on the subnet, run the following command:

```shell
# Enable auto-assign public IP on the subnet
aws ec2 modify-subnet-attribute \
  --subnet-id $AWS_PUBLIC_SUBNET \
  --map-public-ip-on-launch
```

#### Step 5: Create an Internet Gateway

To create an internet gateway, run the following command:

```shell
# Create an Internet Gateway
AWS_INTERNET_GATEWAY=$(aws ec2 create-internet-gateway \
  --query 'InternetGateway.InternetGatewayId' \
  --output text)

# Add a name tag to the Internet Gateway
aws ec2 create-tags \
  --resources $AWS_INTERNET_GATEWAY \
  --tags Key=Name,Value=environments-internet-gateway
```

#### Step 6: Attach the Internet Gateway to your VPC

To attach the internet gateway to your VPC, run the following command:

```shell
# Attach the Internet Gateway to the VPC
aws ec2 attach-internet-gateway \
  --internet-gateway-id $AWS_INTERNET_GATEWAY \
  --vpc-id $AWS_VPC
```

#### Step 7: Create a Route Table

To create a route table, run the following command:

```shell
# Create a route table
AWS_ROUTE_TABLE=$(aws ec2 create-route-table \
  --vpc-id $AWS_VPC \
  --query 'RouteTable.RouteTableId' \
  --output text)

# Add a name tag to the route table
aws ec2 create-tags \
  --resources $AWS_ROUTE_TABLE \
  --tags Key=Name,Value=environments-route-table
```

#### Step 8: Create a custom route table association

To create a route in the route table, run the following command:

```shell
# Create a custom route table association
aws ec2 associate-route-table \
  --subnet-id $AWS_PUBLIC_SUBNET \
  --route-table-id $AWS_ROUTE_TABLE
```

#### Step 9: Associate the subnet with route table, making it a public subnet

To associate the subnet with route table, making it a public subnet, run the following command:

```shell
# Associate the subnet with route table, making it a public subnet
aws ec2 create-route \
  --route-table-id $AWS_ROUTE_TABLE \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id $AWS_INTERNET_GATEWAY
```

#### Step 10: Create a Security Group

To create a security group, run the following command:

```shell
# Create a security group
AWS_SECURITY_GROUP=$(aws ec2 create-security-group \
  --group-name aws-security-group \
  --description "AWS Security Group" \
  --vpc-id $AWS_VPC \
  --query 'GroupId' \
  --output text)

# Add a name tag to the security group
aws ec2 create-tags \
  --resources $AWS_SECURITY_GROUP \
  --tags Key=Name,Value=environments-security-group
```

#### Step 11: Add inbound rules to the security group

To add inbound rules to the security group, run the following command:

```shell
# Add inbound rules to the security group

# Add SSH rule
aws ec2 authorize-security-group-ingress \
  --group-id $AWS_SECURITY_GROUP \
  --protocol tcp \
  --port 22 \
  --cidr 0.0.0.0/0 \
  --output text

# Add HTTP rule
aws ec2 authorize-security-group-ingress \
  --group-id $AWS_SECURITY_GROUP \
  --protocol tcp \
  --port 80 \
  --cidr 0.0.0.0/0 \
  --output text

# Add HTTPS rule
aws ec2 authorize-security-group-ingress \
  --group-id $AWS_SECURITY_GROUP \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0 \
  --output text
```

### Create a Two EC2 Instances

#### Step 1: Get the latest AMI ID for Amazon Linux 2

To get the latest AMI ID for Amazon Linux 2, run the following command:

```shell
# Get the latest AMI ID for Amazon Linux 2
AWS_AMI=$(aws ec2 describe-images \
  --owners 'amazon' \
  --filters 'Name=name,Values=amzn2-ami-hvm-2.0.*' \
  'Name=state,Values=available' \
  --query 'sort_by(Images, &CreationDate)[-1].[ImageId]' \
  --output 'text')
```

#### Step 2: Create a Key Pair

To create a key pair, run the following command:

```shell
# Create a key pair
aws ec2 create-key-pair \
  --key-name aws-key-pair \
  --query 'KeyMaterial' \
  --output text > aws-key-pair.pem

# Change the permission of the key pair
chmod 400 aws-key-pair.pem
```

#### Step 3: Create a user data script

To create a user data script, run the following command:

```shell
# Create a userdata script
cat < userdata.sh
#!/bin/bash

# update the system
sudo yum update -y

# install httpd
sudo yum install -y httpd

# start httpd
sudo systemctl start httpd

# enable httpd
sudo systemctl enable httpd

# at first, we will enable amazon-linux-extras so that we can specify the PHP version that we want to install.
sudo amazon-linux-extras enable php7.4 -y

# install php
sudo yum install -y php php-{pear,cgi,common,curl,mbstring,gd,mysqlnd,gettext,bcmath,json,xml,fpm,intl,zip,imap}

# install MariaDB
sudo yum install -y mariadb-server

# start MariaDB
sudo systemctl start mariadb

# enable MariaDB
sudo systemctl enable mariadb

# we will now secure MariaDB.
sudo mysql_secure_installation <setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
  echo "Connection failed: " . $e->getMessage();
}
```

#### Step 4: Create the controller file

To create the controller file, run the following command:

```php:app/controllers/index.php
<?php

// Include the connection.php file
require_once 'app/config/connection.php';

// Get the data from the database
$query = "SELECT * FROM `users`";
$stmt = $conn->prepare($query);
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

// close the connection
$conn = null;
```

#### Step 5: Create the view file

To create the view file, run the following command:

```php:app/views/index.php
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title><?php echo ENV; ?> | My Site</title>
    <link rel="stylesheet" href="./assets/css/normalize.css" />
    <link rel="stylesheet" href="./assets/css/main.css" />
  </head>
  <body>
    <header>
      <h1>My Site</h1>

      <nav>
        <ul>
          <li><a href="./index.php">Home</a></li>
        </ul>
      </nav>
    </header>

    <main>
      <h2>Home</h2>
      <p>Welcome to my site!</p>
      <p>
        <?php echo "Environment: " . ENV; ?>
      </p>
    </main>

    <footer>
      <p>My Site &copy; 2022</p>
    </footer>
  </body>
</html>
```

#### Step 6: Create the `index.php` file

To create the `index.php` file, run the following command:

```php:index.php
<?php

// Include the controller file
require_once 'app/controllers/index.php';

// Include the view file
require_once 'app/views/index.php';
```

#### Step 7: Create the `normalize.css` file

To create the `normalize.css` file, run the following command:

<details>

<summary>Click to see the code of the `normalize.css` file</summary>

```css:assets/css/normalize.css
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */

/* Document
   ========================================================================== */

/**
 * 1. Correct the line height in all browsers.
 * 2. Prevent adjustments of font size after orientation changes in iOS.
 */

html {
  line-height: 1.15; /* 1 */
  -webkit-text-size-adjust: 100%; /* 2 */
}

/* Sections
   ========================================================================== */

/**
 * Remove the margin in all browsers.
 */

body {
  margin: 0;
}

/**
 * Render the `main` element consistently in IE.
 */

main {
  display: block;
}

/**
 * Correct the font size and margin on `h1` elements within `section` and
 * `article` contexts in Chrome, Firefox, and Safari.
 */

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

/* Grouping content
   ========================================================================== */

/**
 * 1. Add the correct box sizing in Firefox.
 * 2. Show the overflow in Edge and IE.
 */

hr {
  box-sizing: content-box; /* 1 */
  height: 0; /* 1 */
  overflow: visible; /* 2 */
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd `em` font sizing in all browsers.
 */

pre {
  font-family: monospace, monospace; /* 1 */
  font-size: 1em; /* 2 */
}

/* Text-level semantics
   ========================================================================== */

/**
 * Remove the gray background on active links in IE 10.
 */

a {
  background-color: transparent;
}

/**
 * 1. Remove the bottom border in Chrome 57-
 * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
 */

abbr[title] {
  border-bottom: none; /* 1 */
  text-decoration: underline; /* 2 */
  text-decoration: underline dotted; /* 2 */
}

/**
 * Add the correct font weight in Chrome, Edge, and Safari.
 */

b,
strong {
  font-weight: bolder;
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd `em` font sizing in all browsers.
 */

code,
kbd,
samp {
  font-family: monospace, monospace; /* 1 */
  font-size: 1em; /* 2 */
}

/**
 * Add the correct font size in all browsers.
 */

small {
  font-size: 80%;
}

/**
 * Prevent `sub` and `sup` elements from affecting the line height in
 * all browsers.
 */

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

/* Embedded content
   ========================================================================== */

/**
 * Remove the border on images inside links in IE 10.
 */

img {
  border-style: none;
}

/* Forms
   ========================================================================== */

/**
 * 1. Change the font styles in all browsers.
 * 2. Remove the margin in Firefox and Safari.
 */

button,
input,
optgroup,
select,
textarea {
  font-family: inherit; /* 1 */
  font-size: 100%; /* 1 */
  line-height: 1.15; /* 1 */
  margin: 0; /* 2 */
}

/**
 * Show the overflow in IE.
 * 1. Show the overflow in Edge.
 */

button,
input {
  /* 1 */
  overflow: visible;
}

/**
 * Remove the inheritance of text transform in Edge, Firefox, and IE.
 * 1. Remove the inheritance of text transform in Firefox.
 */

button,
select {
  /* 1 */
  text-transform: none;
}

/**
 * Correct the inability to style clickable types in iOS and Safari.
 */

button,
[type="button"],
[type="reset"],
[type="submit"] {
  -webkit-appearance: button;
}

/**
 * Remove the inner border and padding in Firefox.
 */

button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

/**
 * Restore the focus styles unset by the previous rule.
 */

button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
  outline: 1px dotted ButtonText;
}

/**
 * Correct the padding in Firefox.
 */

fieldset {
  padding: 0.35em 0.75em 0.625em;
}

/**
 * 1. Correct the text wrapping in Edge and IE.
 * 2. Correct the color inheritance from `fieldset` elements in IE.
 * 3. Remove the padding so developers are not caught out when they zero out
 *    `fieldset` elements in all browsers.
 */

legend {
  box-sizing: border-box; /* 1 */
  color: inherit; /* 2 */
  display: table; /* 1 */
  max-width: 100%; /* 1 */
  padding: 0; /* 3 */
  white-space: normal; /* 1 */
}

/**
 * Add the correct vertical alignment in Chrome, Firefox, and Opera.
 */

progress {
  vertical-align: baseline;
}

/**
 * Remove the default vertical scrollbar in IE 10+.
 */

textarea {
  overflow: auto;
}

/**
 * 1. Add the correct box sizing in IE 10.
 * 2. Remove the padding in IE 10.
 */

[type="checkbox"],
[type="radio"] {
  box-sizing: border-box; /* 1 */
  padding: 0; /* 2 */
}

/**
 * Correct the cursor style of increment and decrement buttons in Chrome.
 */

[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

/**
 * 1. Correct the odd appearance in Chrome and Safari.
 * 2. Correct the outline style in Safari.
 */

[type="search"] {
  -webkit-appearance: textfield; /* 1 */
  outline-offset: -2px; /* 2 */
}

/**
 * Remove the inner padding in Chrome and Safari on macOS.
 */

[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

/**
 * 1. Correct the inability to style clickable types in iOS and Safari.
 * 2. Change font properties to `inherit` in Safari.
 */

::-webkit-file-upload-button {
  -webkit-appearance: button; /* 1 */
  font: inherit; /* 2 */
}

/* Interactive
   ========================================================================== */

/*
 * Add the correct display in Edge, IE 10+, and Firefox.
 */

details {
  display: block;
}

/*
 * Add the correct display in all browsers.
 */

summary {
  display: list-item;
}

/* Misc
   ========================================================================== */

/**
 * Add the correct display in IE 10+.
 */

template {
  display: none;
}

/**
 * Add the correct display in IE 10.
 */

[hidden] {
  display: none;
}
```

</details>

#### Step 8: Create the `main.css` file

To create the `main.css` file, run the following command:

<details>

<summary>Click to expand the code of the `main.css` file</summary>

```css:assets/css/main.css
* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

html {
  scroll-behavior: smooth;
  font-size: 62.5%;
}

body {
  font-family: "Roboto", sans-serif;
  font-size: 1.6rem;
  line-height: 1.6;
  color: #333;
}

header {
  display: flex;
  justify-content: space-around;
  align-items: center;
  background-color: #333;
  color: #fff;
  height: 10rem;
}

header > nav {
  display: flex;
  justify-content: space-around;
  align-items: center;
  width: 50%;
}

header > nav > ul {
  display: flex;
  justify-content: space-around;
  align-items: center;
  width: 100%;
}

header > nav > ul > li {
  list-style: none;
}

header > nav > ul > li > a {
  text-decoration: none;
  color: #fff;
}

main {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  height: 70vh;
}

main > h1 {
  font-size: 5rem;
  margin-bottom: 2rem;
}

main > p {
  font-size: 2rem;
  margin-bottom: 2rem;
}

footer {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 10rem;
  background-color: #333;
  color: #fff;
}
```

</details>

#### Step 8: Add the files to the GitHub repository

To add the files to the GitHub repository, run the following command:

```shell
# Add the files to the GitHub repository
git add .

# Commit the files to the GitHub repository
git commit -m "build: add the files to the GitHub repository"

# Push the files to the GitHub repository
git push origin devlopment
```

### Create a Jenkins Pipeline

#### Step 1: Create a new Jenkins pipeline

To create a new Jenkins pipeline, run the following command:

```shell
# Create a new Jenkins pipeline
touch Jenkinsfile
```

#### Step 2: Add the code to the Jenkins pipeline

To add the code to the Jenkins pipeline, run the following command:

```groovy:Jenkinsfile
pipeline {
  agent any
  environment {
    AWS_PUBLIC_IP_DEV = 'AWS_PUBLIC_IP_DEV'
  }

  stages {
    stage('Deploy to Development') {
      steps {
        sh '''
          # remove the files
          ssh -i "/var/lib/jenkins/.ssh/aws-key-pair.pem" -o StrictHostKeyChecking=no ec2-user@${AWS_PUBLIC_IP_DEV} "sudo rm -rf /var/www/html/*"
          # copy the files to the dev server
          scp -i "/var/lib/jenkins/.ssh/aws-key-pair.pem" -o StrictHostKeyChecking=no -r ./* ec2-user@${AWS_PUBLIC_IP_DEV}:/var/www/html
          # restart the apache server
          ssh -i "/var/lib/jenkins/.ssh/aws-key-pair.pem" -o StrictHostKeyChecking=no ec2-user@${AWS_PUBLIC_IP_DEV} "sudo systemctl restart httpd"
        '''
      }
    }
  }
}
```

#### Step 3: Add the files to the GitHub repository

To add the files to the GitHub repository, run the following command:

```shell
# Add the files to the GitHub repository
git add .

# Commit the files to the GitHub repository
git commit -m "build: add the jenkins pipeline to the GitHub repository"

# Push the files to the GitHub repository
git push origin devlopment
```

### Create a Jenkins Job

#### Step 1: Create a new Jenkins job

1. Open the Jenkins dashboard at EC2 instance from previous blog post. Go to `http://<public-ip-address>:8080/` and login with the credentials you created in the previous blog post.

![Jenkins dashboard](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-dashboard.png)

2. Click on `New Item` and enter the name of the job. Select `Pipeline` and click on `OK`.

- Name: `Quick Test Jenkins Devlopment`
- Select: `Pipeline`
- Click on: `OK`

![Jenkins new item](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-new-item.png)

3. Configure the job.

- Description: `Quick Test Jenkins Devlopment`
- Select: `Pipeline`
  - Definition: `Pipeline script from SCM`
  - SCM: `Git`
    - Repository URL: `https://github.com/MKAbuMattar/quick-test-jenkins.git`
    - Credentials: `Add` > `Jenkins` > `Global credentials (unrestricted)` > `Add Credentials` > `Username with password` > `Kind: Username with password` > `Username: <your-github-username>` > `Password: <your-github-password>` > `ID: <your-github-username>` > `Description: <your-github-username>` > `OK` > `OK` > `OK`
    - Branches to build: `*/devlopment`
- Click on: `Save`

> **Note:** How to Add Git Credentials in Jenkins for private repositories on GitHub

<iframe
  width="100%"
  height="515"
  src="https://www.youtube.com/embed/HSA_mZoADSw"
  title="How to Add Git Credentials in Jenkins"
  frameborder="0"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
  allowfullscreen
></iframe>

![Jenkins configure job](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-configure-job.png)

#### Step 2: Build the Jenkins job

Before building the Jenkins job, you need to add the key pair to the Jenkins server.

1. Connect to the Jenkins server using SSH.

```shell
# Connect to the Jenkins server using SSH
ssh -i "<path-to-key-pair>" ec2-user@<public-ip-address>
```

2. Login to the Jenkins server.

```shell
# Login to the Jenkins server
sudo -su jenkins
```

3. Create a `.ssh` directory.

```shell
# Create a .ssh directory
mkdir .ssh

# Change the directory
cd .ssh
```

4. Create a `aws-key-pair.pem` file.

```shell
# Create a aws-key-pair.pem file
cat > aws-key-pair.pem << EOF
-----BEGIN RSA PRIVATE KEY-----
copy the private key from the key pair
-----END RSA PRIVATE KEY-----
EOF
```

5. Change the permission of the `aws-key-pair.pem` file.

```shell
# Change the permission of the aws-key-pair.pem file
chmod 400 aws-key-pair.pem
```

6. Exit the Jenkins server.

```shell
# Exit the Jenkins server
exit
```

1. Click on `Build Now` to build the Jenkins job.

![Jenkins build now](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-build-now.png)

2. Click on `Console Output` to see the build logs.

![Jenkins console output](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-console-output.png)

#### Step 3: Build the Jenkins job Automatically on GitHub push

- Go to `GitHub` > go to `quick-test-jenkins` repository
  - Click on `Settings`
    - Click on `Webhooks`
      - Click on `Add webhook`
        - Payload URL: `http://<public-ip-address>:8080/github-webhook/`
        - Content type: `application/x-www-form-urlencoded`
        - Which events would you like to trigger this webhook?: `Just the push event.`
        - Active: `Yes`
        - Click on: `Add webhook`

![GitHub add webhook](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/github-add-webhook.png)

- Go to `Jenkins` > `Quick Test Jenkins Devlopment` job
  - Click on `Configure`
    - Select: `GitHub hook trigger for GITScm polling`
    - Click on: `Save`

![Jenkins configure job](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-configure-job-2.png)

#### Step 4: Test the Jenkins job Automatically on GitHub push

We'll add new files to the `devlopment` branch and push them to the GitHub repository.

```php:app/views/tabs.php
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title><?php echo ENV; ?> | My Site</title>
    <link rel="stylesheet" href="./assets/css/normalize.css" />
    <link rel="stylesheet" href="./assets/css/main.css" />
  </head>
  <body>
    <header>
      <h1>My Site</h1>

      <nav>
        <ul>
          <li><a href="./index.php">Home</a></li>
          <li><a href="./table.php">Table</a></li>
        </ul>
      </nav>
    </header>

    <main>
      <h2>Home</h2>
      <p>Welcome to my site!</p>
      <p>
        <?php echo "Environment: " . ENV; ?>
      </p>

      <table border="1">
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
          </tr>
        </thead>
        <tbody>
          <?php foreach ($users as $user) : ?>
            <tr>
              <td><?php echo $user['id']; ?></td>
              <td><?php echo $user['name']; ?></td>
              <td><?php echo $user['email']; ?></td>
            </tr>
          <?php endforeach; ?>
        </tbody>
      </table>
    </main>

    <footer>
      <p>My Site &copy; 2022</p>
    </footer>
  </body>
</html>
```

```php:app/views/index.php
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title><?php echo ENV; ?> | My Site</title>
    <link rel="stylesheet" href="./assets/css/normalize.css" />
    <link rel="stylesheet" href="./assets/css/main.css" />
  </head>
  <body>
    <header>
      <h1>My Site</h1>

      <nav>
        <ul>
          <li><a href="./index.php">Home</a></li>
          <li><a href="./table.php">Table</a></li>
        </ul>
      </nav>
    </header>

    <main>
      <h2>Home</h2>
      <p>Welcome to my site!</p>
      <p>
        <?php echo "Environment: " . ENV; ?>
      </p>
    </main>

    <footer>
      <p>My Site &copy; 2022</p>
    </footer>
  </body>
</html>
```

```php:table.php
<?php

// Include the controller file
require_once 'app/controllers/index.php';

// Include the view file
require_once 'app/views/tabs.php';
```

Before we push the files to the GitHub repository, we'll open the EC2 instance in the browser to see the changes.

![EC2 instance](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/ec2-instance-2.png)

```shell
# Add the files to the GitHub repository
git add .

# Commit the files to the GitHub repository
git commit -m "build: test the Jenkins job Automatically on GitHub push"

# Push the files to the GitHub repository
git push origin devlopment
```

![GitHub push](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/github-push.png)

1. Go to `Jenkins` > `Quick Test Jenkins Devlopment` job > `Build History`.

![Jenkins build history](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-build-history-2.png)

2. Click on `Build #2` to see the build details.

![Jenkins build details](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-build-details-2.png)

3. Open the EC2 instance in the browser to see the changes.

![EC2 instance](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/ec2-instance-3.png)

#### Step 5: We'll do the Same Changes for `Jenkinsfile` file

```shell
# create a new directory
mkdir -p .build

# create a new directory for development and production
mkdir -p .build/dev .build/prod

# move the Jenkinsfile file to the development directory
mv Jenkinsfile .build/dev

# create a new Jenkinsfile file for production
touch .build/prod/Jenkinsfile
```

```groovy:.build/prod/Jenkinsfile
pipeline {
  agent any
  environment {
    AWS_PUBLIC_IP_PROD = ''
  }

  stages {
    stage('Deploy to Production') {
      steps {
        sh '''
          # remove the files
          ssh -i "/var/lib/jenkins/.ssh/aws-key-pair.pem" -o StrictHostKeyChecking=no ec2-user@${AWS_PUBLIC_IP_PROD} "sudo rm -rf /var/www/html/*"
          # copy the files to the prod server
          scp -i "/var/lib/jenkins/.ssh/aws-key-pair.pem" -o StrictHostKeyChecking=no -r ./* ec2-user@${AWS_PUBLIC_IP_PROD}:/var/www/html
          # restart the apache server
          ssh -i "/var/lib/jenkins/.ssh/aws-key-pair.pem" -o StrictHostKeyChecking=no ec2-user@${AWS_PUBLIC_IP_PROD} "sudo systemctl restart httpd"
        '''
      }
    }
  }
}
```

#### Step 6: Do Some Changes at Jenkins Configure for Development Jenkins Job

1. Go to `Jenkins` > `Quick Test Jenkins Devlopment` job > `Configure`.
2. Scroll down to `Script Path` and change the value to `Jenkinsfile` to `.build/dev/Jenkinsfile`.

![Jenkins configure](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-configure-2.png)

#### Step 7: Setup the Production Jenkins job

the Production Jenkins job will be triggered automatically when the Development Jenkins job is successful merge to the `main` branch.

![Jenkins dashboard](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-dashboard-2.png)

2. Click on `New Item` and enter the name of the job. Select `Pipeline` and click on `OK`.

- Name: `Quick Test Jenkins Production`
- Select: `Pipeline`
- Click on: `OK`

![Jenkins new item](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-new-item-2.png)

3. Configure the job.

- Description: `Quick Test Jenkins Production`
- Select: `Poll SCM`
  - Schedule: `H/5 * * * *`
  - Select: `Ignore post-commit hooks`
- Select: `Pipeline`
  - Definition: `Pipeline script from SCM`
  - SCM: `Git`
    - Repository URL: `https://github.com/MKAbuMattar/quick-test-jenkins.git`
    - Credentials: `Add` > `Jenkins` > `Global credentials (unrestricted)` > `Add Credentials` > `Username with password` > `Kind: Username with password` > `Username: <your-github-username>` > `Password: <your-github-password>` > `ID: <your-github-username>` > `Description: <your-github-username>` > `OK` > `OK` > `OK`
    - Branches to build: `*/main`
- Click on: `Save`

![Jenkins configure job](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/jenkins-configure-job-3.png)

#### Step 8: Go to GitHub and Merge the `devlopment` branch to the `main` branch

1. Go to `GitHub` > `quick-test-jenkins` repository > `devlopment` branch > `Pull requests` > `New pull request`.

![GitHub pull request](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/github-pull-request-2.png)

Check the EC2 instance in the browser to see the changes, and you'll see the changes.

![EC2 instance](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/ec2-instance-4.png)

![EC2 instance](/assets/blog/0032-how-to-ci-cd-aws-with-github-using-jenkins/ec2-instance-5.png)

## Conclusion

In this tutorial, we learned how to setup a CI/CD pipeline using Jenkins and GitHub. We also learned how to setup a Jenkins job to automatically deploy the code to the AWS EC2 instance.

## References

- [Jenkins User Documentation](https://www.jenkins.io/doc/)
- [Jenkins Pipeline Syntax](https://www.jenkins.io/doc/book/pipeline/syntax/)
- [GitHub Docs - Webhooks](https://docs.github.com/en/webhooks)
- [AWS Command Line Interface (CLI) User Guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html)
- [Amazon EC2 User Guide for Linux Instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html)
- [Amazon VPC User Guide](https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html)
- [AWS Identity and Access Management (IAM) User Guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html)
- [PHP Manual](https://www.php.net/manual/en/)
- [Getting started with Jenkins Pipeline](https://www.jenkins.io/doc/book/pipeline/getting-started/)
- [Managing Jenkins Credentials](https://www.jenkins.io/doc/book/using/managing-credentials/)
- [AWS EC2 Key Pairs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html)
- [User data and shell scripts for EC2 instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html)
- [Install Jenkins on AWS EC2 Instance](/blog/install-jenkins-on-aws-ec2-instance) (Previous related post)
- [Git Handbook](https://guides.github.com/introduction/git-handbook/)
- [Continuous Integration with Jenkins - Jenkins Documentation](https://www.jenkins.io/doc/developer/guides/continuous-integration/)
