A production-style DevOps project demonstrating automated CI/CD, Infrastructure as Code, security scanning, monitoring, deployment, health checks, and automatic rollback on AWS.
The project uses GitHub Actions with AWS OpenID Connect (OIDC), eliminating the need to store long-lived AWS credentials in GitHub.
The environment includes application CI/CD, Terraform infrastructure automation, Amazon ECR, EC2, AWS Systems Manager, CloudWatch monitoring, SNS alerting, and S3 remote Terraform state.
flowchart TD
DEV[Developer] --> FB[Feature Branch]
FB --> PR[Pull Request]
PR --> TEST[Pytest]
PR --> TRIVY[Trivy Security Scan]
PR --> TFPLAN[Terraform Validate & Plan]
TEST --> MAIN[Protected Main Branch]
TRIVY --> MAIN
TFPLAN --> MAIN
MAIN --> APP[Application CI/CD]
MAIN --> TFAPPLY[Terraform Apply]
APP --> OIDC1[GitHub OIDC]
OIDC1 --> ECR[Amazon ECR]
ECR --> SSM[AWS Systems Manager]
SSM --> EC2[Amazon EC2]
TFAPPLY --> PROD[Production Environment]
PROD --> OIDC2[GitHub OIDC]
OIDC2 --> AWS[AWS Infrastructure]
TFPLAN --> STATE[(S3 Terraform State)]
TFAPPLY --> STATE
EC2 --> CW[Amazon CloudWatch]
CW --> SNS[Amazon SNS]
- Amazon EC2
- Amazon ECR
- AWS IAM
- AWS Systems Manager
- Amazon CloudWatch
- CloudWatch Logs
- Amazon SNS
- Amazon S3
- Terraform
- S3 Remote State
- GitHub Actions
- GitHub OIDC
- GitHub Environments
- Protected branches
- Pull Request checks
- Docker
- Python
- Flask
- Gunicorn
- Nginx
- Pytest
- Trivy vulnerability scanner
The project contains a small Flask backend used to demonstrate the complete CI/CD lifecycle.
The application exposes:
/
which returns application information and:
/health
which is used by the deployment pipeline to verify application health.
Example health response:
{
"status": "healthy"
}The application runs inside a Docker container on Amazon EC2.
Host port 3001
↓
Docker container
↓
Flask / Gunicorn
↓
Port 3000
Application deployments are automated using GitHub Actions.
A push to the protected main branch starts the deployment pipeline.
Push / Merge to main
↓
Automated Tests
↓
Docker Build
↓
Package Verification
↓
Trivy Security Scan
↓
Amazon ECR
↓
AWS Systems Manager
↓
Amazon EC2
↓
Health Check
↓
Deployment Success / Rollback
Docker images are tagged using the Git commit SHA, providing traceability between source code and deployed containers.
The pipeline also publishes a latest image tag.
Changes are developed on feature branches and submitted through pull requests.
Before code can be merged into main, GitHub runs required checks.
Pytest validates application functionality before deployment.
Current tests verify:
/health- application root endpoint
- expected application metadata
A failing test prevents the change from reaching the deployment pipeline.
The application Docker image is scanned using Trivy.
The pipeline checks:
CRITICAL
HIGH
vulnerabilities across:
OS packages
Library dependencies
The workflow fails when unresolved HIGH or CRITICAL vulnerabilities are detected.
This prevents vulnerable images from being pushed into the deployment process.
The main branch is protected using GitHub repository rules.
Configured protections include:
- Pull request required before merge
- Required status checks
- Branch deletion protection
- Force push protection
- Squash merge workflow
Required application checks include:
Automated Tests
Docker Security Scan
Terraform Validate
This ensures changes cannot bypass the CI and security pipeline.
GitHub Actions authenticates with AWS using OpenID Connect.
No permanent AWS access keys are stored in GitHub Secrets.
The authentication flow is:
GitHub Actions
↓
OIDC Token
↓
AWS STS
↓
IAM Role
↓
Temporary AWS Credentials
Separate IAM roles are used for different responsibilities.
Examples include:
GitHubActions-ECR-aws-cicd-devops-lab
GitHubActions-TerraformPlan-aws-cicd-devops-lab
GitHubActions-TerraformApply-aws-cicd-devops-lab
This separates application deployment, Terraform planning, and infrastructure modification permissions.
Application deployment is performed using AWS Systems Manager instead of direct SSH access from GitHub Actions.
GitHub Actions sends deployment commands to the EC2 instance using SSM.
The deployment process:
GitHub Actions
↓
AWS OIDC
↓
AWS Systems Manager
↓
EC2
↓
Pull Docker image from ECR
↓
Start container
↓
Health check
The deployed container is:
cicd-backend
with port mapping:
3001:3000
The deployment pipeline performs an application health check after starting the new Docker image.
It checks:
http://localhost:3001/health
If the new application responds successfully, the deployment completes.
If the health check fails, the pipeline automatically restores the previously deployed Docker image.
Deploy new image
↓
Health Check
/ \
OK FAIL
↓ ↓
Success Stop failed container
↓
Previous image
↓
Restart container
↓
Rollback
Rollback behavior was intentionally tested by deploying an unhealthy application version.
The pipeline failed as expected while the previous healthy application remained available.
The AWS infrastructure used by this project is managed with Terraform.
Terraform configuration covers resources including:
- EC2 instance
- Security Group
- IAM role
- IAM instance profile
- Amazon ECR repository
- CloudWatch log groups
- CloudWatch alarms
- CloudWatch dashboard
- Amazon SNS topic
Terraform state is stored remotely in Amazon S3.
aws-cicd-devops-lab-terraform-state-303974373642
Remote state allows Terraform to maintain a consistent infrastructure state between local development and GitHub Actions.
This replaces reliance on a local terraform.tfstate file for CI/CD operations.
Terraform changes are validated automatically during pull requests.
The Terraform CI workflow performs:
terraform fmt -check
↓
AWS OIDC Authentication
↓
terraform init
↓
terraform validate
↓
terraform plan
Terraform Plan uses the dedicated IAM role:
GitHubActions-TerraformPlan-aws-cicd-devops-lab
The Plan role is designed for infrastructure inspection and Terraform planning without providing the permissions used by the Apply workflow to modify infrastructure.
This allows infrastructure changes to be reviewed before they are applied.
Infrastructure changes are applied only after changes reach the protected main branch.
The Terraform Apply workflow uses the GitHub:
production
environment.
The workflow performs:
Merge to main
↓
Terraform Apply workflow
↓
GitHub Production Environment
↓
GitHub OIDC
↓
Terraform Apply IAM Role
↓
terraform init
↓
terraform validate
↓
terraform plan
↓
terraform apply
↓
AWS Infrastructure
The Apply workflow uses:
GitHubActions-TerraformApply-aws-cicd-devops-lab
This role is separate from the Terraform Plan role.
Infrastructure changes follow a pull-request-based workflow.
- Terraform code is modified on a feature branch.
- A pull request is opened against
main. - Application tests run.
- Docker security scanning runs.
- Terraform formatting is checked.
- Terraform configuration is validated.
- Terraform generates an execution plan.
- Required GitHub checks must pass.
- The pull request is squash-merged.
- Terraform Apply runs from
main. - AWS infrastructure is updated.
A successfully tested infrastructure change produced:
Plan: 0 to add, 1 to change, 0 to destroy.
followed by:
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
The existing EC2 instance was updated in-place without replacing or destroying the resource.
Amazon CloudWatch provides monitoring and observability for the environment.
The EC2 instance uses the CloudWatch Agent to collect additional system metrics and logs.
Monitoring includes:
- CPU utilization
- Memory utilization
- Disk utilization
- Network traffic
- EC2 status checks
- Nginx access logs
- Nginx error logs
- Nginx latency logs
- HTTP 4xx errors
- HTTP 5xx errors
- Request rate
- Response time
Nginx logs are sent to dedicated CloudWatch Log Groups:
/devops-lab/nginx/access
/devops-lab/nginx/error
/devops-lab/nginx/latency
CloudWatch Logs Insights is used to analyze application and proxy traffic.
Examples include:
- Top requested URLs
- HTTP status codes
- HTTP status classes
- Top external IPs causing errors
- Slowest Nginx URLs
- Nginx response time
- Request rate
- HTTP methods
- 5xx errors over time
The project includes a centralized CloudWatch dashboard:
devops-lab-dashboard
The dashboard provides visibility into infrastructure health and application traffic.
Metrics include:
CPU Utilization
Memory Used %
Disk Used %
Network Traffic
EC2 Status Check
Nginx 4xx Errors
Nginx 5xx Errors
Nginx Response Time
CloudWatch alarms monitor infrastructure and application health.
Configured alarms include:
devops-lab-ec2-status-check
devops-lab-high-cpu
devops-lab-high-memory
devops-lab-high-disk
devops-lab-nginx-4xx
devops-lab-nginx-5xx
Amazon SNS is used to deliver alarm notifications.
The environment was intentionally tested using failure scenarios.
The backend application was intentionally stopped to reproduce:
502 Bad Gateway
The incident was visible through Nginx and CloudWatch monitoring.
After restoring the backend, successful HTTP responses were confirmed.
A test expectation was intentionally changed to an invalid application version.
Pytest failed as expected and GitHub Actions prevented deployment.
This demonstrated that failing application tests block releases.
Trivy was configured to fail the CI pipeline when HIGH or CRITICAL vulnerabilities were detected.
Package and container security issues were investigated and resolved before deployment was allowed.
An unhealthy application deployment was intentionally triggered.
The health check failed and the deployment workflow restored the previous healthy Docker image.
This verified automatic rollback behavior.
The project applies several security practices:
- AWS authentication through GitHub OIDC
- No long-lived AWS access keys in GitHub
- Dedicated IAM roles
- Separate Terraform Plan and Apply permissions
- Protected
mainbranch - Required pull request checks
- Trivy container vulnerability scanning
- HIGH and CRITICAL vulnerability enforcement
- Automated application testing
- SSM-based deployment
- Docker image SHA tagging
- Health checks
- Automatic rollback
- S3-backed Terraform state
aws-cicd-devops-lab/
│
├── .github/
│ └── workflows/
│ ├── deploy.yml
│ ├── pr-check.yml
│ ├── terraform.yml
│ └── terraform-apply.yml
│
├── app/
│ ├── app.py
│ └── requirements.txt
│
├── nginx/
│ └── default.conf
│
├── terraform/
│ ├── backend.tf
│ └── ...
│
├── test/
│ └── test_app.py
│
├── compose.yaml
├── Dockerfile
└── README.md
This lab demonstrates practical experience with:
- AWS cloud infrastructure
- Infrastructure as Code
- Terraform
- CI/CD architecture
- GitHub Actions
- AWS OIDC federation
- IAM roles and permissions
- Docker containerization
- Amazon ECR
- Amazon EC2
- AWS Systems Manager
- Automated testing
- Container security scanning
- Pull request quality gates
- Protected production branches
- Automated deployment
- Health checks
- Automatic rollback
- CloudWatch monitoring
- Centralized logging
- Infrastructure alerting
- Remote Terraform state
- Incident simulation and recovery
The project is designed to reproduce a practical production-style DevOps workflow rather than a simple deployment demo.