Django CI/CD Pipeline with GitHub Actions

Django CI/CD Pipeline with GitHub Actions

Django CI/CD Pipeline with GitHub Actions

Manually deploying your Django project every time you make a change slows down your workflow and increases the risk of mistakes.

A CI/CD pipeline (Continuous Integration and Continuous Deployment) ensures that your code is tested, built, and deployed automatically every time you push to your main branch.

This tutorial shows how to build a full pipeline for your Django app using GitHub Actions and Fly.io.

๐Ÿš€ What Youโ€™ll Learn

  • Automate testing and migrations
  • Build Docker images for Django
  • Deploy automatically to Fly.io
  • Use GitHub Secrets to store environment variables

๐Ÿงฉ Prerequisites

Before we begin, make sure you have:

  • A Django app (preferably containerized with Docker)
  • A Fly.io account and working app (fly.toml already created)
  • GitHub repo connected to your project

๐Ÿณ Step 1: Dockerize Your Django App

If you donโ€™t already have a Dockerfile, hereโ€™s a minimal example:

# Dockerfile
FROM python:3.11-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
CMD ["gunicorn", "projectname.wsgi:application", "--bind", "0.0.0.0:8000"]

and .dockerignore:

__pycache__
*.pyc
.env
.git

Build and test locally:

docker build -t myapp .
docker run -p 8000:8000 myapp

โš™๏ธ Step 2: Configure Fly.io for Deploys

Initialize your app if you havenโ€™t yet:

fly launch

This creates a fly.toml configuration file that looks like this:

app = "your-app-name"

[build]
  image = "registry.fly.io/your-app-name:latest"

[env]
  DJANGO_SETTINGS_MODULE = "projectname.settings"
  PORT = "8000"

๐Ÿ”‘ Step 3: Add Secrets to Fly.io

Store production secrets on Fly.io (so theyโ€™re not in your repo):

fly secrets set DJANGO_SECRET_KEY='supersecret'
fly secrets set DEBUG='False'
fly secrets set DATABASE_URL='postgresql://user:password@host:5432/dbname'

๐Ÿง  Step 4: Add GitHub Secrets

In your GitHub repo:

  1. Go to Settings โ†’ Secrets and variables โ†’ Actions
  2. Add the following secrets:
NameDescription
FLY_API_TOKENYour Fly.io API token (fly auth token)
DJANGO_SECRET_KEYDjangoโ€™s secret key
DATABASE_URLConnection string to production DB
DEBUGSet to False

โšก Step 5: Create the GitHub Actions Workflow

Create a new workflow file:

.github/workflows/deploy.yml
name: Deploy Django to Fly.io

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install flake8 black
          pip install -r requirements.txt

      - name: Run code quality checks
        run: |
          black --check .
          flake8 .

      - name: Run Django tests
        run: |
          python manage.py collectstatic --noinput
          python manage.py test

      - name: Set up Fly.io
        uses: superfly/flyctl-actions/setup-flyctl@master

      - name: Deploy to Fly.io
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
        run: |
          flyctl deploy --remote-only

๐Ÿงช Step 6: Trigger the Workflow

Push a commit to your main branch:

git add .
git commit -m "Enable GitHub Actions deploy"
git push origin main

Go to your GitHub repo โ†’ Actions tab โ†’ watch the deployment run.

Once complete, Fly.io will automatically build and deploy your app. ๐ŸŽ‰

๐Ÿงฐ Step 7: Add Automatic Database Migrations

You can chain migrations to run right after deploy.

Add this job after deployment in deploy.yml:

  run-migrations:
    needs: deploy
    runs-on: ubuntu-latest
    steps:
      - name: Set up Fly.io
        uses: superfly/flyctl-actions/setup-flyctl@master
      - name: Run Django migrations
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
        run: |
          flyctl ssh console -C "python manage.py migrate"

Now, every deploy will:

  • Test your code
  • Build your Docker image
  • Push to Fly.io
  • Run migrations automatically

๐Ÿ” Step 8: Optional โ€” Staging Environment

If you want a staging app:

fly apps create myapp-staging
fly launch --name myapp-staging

Then duplicate your workflow file and deploy from a staging branch instead of main.

on:
  push:
    branches: [staging]

You can even use separate secrets (FLY_API_TOKEN_STAGING, etc.).

๐Ÿงญ Step 9: CI Enhancements

Add more checks as your app grows:

  • pytest-django for unit tests
  • Bandit for security scans
  • Black + Flake8 for linting
  • isort for import ordering

Example:

pip install pytest pytest-django bandit
pytest
bandit -r .

๐ŸŒ Step 10: Monitor and Roll Back

Fly.io keeps image versions automatically.
To rollback to a previous version:

fly releases
fly deploy --image <previous_image>

You can also view logs in real time:

fly logs

๐Ÿ Conclusion

You now have a production-ready Django CI/CD pipeline:

โœ… Automated testing and code checks
โœ… Zero-downtime Docker deploys
โœ… Environment secrets stored securely
โœ… Optional staging + rollback

With this workflow, your Fly.io apps deploy themselves โ€” every push ships production code.

Written by Bailey Burnsed โ€” Senior Software Engineer, Founder of BaileyBurnsed.dev