A production-ready continuous integration and continuous deployment pipeline demonstrating automated Docker containerization and deployment using GitHub Actions self-hosted runner on AWS EC2.
- Overview
- Architecture
- Prerequisites
- Infrastructure Setup
- Project Structure
- Application Files
- Deployment
- Verification
- Workflow Explanation
- Security Considerations
This project implements an enterprise-grade automated deployment pipeline that demonstrates modern DevOps practices. Every code push triggers a complete CI/CD workflow that builds a Docker container, pushes it to Docker Hub, and deploys it to an AWS EC2 instance using a GitHub Actions self-hosted runner. The solution showcases infrastructure automation, containerization, and continuous deployment in a real-world cloud environment.
- Automated build and deployment on every git push
- Docker containerization for consistent environments
- Self-hosted GitHub Actions runner on EC2
- Zero-downtime deployment with container orchestration
- Secure credential management with GitHub Secrets
- Production-ready Flask web application
The pipeline consists of the following components:
- GitHub Actions: Orchestrates the CI/CD workflow
- Self-Hosted Runner: Executes workflows directly on the EC2 instance
- Docker: Containerizes the application for consistent deployments
- Docker Hub: Stores and distributes container images
- AWS EC2: Hosts both the runner and the deployed application
- Flask Application: Lightweight Python web application
- AWS Account with EC2 access
- GitHub Account
- Docker Hub Account
- Basic knowledge of Git, Docker, and AWS
Launch an EC2 instance with the following specifications:
- Operating System: Ubuntu 22.04 LTS
- Instance Type: t2.micro or t3.micro
- Security Group Rules:
- SSH (Port 22): For remote access
- HTTP (Port 80): For application access
Connect to your EC2 instance via SSH and execute the following commands:
sudo apt update
sudo apt install docker.io -y
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker ubuntuLog out and log back in for group changes to take effect:
exit
# Reconnect via SSH
docker psNavigate to your GitHub repository and configure the following secrets:
- Go to Settings β Secrets and variables β Actions
- Click New repository secret
- Add the following secrets:
| Secret Name | Value |
|---|---|
| DOCKER_USERNAME | Your Docker Hub username |
| DOCKER_PASSWORD | Your Docker Hub access token |
- In your GitHub repository, navigate to Settings β Actions β Runners
- Click New self-hosted runner
- Select Linux and x64 architecture
- Copy and execute the provided installation commands on your EC2 instance
- Start the runner service
.
βββ app.py
βββ requirements.txt
βββ Dockerfile
βββ .github/
βββ workflows/
βββ deploy.yml
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello from Flask running in Docker!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)flask
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]Create .github/workflows/deploy.yml:
name: Self-Hosted-Ubuntu-Runner-DockerHub
on:
push:
jobs:
build:
runs-on: self-hosted
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build Docker image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/flask-app:latest .
- name: Push Docker image
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/flask-app:latest
- name: Stop old container
run: |
docker stop flask-app || true
docker rm flask-app || true
- name: Run new container
run: |
docker run -d -p 80:5000 --name flask-app \
${{ secrets.DOCKER_USERNAME }}/flask-app:latest
- Initialize Git repository (if not already done):
git init
git remote add origin <your-repository-url>- Commit and push your code:
git add .
git commit -m "Add automated deployment pipeline"
git push origin main- Navigate to your repository's Actions tab
- Verify the workflow executed successfully
- Confirm the runner is listed as "self-hosted"
Execute the following commands on your EC2 instance:
docker images
docker psExpected output should show:
- Image:
<dockerhub-username>/flask-app:latest - Running container named
flask-app
Open your web browser and navigate to:
http://<EC2_PUBLIC_IP>
You should see: "Hello from Flask running in Docker!"
The CI/CD pipeline executes the following steps on every push:
- Code Checkout: Retrieves the latest code from the repository
- Docker Hub Authentication: Logs into Docker Hub using stored secrets
- Image Build: Creates a new Docker image from the Dockerfile
- Image Push: Uploads the image to Docker Hub repository
- Container Cleanup: Stops and removes the previous container
- Container Deployment: Launches a new container with the updated image
- Docker Hub credentials are stored as GitHub encrypted secrets
- Never commit sensitive credentials to the repository
- Stop EC2 when not in use: Stop your instance when you're done testing to avoid unnecessary charges :)
