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
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
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)
Create a requirements file:
Flask==2.3.3
gunicorn==21.2.0
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 }}
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
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
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
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
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;
}
}
This configuration routes incoming requests through Nginx to your Flask application running on port 5000.
Deploy the Application
Set Up CodeDeploy Application
- Create a CodeDeploy application in the AWS Console
- Create a deployment group targeting your EC2 instances
- 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:
- Install Python dependencies
- Run tests (if present)
- Trigger AWS CodeDeploy
- 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
, not127.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)