Introduction
Continuous Integration and Continuous Deployment (CI/CD) are essential for modern software development. In this tutorial, I will walk you through setting up a CI/CD pipeline for a Flask application using GitHub Actions and Docker. By the end of this guide, you will have an automated workflow that builds, tests, and pushes a Docker image to Docker Hub.
⚙️ Prerequisites
- Python 3.x
- pip
- Docker
- GitHub Repository
- Docker Hub Account
- AWS EC2 Instance with Docker installed
- SSH Access to the EC2 instance
Step 1: Setting Up the Flask Application
Let's start by creating a simple Flask application.
-
Create a project directory:
mkdir flask-cicd && cd flask-cicd
-
Create a virtual environment and activate it:
python3 -m venv venv source venv/bin/activate # On macOS/Linux venv\Scripts\activate # On Windows
-
Install Flask:
pip install flask
-
Create an
app.py
file:
from flask import Flask app = Flask(__name__) @app.route('/') def home(): return "Hello, Flask CI/CD!" if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)
-
Create a
requirements.txt
file:
flask
Step 2: Dockerizing the Flask Application
Create a Dockerfile
in the project directory:
# Use official Python image
FROM python:3.10
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
To test the Docker container locally, build and run it:
docker build -t flask-cicd .
docker run -p 5000:5000 flask-cicd
Step 3: Setting Up GitHub Actions for CI/CD
Now, we will create a GitHub Actions workflow to automate building and pushing our Docker image to Docker Hub.
🔄 CI/CD Pipeline (GitHub Actions)
✅ CI — Continuous Integration
GitHub Actions automatically runs the following on each push to main:
Clone repository
Set up Python and install dependencies
Run unit tests
Build Docker image
Push image to Docker Hub
🚀 CD — Continuous Deployment to EC2
After a successful Docker push, a second GitHub Actions job connects to your EC2 server via SSH and:
Logs into Docker (if private image)
Pulls the latest image from Docker Hub
Stops and removes the old container
Runs the updated container
This enables automatic deployment of your latest code to a live server!
- Inside your project, create
.github/workflows/main.yml
and add the following:
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Python application
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and Push Docker Image
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile # Ensure this path is correct
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/practice:latest
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy on EC2 via SSH
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker pull ${{ secrets.DOCKER_USERNAME}}/practice:latest
docker stop flask-app || true
docker rm flask-app || true
docker run -d \
--name flask-app \
-p 5000:5000 \
${{ secrets.DOCKER_USERNAME}}/practice:latest
Once pushed, GitHub Actions will automatically run the workflow. You can check the status under the Actions tab in your repository.
🔐 GitHub Secrets Configuration
Remember to add the appropriate secrets to your github secret, go to Actions>secrets and add the following with correct values
Secret Name | Purpose |
---|---|
DOCKER_USERNAME |
Docker Hub username |
DOCKER_PASSWORD |
Docker Hub password/token |
EC2_HOST |
Public IP or DNS of your EC2 |
EC2_USER |
SSH username (e.g., ubuntu ) |
EC2_SSH_KEY |
Private SSH key for EC2 access |
Conclusion
Congratulations! 🎉 You have successfully set up a CI/CD pipeline for your Flask application using GitHub Actions and Docker. Now, every time you push code, your application will be tested, built, and deployed automatically.
Feel free to share your thoughts and improvements in the comments below! 🚀
Top comments (0)