Automated Deployments for Python Apps with GitHub Actions
Automated deployments transform your CI/CD pipeline from testing to live users. When tests pass on main, your workflow automatically deploys the latest code to staging or production, eliminating manual steps, reducing human error, and accelerating time-to-value.
This guide covers deploying Python web applications via SSH, containerized deployments with Docker, and cloud-native deployments to platforms like Heroku, AWS, and Google Cloud. You'll learn how to validate deployments, configure staging/production environments, and safely roll back on failure.
Deploying via SSH
SSH deployments pull the latest code from your repository and restart your application. This works for Python web apps running on VPS or dedicated servers.
First, add your server's private SSH key to GitHub Actions secrets as DEPLOY_KEY:
name: Deploy Python Web App
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install and test
run: |
pip install -r requirements.txt
pytest tests/
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd /var/www/myapp
git pull origin main
pip install -r requirements.txt
systemctl restart myapp
This workflow:
- Runs tests on push to
main - If tests pass, deploys by pulling code and restarting the service
- Uses the
appleboy/ssh-actionaction to execute commands remotely
The server needs systemctl to manage the service. For other server types (gunicorn, FastAPI), adjust the restart command.
Docker-Based Deployments
Docker containers package your Python app with all dependencies. Build and push a Docker image to a container registry, then pull and run it on your deployment platform.
Create a Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Then deploy in your workflow:
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
docker pull ghcr.io/myuser/myapp:latest
docker stop myapp || true
docker run -d --name myapp -p 8000:8000 \
ghcr.io/myuser/myapp:latest
This builds a Docker image, pushes it to GitHub Container Registry (GHCR), then pulls and runs it on your server.
Deploying to Heroku
Heroku is a PaaS (Platform as a Service) that handles infrastructure for you. Deploying is as simple as pushing to a Heroku-managed git remote:
- name: Deploy to Heroku
uses: akhileshns/heroku-[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: ${{ secrets.HEROKU_APP_NAME }}
heroku_email: ${{ secrets.HEROKU_EMAIL }}
Create a Heroku account, create an app, and generate an API key. Add the key as a GitHub secret. Heroku automatically runs pip install -r requirements.txt and starts your app according to Procfile:
web: python -m gunicorn main:app
Deploying to AWS
AWS Lambda runs Python functions serverlessly (pay per invocation, no servers to manage). Deploy using serverless framework:
- name: Deploy to AWS Lambda
uses: serverless/github-action@v3
with:
args: deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: us-east-1
Configure serverless.yml:
service: myapp
provider:
name: aws
runtime: python3.11
region: us-east-1
functions:
api:
handler: main.handler
events:
- http:
path: api/{proxy+}
method: ANY
This packages your Python app and deploys to Lambda, creating an HTTPS endpoint automatically.
Health Checks and Rollback
Always verify deployments succeeded:
- name: Health check
run: |
sleep 10 # Wait for deployment
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://myapp.example.com)
if [ "$HTTP_CODE" != "200" ]; then
echo "Health check failed (HTTP $HTTP_CODE)"
exit 1
fi
If health checks fail, trigger a rollback:
- name: Rollback on failure
if: failure()
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd /var/www/myapp
git checkout main~1
systemctl restart myapp
This reverts to the previous commit if deployment fails.
Environment-Specific Deployments
Use conditional logic to deploy to staging on PR, production on main:
deploy-staging:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Deploy to staging
run: |
curl -X POST https://staging-server.example.com/deploy
deploy-production:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: |
curl -X POST https://production-server.example.com/deploy
This pattern ensures pull requests deploy to staging for testing before merging to main, which triggers production deployment.
Key Takeaways
- Use SSH deployments for simple VPS; pull code and restart the service.
- Docker deployments are reproducible; build once, run anywhere.
- Heroku simplifies Python app deployment; create an app and configure Procfile.
- AWS Lambda runs serverless Python functions; pay only for execution time.
- Always include health checks to verify deployment success.
- Implement rollback mechanisms to revert failed deployments automatically.
- Use conditional logic to deploy staging on PR, production on main.
Frequently Asked Questions
What happens if my deployment fails mid-way?
If a step fails, the workflow stops and alerts you (red X on the commit). Implement health checks and rollback logic to automatically revert on failure. For critical systems, require manual approval before production deploys.
How do I deploy without downtime?
Use blue-green deployments: run two identical environments (blue and green), switch traffic from one to the other. Only applicable to load-balanced systems. For simple apps, accept brief downtime during restart.
Can I deploy to multiple servers in parallel?
Yes, use a matrix to deploy to multiple servers simultaneously:
deploy:
strategy:
matrix:
server: [server1, server2, server3]
steps:
- uses: appleboy/ssh-action@v1
with:
host: ${{ matrix.server }}
Should I deploy on every push or only on tags?
Deploy main branch to staging continuously; production on version tags. This gives you staging testing before production release.
How do I handle database migrations in deployments?
Run migrations before restarting:
- name: Run migrations
run: python manage.py migrate
- name: Restart service
run: systemctl restart myapp
Test migrations thoroughly; a failed migration can break production.