Presenter: Rahul Shivalkar
Event: SIES College, Nerul, Navi Mumbai, India
Audience: Faculties
Before starting this workshop, ensure you have:
-
An active AWS Account with admin or equivalent permissions
-
Basic understanding of:
- Linux commands
- Git & GitHub
- CI/CD concepts (high-level is enough)
-
AWS services familiarity (nice to have):
- EC2
- S3
- IAM
- DynamoDB
-
A modern browser + stable internet
-
Ability to use:
- AWS CloudShell
- SSH into EC2
- GitHub clone, and push operations
This hands-on CI/CD workshop is built for:
- Professionals exploring AWS CI/CD fundamentals
- DevOps beginners learning end-to-end CI/CD pipelines
- Students preparing for interviews or cloud practice
- Developers wanting to automate deployments
- Anyone who wants a practical, working CI/CD pipeline on AWS
No prior CI/CD experience is required β everything is step-by-step.
To avoid confusion and maintain consistency:
- All AWS resources must be created in the region:
US East (N. Virginia) β
us-east-1
Why?
- S3 static website hosting
- AMI availability
- DynamoDB consistency
- Keeping screenshots and instructions aligned
- Avoiding region mismatch issues during the workshop
If you accidentally create resources in another region, you may experience:
- S3 website not accessible
- EC2 AMI not found
- DynamoDB table not visible
- IAM roles misconfigured
So ensure AWS Console is always set to us-east-1.
This workshop implements a 3-tier architecture with CI/CD automation:
- Build Server (EC2): Runs Jenkins and automates deployments
- Frontend: Hosted as a static site on S3
- Backend: Runs on a separate EC2 server
- DynamoDB: Stores assignment/submission data
- GitHub: Single source of truth for both frontend & backend code
You get a real, CI/CD workflow in a simplified, workshop-friendly environment.
In this lab, you will build an end-to-end CI/CD pipeline:
- 1Γ Build Server (EC2) running Jenkins
- 1Γ Backend Server (EC2)
- 1Γ Frontend S3 Static Website
- 2Γ DynamoDB Table
- Required IAM roles, policies & permissions
- 1Γ Backend Github Repo
- 1Γ Frontend Github Repo
- Jenkins pipeline for frontend
- Jenkins pipeline for backend
- Automated deployment into S3
- Automated SSH deployment to backend server
- GitHub β Jenkins webhook integration
- A working βAssignment Submission Appβ with live frontend β backend β DynamoDB flow
Everything is hands-on. You will deploy actual code from the repo.
π Why we need the repository
The workshop repository contains all pre-written code, configuration files, and instructions required to follow along. Accessing the repository ensures everyone uses the same version of files, avoiding inconsistencies and errors during the CI/CD setup.To start the workshop, we first need to access the repository containing all the files and instructions.
- Open Google.
- Search for:
rahul shivalkar github - Click on the GitHub profile shown in the results.
- Go to the Repositories tab.
- Click on the repository named
ci-cd-workshop.
π Why we do this: This ensures you are always accessing the correct and latest repository, even if you donβt have the direct link. It also helps beginners practice finding resources on GitHub.
βοΈ Why We Need an AWS Account
AWS provides the cloud infrastructure required to run our CI/CD workshop application. With an AWS account, you can access essential services like:- EC2 β to run backend and build servers
- S3 β to host the frontend static website
- DynamoDB β to store application data
Having an account ensures you can create, manage, and deploy resources needed for all layers of the application.
- Open the AWS Console: https://aws.amazon.com/console/
- If you donβt have an account, click Create account and follow the sign-up steps.
- If you already have an account, click Sign In and enter your credentials.
π‘ Tip for Beginners: Make sure you note down your account email and password, as youβll need them throughout the workshop.
π Why GitHub is Important
GitHub is a cloud-based platform for **version control** using Git.It allows you to:
- Store your code securely in repositories
- Track changes to your files over time
- Collaborate with others on the same project
- Integrate with Jenkins for automated CI/CD pipelines
Using GitHub ensures your workshop code is organized, safe, and ready for deployment.
- Open the GitHub website: https://github.com/
- If you donβt have an account, click Sign up and follow the instructions.
- If you already have an account, click Sign in and enter your credentials.
π‘ Tip for Beginners: Remember your GitHub username and password, as youβll need them when configuring repositories and Jenkins pipelines.
π₯οΈ Purpose of the Build Server
The build server (EC2 instance) acts as the CI/CD orchestrator. It runs Jenkins, pulls code from GitHub, and automates deployment for both frontend and backend components. Using a dedicated server keeps deployment consistent and separate from local machines.We will create one build server in the N. Virginia (us-east-1) region.
Ensure your AWS region is set to:
US East (N. Virginia) β us-east-1
-
In the AWS Console, search for EC2 and open it.
-
Click Instances in the left-hand menu.
-
Click Launch instance.
-
Fill in the following configuration details:
-
Name:
ci-cd-workshop-build-server -
AMI: Ubuntu Server 24.04 LTS (HVM), SSD Volume Type
-
Instance Type:
t2.medium -
Key Pair:
- Click Create new key pair
- Name:
ci-cd-workshop - Type: RSA
- File Format:
.pem(We will use AWS CloudShell, which supports.pemfiles directly.) - Click Create key pair to download the file.
-
Network Settings:
- Select Create security group
- Allow SSH (port 22) from Anywhere (0.0.0.0/0)
β οΈ This is NOT recommended for production. For demo and workshop purposes, we are allowing open SSH access to avoid connection issues.
-
Storage:
- Root volume size: 20 GB
-
Number of Instances:
- Set to 1
-
-
Click Launch instance.
π IAM Role for Secure Access
An IAM role grants the EC2 build server the permissions it needs to interact with AWS resources. For this workshop, the role allows the build server to access S3 for frontend deployments. Roles eliminate the need to store credentials on the server itself.To allow the backend to access S3 :
-
Go to AWS Console β IAM: https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/home
-
Click Roles in the left-hand menu.
-
Click Create role
-
Choose EC2 as the Service or use case β Next
-
Search and Select the following policies to attach it to the role β Next
AmazonS3FullAccess
-
Name the role:
ci-cd-workshop-build-server-role -
Click Create role
π Linking Role to EC2
Attaching the IAM role to the EC2 instance enables the server to assume the role and gain the permissions defined earlier. This is required for Jenkins pipelines to deploy the frontend to S3 without manually providing AWS credentials.- Go to EC2 β Instances β Select ci-cd-workshop-build-server β Actions β Security β Modify IAM Role β Search and Select
ci-cd-workshop-build-server-roleβ Click Update IAM role
π Secure Connection Methods
SSH allows secure remote access to the build server. Using AWS CloudShell simplifies the connection process, as it works directly from the browser and supports the `.pem` key. Once connected, you can run commands, install Jenkins, and manage deployments.When launching the EC2 instance earlier, we downloaded a .pem key pair file. This key is required to securely connect to the server.
There are multiple ways to connect to an EC2 instance:
- Windows: Use PuTTY
β You would download a .ppk key (or convert
.pemβ.ppk) - Mac / Linux: Use the Terminal (supports
.pemdirectly) - AWS CloudShell: Easiest method, works from the browser
For this workshop, we will use AWS CloudShell to keep things simple.
- On the AWS Console header, click the CloudShell icon (located near the top center of the page, slightly right of the search box).
- A terminal window will open at the bottom of your browser.
- CloudShell will appear regardless of which AWS page you are on.
- In CloudShell, click Actions β Upload file
- Select the key file you downloaded earlier:
ci-cd-workshop.pem - Upload the file.
Run the following command in CloudShell:
chmod 400 ci-cd-workshop.pemThis sets secure permissions required by SSH.
- Open the EC2 Console
- Select your instance ci-cd-workshop-build-server
- Copy the Public IPv4 address from the Details tab
Now connect to your server:
ssh -i ci-cd-workshop.pem ubuntu@<BUILD-SERVER-PUBLIC-IP>When asked:
Are you sure you want to continue connecting (yes/no/[fingerprint])?
Type:
yes
βοΈ Why AWS CLI is needed
The AWS CLI allows the build server to interact with AWS services like S3 and EC2 programmatically. It is essential for pipelines to automate deployments, create resources, and manage AWS infrastructure without manually using the console.Run the following commands on the build server:
sudo apt update -ysudo apt install unzip curl -ycurl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"unzip awscliv2.zipsudo ./aws/installaws --versionThis confirms that the AWS CLI is installed.
Next, run the following command to verify that the IAM role is correctly attached to the EC2 instance:
aws sts get-caller-identityIf everything is configured properly, you will see output similar to:
{
"UserId": "ARO********FWTVQ:i-0b85*********e0945",
"Account": "************",
"Arn": "arn:aws:sts::************:assumed-role/ci-cd-workshop-build-server-role/i-0b85*********e0945"
}This means:
- The EC2 instance is successfully assuming the IAM role (
ci-cd-workshop-build-server-role). - Jenkins pipelines running on this instance can now use AWS CLI commands.
- This enables automatic frontend deployment to S3 and other AWS actions.
π§© Jenkins for CI/CD
Jenkins is a continuous integration/continuous deployment (CI/CD) tool that automates building, testing, and deploying code. Installing it on the build server allows automated pipelines for both frontend and backend projects, triggered on GitHub commits.Follow the steps below on your EC2 build server after connecting through CloudShell.
sudo apt updatesudo apt install openjdk-17-jdk -yVerify:
java -versioncurl -fsSL https://pkg.jenkins.io/debian/jenkins.io-2023.key \
| sudo tee /usr/share/keyrings/jenkins-keyring.asc > /dev/nullecho "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian binary/" \
| sudo tee /etc/apt/sources.list.d/jenkins.list > /dev/nullsudo apt updateYou should now see entries from pkg.jenkins.io in the output.
sudo apt install jenkins -yStart Jenkins:
sudo systemctl start jenkinsEnable auto-start on boot:
sudo systemctl enable jenkinsCheck status:
sudo systemctl status jenkinsYou should see active (running). Press q to exit the status screen.
Jenkins runs on port 8080, so we must allow inbound traffic to this port on the Build Serverβs security group.
Follow these steps carefully:
-
Go to the AWS Console
-
Search for EC2 and open it
-
In the left menu, click Instances
-
Select your instance named ci-cd-workshop-build-server
-
At the bottom panel, click on the Security tab
-
Under Security groups, click on the security group linked to your instance (Example:
sg-0123456789abcdef) -
Now you are on the Security Group Details page
-
Click the Inbound rules tab
-
Click on Edit inbound rules
-
Click Add rule
-
Enter the following:
-
Type: Custom TCP
-
Port range:
8080 -
Source:
0.0.0.0/0(Anywhere IPv4)This is NOT recommended for production, but acceptable for this workshop/demo.
-
-
Click Save rules
Your Build Server is now accessible on port 8080, which is required to open the Jenkins UI.
π§ Configuring Jenkins
The initial setup configures Jenkins with suggested plugins, unlocks the admin account, and prepares it for pipelines. The SSH Agent Plugin allows Jenkins to securely connect to backend EC2 servers via SSH, enabling automated backend deployments.The backend pipeline uses the sshagent step for SSH-based deployment. To enable it, you must install the SSH Agent Plugin.
Open your browser and visit:
http://<build-server-public-ip>:8080
Replace <public-ip> with the public IP of the Build Server EC2 instance.
Run the following command in CloudShell (already connected to your EC2 instance):
sudo cat /var/lib/jenkins/secrets/initialAdminPasswordCopy the displayed password and paste it into the Jenkins unlock screen and cick on Continue.
After unlocking Jenkins, you will see the setup wizard. Follow these steps:
-
Install Suggested Plugins Jenkins will automatically begin installing the recommended plugins. (This may take a few minutes.)
-
On the next screen, when asked to create the first admin user: Click βSkip and continue as adminβ (For the workshop, we do not need to create a new user.)
-
Jenkins will show the Jenkins URL. Do not change anything here. Simply click Save and Finish.
-
You will now see the confirmation message: βJenkins is ready!β
-
Click Start using Jenkins
You will now be taken to the Jenkins dashboard.
The backend pipeline uses the sshagent step for SSH-based deployment.
To enable it, you must install the SSH Agent Plugin.
- From the Jenkins dashboard β Click Manage Jenkins (Gear icon at the top right, just before your profile icon)
- Click Plugins
- Go to the Available Plugins tab
- Search and Select:
SSH Agent
- Click on Install
This plugin enables the sshagent { ... } syntax used in the backend Jenkinsfile.
ποΈ Three-Tier Architecture
The workshop app uses a 3-tier architecture:- Frontend (S3) β Serves static website content.
- Backend (EC2) β Hosts the Python Flask application.
- Database (DynamoDB) β Stores assignments and submissions.
Separating these layers improves scalability, maintainability, and deployment automation.
Before we deploy our code, we need to create the infrastructure where the app will run. This includes:
- Frontend β S3 bucket (static website)
- Backend β EC2 instance (Flask app)
- Database β DynamoDB table
Letβs create them step by step.
-
Open the AWS Console β S3: https://us-east-1.console.aws.amazon.com/s3/home?region=us-east-1 and click Create bucket.
-
Configure the bucket:
-
Bucket name:
ci-cd-workshop-frontend-<your-name> -
Region: same as your EC2 Build Server (e.g., us-east-1)
-
Block all public access: Uncheck and tick the acknowledgement checkbox
β Note: Not recommended for production, but fine for this demo. Bucket names must be globally unique β if taken, add some characters at the end.
-
Leave other options as default.
-
-
Click Create bucket.
-
Enable Static website hosting:
- Open your bucket β Properties β Static website hosting β Edit β Enable
- Index document:
index.htmlβ Save changes - Note the Bucket website endpoint URL.
-
Make the bucket publicly accessible:
- Go to Permissions β Bucket Policy β Edit
- Paste the following policy (replace
<your-bucket-name>):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::<your-bucket-name>/*"
}
]
}- Click Save changes.
β Your frontend S3 bucket is ready to host the static website and is publicly accessible.
Our backend Flask app will run on an EC2 instance.
-
Go to AWS Console β EC2: https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#Home:
-
Click Launch instances
-
Configure the instance:
-
Name:
ci-cd-workshop-backend-server -
AMI: Ubuntu Server 24.04 LTS (HVM), SSD Volume Type
-
Instance type: t2.medium
-
Key pair: Search and select an existing
ci-cd-workshop -
Network Settings:
- Select Create security group
- Allow SSH (port 22) from Anywhere (0.0.0.0/0)
β οΈ This is NOT recommended for production. For demo and workshop purposes, we are allowing open SSH access to avoid connection issues.
-
Storage: 20 GB
-
Number of instances: 1
-
-
Click Launch instance
-
Once running, note the public IP β we will use this to connect to the backend.
Python runs on port 5000, so we must allow inbound traffic to this port on the Backend Serverβs security group.
Follow these steps carefully:
-
Go to the AWS Console
-
Search for EC2 and open it
-
In the left menu, click Instances
-
Select your instance named ci-cd-workshop-backend-server
-
At the bottom panel, click on the Security tab
-
Under Security groups, click on the security group linked to your instance (Example:
sg-0123456789abcdef) -
Now you are on the Security Group Details page
-
Click the Inbound rules tab
-
Click on Edit inbound rules
-
Click Add rule
-
Enter the following:
-
Type: Custom TCP
-
Port range:
5000 -
Source:
0.0.0.0/0(Anywhere IPv4)This is NOT recommended for production, but acceptable for this workshop/demo.
-
-
Click Save rules
Your Backend Server is now accessible on port 5000, which is required for the Frontend App to connect on the Backed App.
π Important: Before connecting, ensure you're working from AWS CloudShell, not from the ci-cd-workshop-build-server EC2 instance. If unsure, simply type exit to start a fresh CloudShell session.
- Open the EC2 Console
- Select your instance ci-cd-workshop-backend-server
- Copy the Public IPv4 address from the Details tab
Now connect to your server:
ssh -i ci-cd-workshop.pem ubuntu@<BACKEND-SERVER-PUBLIC-IP>When asked:
Are you sure you want to continue connecting (yes/no/[fingerprint])?
Type and enter:
yes
Run the following commands on the backend server:
sudo apt-get updatesudo apt install python3.12-venvWhen asked:
Do you want to continue? [Y/n] Y
Type and enter:
yes
We will create two DynamoDB tables to store all app data for the workshop:
-
Assignments Table β stores assignment metadata
- Table name:
ci-cd-workshop-assignments - Primary key:
assignment_id(String)
- Table name:
-
Submissions Table β stores student submissions
- Table name:
ci-cd-workshop-submissions - Primary key:
submission_id(String)
- Table name:
β Note: DynamoDB table names must be unique in your account. If you already have a table with this name, add a short suffix like your initials (e.g.,
ci-cd-workshop-assignments-rs)
-
Open AWS Console β DynamoDB: https://us-east-1.console.aws.amazon.com/dynamodbv2/home?region=us-east-1#dashboard
-
Click Create table for the Assignments Table:
- Enter Table name:
ci-cd-workshop-assignments - Set Partition key:
assignment_id(String) - Leave all other settings as default (on-demand capacity, encryption, etc.)
- Click Create table
- Enter Table name:
-
Repeat the process for the Submissions Table:
- Enter Table name:
ci-cd-workshop-submissions - Set Partition key:
submission_id(String) - Leave all other settings as default β Click Create table
- Enter Table name:
β Both tables are now ready to store assignments and student submissions for the workshop app.
To allow the backend to access S3 and DynamoDB:
-
Go to AWS Console β IAM: https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/home
-
Click Roles in the left-hand menu.
-
Click Create role
-
Choose EC2 as the Service or use case β Next
-
Search and Select the following policies to attach it to the role β Next
AmazonS3FullAccessAmazonDynamoDBFullAccess
-
Name the role:
ci-cd-workshop-backend-server-role -
Create role
- Go to EC2 β Instances β Select ci-cd-workshop-backend-server β Actions β Security β Modify IAM Role β Search and Select
ci-cd-workshop-backend-server-roleβ Click Update IAM role
β All three tiers of our app are now ready!
πΎ Code Management and Versioning
Repositories provide a structured location for code and allow tracking changes over time. By cloning, committing, and pushing files from the build server, you ensure that Jenkins pipelines can pull the latest code for automated deployment.In this step, we will create GitHub repositories, clone them on the build server, copy the prepared workshop files, commit, and push them to GitHub using a Personal Access Token (PAT).
β οΈ All commands should be run on the build server using CloudShell
For this workshop, we will create public repositories so Jenkins can easily access them. β‘οΈ In real organizations, repositories are usually private for security and compliance.
-
Open your GitHub account: https://github.com/
-
Click New.
-
Create two repositories:
ci-cd-workshop-frontendci-cd-workshop-backend
-
Leave all options default (Public, No README, No .gitignore, No license).
-
Click Create repository.
-
Click your Profile Picture in top-right corner β Settings β Developer settings β Personal access tokens β Tokens (classic)
-
Click Generate new token β Generate new token (classic)
-
Give a note/name:
ci-cd-workshop-token -
Select expiration (e.g., 90 days)
-
Under Scopes, select:
repoβ Full control of private and public repositoriesworkflowβ Optional for Jenkins triggers (future step)
-
Click Generate token
-
Copy the token somewhere safe, do not share with anyone. You wonβt be able to see it again.
We will use this token as the password when pushing commits from the build server.
- Open CloudShell from AWS Console:
The aim of the following steps is to clone your frontend and backend repositories, add the required workshop code and Jenkinsfiles that are provided in this main repository, and push them back to your GitHub repos.
You can perform these steps from any machine, including your laptop. We use the build server here only for convenience.
Important: Before connecting, ensure you're working from AWS CloudShell, not from the ci-cd-workshop-backend-server EC2 instance. If unsure, simply type exit to start a fresh CloudShell session.
ssh -i ci-cd-workshop.pem ubuntu@<BUILD-SERVER-PUBLIC-IP>- Navigate to a working directory:
mkdir ~/ci-cd-workshopcd ~/ci-cd-workshopReplace <Your Name> and <your-email@example.com> with your actual details:
git config --global user.name "<Your Name>"git config --global user.email "<your-email@example.com>"Replace <your-username> with your GitHub username:
# Frontend
git clone https://github.com/<your-username>/ci-cd-workshop-frontend.git# Backend
git clone https://github.com/<your-username>/ci-cd-workshop-backend.gitIn this step, you will download the required frontend and backend application files into your GitHub repositories. These files are already prepared in the main workshop repo and need to be copied into your cloned frontend/backed repos so that Jenkins can build and deploy them.
Navigate to your frontend repo:
cd ~/ci-cd-workshop/ci-cd-workshop-frontendBelow are the files you will download and why each is needed:
This is the main UI of the Assignment Tracker application. It contains:
- The HTML layout
- JavaScript functions to call the backend API
- Logic to display Assignment lists and versions
Download:
wget https://raw.githubusercontent.com/shivalkarrahul/ci-cd-workshop/main/frontend/index.htmlThis file contains a simple JSON with a version number. It helps verify whether the frontend deployment updated successfully in S3.
Download:
wget https://raw.githubusercontent.com/shivalkarrahul/ci-cd-workshop/main/frontend/frontend_version.jsonThis Jenkinsfile defines:
- How the frontend should be built
- How files are uploaded to S3
- Which S3 bucket to deploy to
- Pipeline stages and parameters
Download:
wget https://raw.githubusercontent.com/shivalkarrahul/ci-cd-workshop/main/frontend/JenkinsfileNavigate to your backend repo:
cd ~/ci-cd-workshop/ci-cd-workshop-backendBelow are the backend files and their purpose:
This is the core Python backend service. It provides:
- API endpoints for assignments
- Logic to submit, list, and create assignments
- Version info endpoint
Download:
wget https://raw.githubusercontent.com/shivalkarrahul/ci-cd-workshop/main/backend/app.pyThis file stores the backend version. It helps confirm if the backend deployment on EC2 updated successfully.
Download:
wget https://raw.githubusercontent.com/shivalkarrahul/ci-cd-workshop/main/backend/backend_version.txtThis file lists all Python libraries required for the backend, such as Flask. Jenkins installs these packages on the server using this file.
Download:
wget https://raw.githubusercontent.com/shivalkarrahul/ci-cd-workshop/main/backend/requirements.txtThis Jenkinsfile defines the backend continuous delivery process including:
- Connecting to backend EC2 via SSH
- Installing dependencies
- Restarting the Flask app
- Passing server IP as a parameter
Download:
wget https://raw.githubusercontent.com/shivalkarrahul/ci-cd-workshop/main/backend/Jenkinsfilecd ~/ci-cd-workshop/ci-cd-workshop-frontendls -lgit statusgit add .git statusgit commit -m "Initial commit: workshop frontend files"git push origin mainDuring
git push, Git will prompt for credentials:
- Username: GitHub username
- Password: Personal Access Token (PAT) created earlier
cd ~/ci-cd-workshop/ci-cd-workshop-backendls -lgit statusgit add .git statusgit commit -m "Initial commit: workshop backend files"git push origin mainDuring
git push, Git will prompt for credentials:
- Username: GitHub username
- Password: Personal Access Token (PAT) created earlier
- Open the repositories in your GitHub account:
Note: Replace <your-username> in the URL with your Github username
- You should see all your files and initial commits.
β Congratulations! Your workshop files are now versioned in GitHub and ready for Jenkins CI/CD pipelines.
π Automating Deployment
Jenkins pipelines automate the deployment of frontend and backend code:- Frontend: Deployed to S3 bucket.
- Backend: Deployed to EC2 via SSH.
GitHub webhooks trigger pipelines on every push, ensuring the application is always up-to-date without manual intervention.
In this step, we will configure Jenkins to automatically deploy the frontend to S3 and the backend to the backend EC2 server whenever code is pushed to GitHub. We will also store the existing EC2 key pair in Jenkins for secure SSH access.
β οΈ All commands and steps are performed on the build server via CloudShell or SSH.
We will use the existing EC2 key pair ci-cd-workshop.pem for backend deployment.
- Open your Jenkins Dashboard in browser:
Open the Jenkins dashboard in your browser using the public IP of your Jenkins server.
http://<build-server-public-ip>:8080
If you are logged out, sign back in using:
-
Username:
admin -
Password: The Jenkins initial admin password you retrieved earlier using the following command on ci-cd-workshop-build-server:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
-
Go to Manage Jenkins(Gear Icon in right top corner) β Credentials β System β Global credentials β Add Credentials.
-
Select:
- Kind: SSH Username with private key
- Scope: Global
- ID:
backend-server-ssh - Description: SSH Key for backend-server deployment
- Username:
ubuntu - Open
ci-cd-workshop.pemin notepad/textpad on your local machine and copy the content - Private Key: Select Enter directly radio button βClick on Add button and paste the content of
ci-cd-workshop.pemyou copied
-
Click Create.
β Jenkins now has the SSH key to connect to the backend server.
We will create a frontend pipeline that deploys the static site to S3 using a parameterized bucket name.
-
In Jenkins Dashboard β New Item.
-
Enter Item Name:
frontend-deploy -
Select Pipeline β Click OK.
-
General β Tick GitHub project, Checkbox and add Frontend Repo URL in Project url.
-
Triggers β Tick GitHub hook trigger for GITScm polling Checkbox
-
Scroll to Pipeline section β Definition:
Pipeline script from SCM. -
Select Git β Repository URL:
https://github.com/<your-username>/ci-cd-workshop-frontend.git -
Branch:
main -
Script Path:
Jenkinsfile -
Save the pipeline.
Note: The frontend
Jenkinsfileshould use a parameter for bucket name, so when triggered via webhook, it automatically deploys to the correct S3 bucket without prompting.
We will create a backend pipeline that deploys the Python Flask app to the backend EC2 server using SSH.
-
In Jenkins Dashboard β New Item.
-
Enter Item Name:
backend-deploy -
Select Pipeline β Click OK.
-
General β Tick GitHub project, Checkbox and add Backend Repo URL in Project url.
-
Triggers β Tick GitHub hook trigger for GITScm polling Checkbox
-
Scroll to Pipeline section β Definition:
Pipeline script from SCM. -
Select Git β Repository URL:
https://github.com/<your-username>/ci-cd-workshop-backend.git -
Branch:
main -
Script Path:
Jenkinsfile -
Save the pipeline.
Note: Backend
Jenkinsfileuses the stored SSH key (backend-server-ssh) to connect to backend EC2, setup virtual environment, install requirements, stop old process, and start Flask app.
We will make Jenkins automatically trigger pipelines on GitHub push.
Repeat this for both the repos ci-cd-workshop-backend and ci-cd-workshop-frontend
- Open GitHub β Repository β Settings β Webhooks β Add webhook.
- Enter:
Note: Replace <build-server-public-ip> with your build server's public IP
-
Payload URL:
http://<build-server-public-ip>:8080/github-webhook/ -
Content type:
application/json -
Secret: leave blank (optional)
-
Which events would you like to trigger this webhook? β Just the push event
- Click Add webhook.
β Whenever you push code to the repo, Jenkins will automatically trigger the pipeline.
The purpose of the following steps is to make small changes to your frontend and backend repositories, commit them, and push them to GitHub.
You can perform these changes from any machine (your laptop, CloudShell, or build server). Here we use the build server only for convenience.
π Important: Before connecting, ensure you're working from AWS CloudShell, not from the ci-cd-workshop-backend-server EC2 instance. If unsure, simply type exit to start a fresh CloudShell session.
Navigate to the frontend directory:
cd ~/ci-cd-workshop/ci-cd-workshop-frontendUpdate frontend_version.json
vim frontend_version.json- Press i β edit the version
- Press Esc, type :wq!, press Enter
Update Jenkinsfile
vim JenkinsfileFind the parameter:
ci-cd-workshop-frontend-<your-name>
Replace it with your actual S3 bucket name.
- Press i β edit the version
- Press Esc, type :wq!, press Enter
Update index.html
Edit using:
vim index.htmlLook for(on line number 98):
const API_BASE = "http://127.0.0.1:5000";Replace with(<your-backend-server-public-ip> change this to your Backend Server Public IP ):
const API_BASE = "http://<your-backend-server-public-ip>:5000";- Press i β edit the version
- Press Esc, type :wq!, press Enter
git statusgit add .git commit -m "updates to deploy the frontend"git push origin mainDuring
git push, Git will prompt for credentials:
- Username: GitHub username
- Password: Personal Access Token (PAT) created earlier
- Open Jenkins β frontend-deploy pipeline β check Builds.
- If successful, Jenkins will upload your updated frontend to S3.
- Go to S3 β Your Bucket β Properties
- Open Static Website Hosting
- Click the Bucket Website Endpoint URL
If the app loads and shows: βFrontend Version: β, the deployment is successful.
Navigate to the backend directory:
cd ~/ci-cd-workshop/ci-cd-workshop-backendUpdate backend_version.txt
vim backend_version.txt- Press i β edit the version
- Press Esc, type :wq!, press Enter
Update Jenkinsfile
Open
vim JenkinsfileUpdate the parameter:
<backend-server-ip>
Replace with your backend EC2 public IP address.
- Press i β edit the version
- Press Esc, type :wq!, press Enter
git statusgit add .git commit -m "updates to deploy the backend"git push origin mainDuring
git push, Git will prompt for credentials:
- Username: GitHub username
- Password: Personal Access Token (PAT) created earlier
Open Jenkins β backend-deploy β verify the latest execution.
This should:
- Connect over SSH
- Install dependencies
- Restart the backend server
- Run your updated backend API
Open your S3 hosted frontend website again.
If you see:
βBackend Version: β, and API calls work (Create, Submit, List Assignments), your backend deployment is successful.
You now have:
β Automated frontend CI/CD β GitHub β Jenkins β S3 β Static Website Hosting
β Automated backend CI/CD β GitHub β Jenkins β SSH β EC2 Deployment
β GitHub Webhooks triggering pipelines on every push
Your environment is now running a complete CI/CD pipeline for a live full-stack application.
π§Ή Cleaning Up Resources
The cleanup script removes all workshop resources: EC2 instances, S3 buckets, DynamoDB tables, IAM roles, and security groups. This ensures no lingering costs or unnecessary AWS resources after the workshop.You can delete all resources manually from the AWS Console (EC2, S3, IAM, DynamoDB, Security Groups, Key Pairs, etc.). That approach is completely valid.
However, manual deletion is:
- slow
- error-prone
- easy to miss resources
- painful if you created many items
So the commands below are provided only to save your time and ensure everything is deleted cleanly.
Before connecting, ensure you're working from AWS CloudShell, not from the ci-cd-workshop-build-server or ci-cd-workshop-backend-server EC2 instance. If unsure, simply type exit to start a fresh CloudShell session.
REGION="us-east-1"
# Build server
BUILD_SERVER_NAME="ci-cd-workshop-build-server"
BUILD_SERVER_ROLE="ci-cd-workshop-build-server-role"
BUILD_KEY_PAIR="ci-cd-workshop"Note: FRONTEND_BUCKET with you Bucket Name
# Frontend S3 bucket
FRONTEND_BUCKET="ci-cd-workshop-frontend-<your-name>"# Backend server
BACKEND_SERVER_NAME="ci-cd-workshop-backend-server"
BACKEND_SERVER_ROLE="ci-cd-workshop-backend-server-role"
# DynamoDB tables
DDB_ASSIGN_TABLE="ci-cd-workshop-assignments"
DDB_SUBMIT_TABLE="ci-cd-workshop-submissions"
# Build server
BUILD_INSTANCE_PROFILE="ci-cd-workshop-build-server-role"
# Backend server
BACKEND_INSTANCE_PROFILE="ci-cd-workshop-backend-server-role"
# Disable AWS pager
export AWS_PAGER=""BUILD_INSTANCE_ID=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=$BUILD_SERVER_NAME" \
--query "Reservations[].Instances[].InstanceId" \
--output text \
--region $REGION \
--no-cli-pager)
echo "Build Server Instance: $BUILD_INSTANCE_ID"
BACKEND_INSTANCE_ID=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=$BACKEND_SERVER_NAME" \
--query "Reservations[].Instances[].InstanceId" \
--output text \
--region $REGION \
--no-cli-pager)
echo "Backend Server Instance: $BACKEND_INSTANCE_ID"BUILD_SG_IDS=$(aws ec2 describe-instances \
--instance-ids $BUILD_INSTANCE_ID \
--query "Reservations[].Instances[].SecurityGroups[].GroupId" \
--output text \
--region $REGION \
--no-cli-pager)
echo "Build Server SGs: $BUILD_SG_IDS"
BACKEND_SG_IDS=$(aws ec2 describe-instances \
--instance-ids $BACKEND_INSTANCE_ID \
--query "Reservations[].Instances[].SecurityGroups[].GroupId" \
--output text \
--region $REGION \
--no-cli-pager)
echo "Backend Server SGs: $BACKEND_SG_IDS"aws ec2 terminate-instances \
--instance-ids $BUILD_INSTANCE_ID $BACKEND_INSTANCE_ID \
--region $REGION \
--no-cli-pagerWait until the instances are fully terminated and the command finishes executing.
aws ec2 wait instance-terminated \
--instance-ids $BUILD_INSTANCE_ID $BACKEND_INSTANCE_ID \
--region $REGION \
--no-cli-pagerfor sg in $BUILD_SG_IDS $BACKEND_SG_IDS; do
echo "Deleting SG: $sg"
aws ec2 delete-security-group \
--group-id "$sg" \
--region $REGION \
--no-cli-pager
doneaws ec2 delete-key-pair \
--key-name "$BUILD_KEY_PAIR" \
--region $REGION \
--no-cli-pageraws s3 rm s3://$FRONTEND_BUCKET --recursive --no-cli-pager
aws s3 rb s3://$FRONTEND_BUCKET --force --no-cli-pageraws dynamodb delete-table \
--table-name $DDB_ASSIGN_TABLE \
--region $REGION \
--no-cli-pager
aws dynamodb delete-table \
--table-name $DDB_SUBMIT_TABLE \
--region $REGION \
--no-cli-pageraws iam list-attached-role-policies \
--role-name $BACKEND_SERVER_ROLE \
--no-cli-pagerFor example:
aws iam detach-role-policy \
--role-name $BACKEND_SERVER_ROLE \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess \
--no-cli-pager
aws iam detach-role-policy \
--role-name $BACKEND_SERVER_ROLE \
--policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess \
--no-cli-pager(These two are the only policies you attached.)
aws iam remove-role-from-instance-profile \
--instance-profile-name $BACKEND_INSTANCE_PROFILE \
--role-name $BACKEND_SERVER_ROLE \
--no-cli-pageraws iam delete-instance-profile \
--instance-profile-name $BACKEND_INSTANCE_PROFILE \
--no-cli-pageraws iam delete-role \
--role-name $BACKEND_SERVER_ROLE \
--no-cli-pageraws iam list-attached-role-policies \
--role-name $BUILD_SERVER_ROLE \
--no-cli-pageraws iam detach-role-policy \
--role-name $BUILD_SERVER_ROLE \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess \
--no-cli-pageraws iam remove-role-from-instance-profile \
--instance-profile-name $BUILD_INSTANCE_PROFILE \
--role-name $BUILD_SERVER_ROLE \
--no-cli-pager aws iam delete-instance-profile \
--instance-profile-name $BUILD_INSTANCE_PROFILE \
--no-cli-pageraws iam delete-role \
--role-name $BUILD_SERVER_ROLE \
--no-cli-pagerAs you created the frontend and backend repositories only for this workshop, you can delete them from the GitHub Console if you want to.
-
Open GitHub and go to the repository you want to delete (e.g.,
ci-cd-workshop-frontendorci-cd-workshop-backend). -
Click Settings (top menu of the repository).
-
Scroll to the very bottom to the Danger Zone section.
-
Click Delete this repository.
-
GitHub will ask you to type the repository name for confirmation (example:
<user-name>/ci-cd-workshop-frontend). -
Click I understand the consequences, delete this repository.
You can repeat the same steps for both frontend and backend repos if you no longer need them.




























