DEV Community

Cover image for How to Build a Production Flask API CI/CD Pipeline on AWS with GitHub Actions

How to Build a Production Flask API CI/CD Pipeline on AWS with GitHub Actions

When I built my first Flask API, I wanted to automate everything, tests, builds, deployments.

But Jenkins was overwhelming.

Too many plugins. Too much setup. Too much YAML.

When you’re building your first production CI/CD pipeline, sometimes the simplest path forward is the best one.

That’s why I built my pipeline using GitHub Actions + AWS CodeDeploy, no Jenkins, no headaches.

This guide demonstrates how to build a fully functional Flask API with complete AWS deployment automation using a hybrid approach, AWS native deployment tools combined with GitHub Actions for the build pipeline. You will learn to set up automated testing, deployment, and production-ready infrastructure that scales efficiently.

Prerequisites

Before following this guide, ensure you have:

  • An AWS account with administrative access
  • A GitHub account for repository hosting
  • Basic knowledge of Git, AWS Console, and command line tools
  • Python 3.9 or later installed locally

Understanding the Architecture

The pipeline uses a hybrid approach that combines GitHub Actions for building and testing with AWS services for deployment:

GitHub Repository → GitHub Actions (Build/Test) → AWS CodeDeploy → EC2 Instance
Enter fullscreen mode Exit fullscreen mode

This architecture provides GitHub's reliable build environment while leveraging AWS native deployment tools for production infrastructure.

Set Up IAM Permissions

Proper IAM configuration prevents most AWS deployment failures. Create an IAM user with these managed policies:

  • AWSCodeCommitFullAccess
  • AWSCodeDeployFullAccess
  • AWSCodePipelineFullAccess
  • AmazonEC2FullAccess

Configure Git credentials for CodeCommit:

$ aws configure set region us-east-1
$ git config --global credential.helper '!aws codecommit credential-helper $@'
$ git config --global credential.UseHttpPath true
Enter fullscreen mode Exit fullscreen mode

Create the Flask Application

Build a production-ready Flask API with proper structure:

# app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/health')
def health():
    return jsonify({'status': 'healthy', 'version': '1.0.0'})

@app.route('/api/users')
def users():
    return jsonify({'users': [{'id': 1, 'name': 'John'}, {'id': 2, 'name': 'Jane'}]})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
Enter fullscreen mode Exit fullscreen mode

Create a requirements file:

Flask==2.3.3
gunicorn==21.2.0
Enter fullscreen mode Exit fullscreen mode

Configure GitHub Actions for CI/CD

Create the GitHub Actions workflow file:

# .github/workflows/deploy.yml
name: Deploy Flask API to AWS
on:
  push:
    branches: [main]

jobs:
  test-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'

      - name: Install dependencies and test
        run: |
          pip install -r requirements.txt
          python -m pytest tests/ || echo "No tests found, skipping"

      - name: Deploy to AWS CodeDeploy
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: us-east-1
        run: |
          aws deploy create-deployment \
            --application-name flask-api \
            --deployment-group-name production \
            --github-location repository=${{ github.repository }},commitId=${{ github.sha }}
Enter fullscreen mode Exit fullscreen mode

This workflow runs tests and triggers AWS CodeDeploy automatically on every push to the main branch.

Test your GitHub Actions workflow locally using act before pushing to catch configuration errors early and save debugging time.

Set Up AWS CodeDeploy Configuration

Create the application specification file:

# appspec.yml
version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/api
    overwrite: yes
permissions:
  - object: /var/www/api
    pattern: "**"
    owner: www-data
    group: www-data
    mode: 755
hooks:
  BeforeInstall:
    - location: scripts/install_dependencies.sh
      timeout: 300
      runas: root
  ApplicationStart:
    - location: scripts/start_server.sh
      timeout: 300
      runas: root
  ApplicationStop:
    - location: scripts/stop_server.sh
      timeout: 300
      runas: root
Enter fullscreen mode Exit fullscreen mode

Create Deployment Scripts

Install Dependencies Script

#!/bin/bash
# scripts/install_dependencies.sh
yum update -y
yum install -y python3 python3-pip nginx

# Install CodeDeploy agent
yum install -y ruby wget
cd /home/ec2-user
wget https://aws-codedeploy-us-east-1.s3.us-east-1.amazonaws.com/latest/install
chmod +x ./install
./install auto

# Install Python dependencies
cd /var/www/api
pip3 install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Start Server Script

#!/bin/bash
# scripts/start_server.sh
cd /var/www/api

# Start the API server
nohup python3 app.py > /var/log/api.log 2>&1 &
echo $! > /var/run/api.pid

# Configure and start nginx
cp nginx.conf /etc/nginx/conf.d/api.conf
systemctl start nginx
systemctl enable nginx
Enter fullscreen mode Exit fullscreen mode

Stop Server Script

#!/bin/bash
# scripts/stop_server.sh
# Stop the API server
if [ -f /var/run/api.pid ]; then
    kill $(cat /var/run/api.pid)
    rm /var/run/api.pid
fi

# Stop nginx
systemctl stop nginx
Enter fullscreen mode Exit fullscreen mode

Configure Nginx Reverse Proxy

Create the Nginx configuration:

# nginx.conf
server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
Enter fullscreen mode Exit fullscreen mode

This configuration routes incoming requests through Nginx to your Flask application running on port 5000.

Deploy the Application

Set Up CodeDeploy Application

  1. Create a CodeDeploy application in the AWS Console
  2. Create a deployment group targeting your EC2 instances
  3. Configure deployment settings for rolling deployments

Configure GitHub Secrets

Add these secrets to your GitHub repository:

  • AWS_ACCESS_KEY_ID: Your IAM user access key
  • AWS_SECRET_ACCESS_KEY: Your IAM user secret key

Test the Pipeline

Push your code to the main branch. GitHub Actions will automatically:

  1. Install Python dependencies
  2. Run tests (if present)
  3. Trigger AWS CodeDeploy
  4. Deploy the application to EC2

Troubleshoot Common Issues

GitHub Actions Failures

  • Verify AWS credentials in GitHub Secrets
  • Check IAM permissions for CodeDeploy access
  • Review GitHub Actions logs for specific error messages

CodeDeploy Failures

  • Ensure CodeDeploy agent is installed on EC2 instances
  • Verify file permissions in appspec.yml
  • Check CloudWatch logs for detailed deployment errors

Application Issues

  • Confirm Flask application listens on 0.0.0.0, not 127.0.0.1
  • Verify Nginx configuration syntax
  • Check security groups allow HTTP traffic on port 80

Monitor and Maintain the Pipeline

Use CloudWatch logs to monitor application performance and deployment status. Set up CloudWatch alarms for key metrics like response time and error rates.

Regular maintenance tasks include:

  • Updating dependencies in requirements.txt
  • Reviewing deployment logs for optimization opportunities
  • Testing deployment scripts on staging environments before production changes

Alternative: CodeBuild Integration

You can achieve similar results with AWS CodeBuild by following the CodeBuild documentation. The deployment scripts and architecture remain identical regardless of your build tool choice.

Cost Considerations

You can monitor and estimate costs using the AWS Pricing Calculator and track actual usage in the AWS Cost Explorer.

Free Tier Benefits

AWS Free Tier provides generous allowances for learning and small projects:

  • EC2: 750 hours per month of t2.micro instances for 12 months
  • CodeDeploy: Always free for EC2 deployments
  • Data Transfer: 15 GB of bandwidth out aggregated across all services
  • CloudWatch: 10 custom metrics and 10 alarms

Post-Free Tier Costs

After the free tier expires, expect monthly costs:

You can view detailed pricing for all AWS services at AWS Pricing. The free tier makes this approach highly cost-effective for learning, development, and small production workloads.

Conclusion

You have successfully built a production-ready CI/CD pipeline that combines GitHub Actions for reliable building and testing with AWS CodeDeploy for scalable deployment. This hybrid approach provides the best of both platforms: GitHub's developer-friendly build environment and AWS native deployment capabilities.

The pipeline automatically tests and deploys your Flask API whenever you push code changes, enabling rapid iteration while maintaining production stability. You can extend this foundation by adding multiple environments, implementing blue-green deployments, and integrating monitoring tools.

This approach demonstrates that effective CI/CD does not require complex tool chains. Focus on building working software that solves real problems, and choose tools based on your specific requirements rather than vendor lock-in considerations.

Top comments (0)