A CI/CD pipeline I built on GitHub Actions — every push to main runs tests, builds a Docker image, scans it with Trivy for vulnerabilities, and publishes the front-end to GitHub Pages. The Docker + Trivy stage validates the containerized build is production-safe even though the current live demo is served statically via Pages.
🔗 Repo: github-action-zero-to-hero
| Category | Tools |
|---|---|
| Application | Python, Flask |
| CI/CD | GitHub Actions |
| Containers | Docker, Docker Hub |
| Security Scanning | Trivy (image vulnerability scanning) |
| Hosting | GitHub Pages |
| OS / Scripting | Linux, Bash |
| Version Control | Git, GitHub |
Developer pushes code
↓
GitHub Actions triggered
↓
Run tests + lint checks
↓
Build Docker image
↓
Trivy scans image — fails pipeline on critical/high CVEs
↓
Push image to Docker Hub
↓
SSH into AWS EC2 and pull + run the new image
↓
Live application
GitHub Repository
↓
GitHub Actions Runner
↓
CI Stage — Test + Lint
↓
Docker Build
↓
Trivy Security Scan
↓
Docker Hub Registry
↓
AWS EC2 (pull + restart container)
↓
Live Application
github-action-zero-to-hero/
├── .github/
│ └── workflows/ # CI/CD pipeline: test → build → scan → push → deploy
├── templates/ # HTML templates for the Flask app
├── app.py # Flask application entrypoint
├── index.html
├── Dockerfile
├── requirements.txt
└── README.md
name: CI/CD Pipeline
on:
push:
branches: [main]
jobs:
build-scan-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/app:${{ github.sha }} .
- name: Scan image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ secrets.DOCKERHUB_USERNAME }}/app:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
- name: Push to Docker Hub
run: |
echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
docker push ${{ secrets.DOCKERHUB_USERNAME }}/app:${{ github.sha }}
- name: Deploy to EC2
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/app:${{ github.sha }}
docker stop app || true && docker rm app || true
docker run -d --name app -p 80:8080 ${{ secrets.DOCKERHUB_USERNAME }}/app:${{ github.sha }}- Trivy blocks the pipeline on any CRITICAL or HIGH severity CVE found in the built image — vulnerable images never reach Docker Hub or EC2
- Docker Hub and EC2 credentials stored as encrypted GitHub Actions secrets, never hardcoded
- EC2 access restricted to SSH key-based auth via GitHub Actions secrets
# clone the repo
git clone https://github.com/aniket-devop/github-action-zero-to-hero.git
cd github-action-zero-to-hero
# run the Flask app locally
pip install -r requirements.txt
python app.py
# to trigger the full pipeline, add these as GitHub repo secrets first:
# DOCKERHUB_USERNAME, DOCKERHUB_TOKEN, EC2_HOST, EC2_USER, EC2_SSH_KEY
# then push to main — the pipeline runs automatically
git push origin main- Writing and debugging multi-stage GitHub Actions workflows
- Integrating Trivy as a hard security gate rather than an advisory scan
- Managing secrets and SSH-based deployment to a cloud VM
- End-to-end ownership of a commit-to-production pipeline, not just the CI half
- Replace SSH-based EC2 deploy with a blue-green or rolling deployment strategy
- Add SonarQube for code quality gating alongside Trivy's security gate
- Move from a single EC2 instance to an Auto Scaling Group behind a load balancer
If this project helped you understand GitHub Actions CI/CD, consider giving it a ⭐ on GitHub.