The fastest way to add continuous integration (CI) to a Django project today is GitHub Actions. Commit a single .github/workflows/ci.yml file and every push or pull request automatically spins up a real PostgreSQL database, installs your dependencies, runs your migrations and test suite, lints the code and reports coverage — across several Python versions at once.
Travis CI was the original go-to for open-source Django, but it is no longer recommended for new projects: travis-ci.org shut down in 2021 and the hosted service is now heavily restricted. The community has standardised on GitHub Actions, with GitLab CI/CD and CircleCI as the other mainstream choices. This guide shows the modern GitHub Actions setup first, then the legacy .travis.yml equivalent so you can migrate an older repo.
Key takeaways
- Use GitHub Actions for new Django projects — it is the de-facto default, lives in the same repo as your code, and needs no third-party signup.
- A good Django CI pipeline does five things: provisions a PostgreSQL service, installs deps, runs
migrate, runs the test suite, and reports lint + coverage. - Test across a Python version matrix (3.11, 3.12, 3.13) so you catch version-specific breakage before users do.
- Do not start a new project on Travis CI. Keep it only for legacy repos, and plan a migration to GitHub Actions or GitLab CI/CD.
- Coverage and linting belong in CI, not on a developer's laptop — upload results to Coveralls or Codecov on every run.
What is CI and why does a Django project need it?
Continuous integration runs an automated check on your code every time it changes, so a broken migration, a failing test or a style violation is caught in minutes instead of in production. For a Django app the highest-value checks are the database-backed test suite and your migration state — both of which need a real database, which is exactly what a CI service provisions for you on demand.
If your test suite is thin, fix that first: a CI pipeline only protects you as well as the tests it runs. Our guide to Django unit test cases with forms and views is a good place to begin, and our software testing services team can help you build a suite worth automating.
Which CI tool should you use for Django in 2026?
For a brand-new project, pick GitHub Actions if your code is on GitHub, or GitLab CI/CD if you are on GitLab — both are built into the platform you already use. CircleCI is a strong independent option when you want advanced caching, orbs or self-hosted runners. Travis CI is now a legacy choice and should be avoided for greenfield work.
| Tool | Status in 2026 | Where it runs | Config file |
|---|---|---|---|
| GitHub Actions | De-facto default for open source | GitHub-hosted or self-hosted runners | .github/workflows/*.yml |
| GitLab CI/CD | Mainstream, built into GitLab | GitLab.com or self-managed runners | .gitlab-ci.yml |
| CircleCI | Mainstream, independent | Cloud or self-hosted runners | .circleci/config.yml |
| Travis CI | Legacy and declining — avoid for new projects | travis-ci.com only (.org shut down in 2021) |
.travis.yml |
All four give a generous no-cost allowance of CI minutes for public, open-source repositories; private repos consume a metered allowance on every plan, so keep your builds fast.
How do you set up GitHub Actions for Django?
Create the file .github/workflows/ci.yml in your repository root. The workflow below runs on every push to main and on every pull request. It tests a matrix of Python versions against a PostgreSQL service container, installs dependencies, runs migrations, executes the test suite under coverage, lints with Ruff and uploads the coverage report to Coveralls.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [ main ]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13"]
# A real PostgreSQL database, available at localhost:5432 during the job.
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test_db
DJANGO_SETTINGS_MODULE: myproject.settings
SECRET_KEY: ci-not-a-real-secret
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install ruff coverage coveralls
- name: Lint with Ruff
run: ruff check .
- name: Run migrations
run: python manage.py migrate --noinput
- name: Run tests with coverage
run: |
coverage run --source='.' manage.py test
coverage report
- name: Upload coverage to Coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: coveralls --service=githubMake settings.py read the database from the environment
The workflow exposes DATABASE_URL and SECRET_KEY as environment variables, so your settings must read them instead of hard-coding credentials. The dj-database-url package parses the connection string in one line and behaves identically on your laptop, in CI and in production:
# settings.py
import os
import dj_database_url
SECRET_KEY = os.environ["SECRET_KEY"]
DATABASES = {
"default": dj_database_url.config(
default=os.environ.get("DATABASE_URL", "sqlite:///db.sqlite3"),
conn_max_age=600,
)
}How do you add coverage and linting?
Linting is already wired in above via ruff check . (Ruff is the fast, modern replacement for flake8 — swap in flake8 . if you prefer). Coverage runs your tests under measurement and then publishes the result.
The Upload coverage to Coveralls step uses the auto-provisioned GITHUB_TOKEN, so there is nothing extra to configure for public repos. For the full Coveralls walkthrough — including the badge and per-app --source settings — see setting up Coveralls for a Django project. To send the same report to Codecov instead, replace the final step with the codecov/codecov-action action.
What does the legacy .travis.yml look like, and how do you migrate?
If you maintain an older repository that still builds on Travis CI, a modernised .travis.yml (Python 2 and Django 1.x removed) looks like this. Treat it as a maintenance artefact, not a template for new work.
# .travis.yml — legacy; keep only for older repos still on Travis CI
language: python
dist: jammy
python:
- "3.11"
- "3.12"
services:
- postgresql
env:
- DATABASE_URL=postgres://postgres@localhost:5432/test_db
install:
- pip install -r requirements.txt
- pip install coverage coveralls
before_script:
- psql -c 'CREATE DATABASE test_db;' -U postgres
script:
- python manage.py migrate --noinput
- coverage run --source='.' manage.py test
after_success:
- coverallsMigrating is mostly a one-to-one mapping: the python: list becomes a matrix.python-version, services: becomes a service container, install: and script: become named run steps, and after_success: coveralls becomes the upload step. The biggest win is that GitHub Actions lives in your repo, so CI config is reviewed in the same pull request as the code it tests.
Frequently Asked Questions
Is Travis CI still usable for open-source Django projects?
Only in a limited way. travis-ci.org shut down in 2021 and builds moved to travis-ci.com, where the open-source allowance is far more restricted than it once was. For new work, GitHub Actions gives a generous no-cost allowance of CI minutes for public repositories and is the recommended default.
Should I start a new Django project on Travis CI?
No. New Django projects should default to GitHub Actions, or GitLab CI/CD if you host on GitLab. Travis is now a legacy platform with a declining open-source ecosystem, so the actions, examples and community help you find online almost all target GitHub Actions.
How do I run Django tests against PostgreSQL in GitHub Actions?
Add a services: block that runs a postgres image with a health check, then point DATABASE_URL at localhost:5432. The database is available to every step in the job, so python manage.py migrate and manage.py test run against a real PostgreSQL instance rather than SQLite.
Can I use pytest instead of manage.py test?
Yes. Install pytest and pytest-django, add a pytest.ini with DJANGO_SETTINGS_MODULE, and replace the test command with pytest --cov. The rest of the workflow — the Postgres service, the Python matrix and the coverage upload — stays exactly the same.
How do I add code coverage to my CI pipeline?
Run your tests under the coverage tool (coverage run --source='.' manage.py test), then upload the result to Coveralls or Codecov in a final step. See our companion guide on setting up Coveralls for a Django project for the badge and configuration details.
How do I migrate from Travis CI to GitHub Actions?
Map each .travis.yml section to a workflow equivalent: python: to matrix.python-version, services: to a service container, install:/script: to named run steps, and after_success: coveralls to a coverage-upload step. Add the new .github/workflows/ci.yml, confirm a green run, then delete .travis.yml.