Setting Up Coveralls for a Django Project (with GitHub Actions)

Blog / Django · September 14, 2015 · Updated June 10, 2026 · 7 min read
Setting Up Coveralls for a Django Project (with GitHub Actions)

Coveralls is a hosted code-coverage service that records what percentage of your Django code your tests exercise, shows line-by-line coverage on every pull request, and renders a coverage badge for your README. To wire it up in 2026 you connect your GitHub (or Bitbucket) repository to coveralls.io, then let your CI run the test suite under coverage and push the report up — the canonical path today is GitHub Actions with the coverallsapp/github-action, or the coveralls Python package if you prefer.

This guide focuses on the Coveralls service and CI integration. For the basics of measuring coverage locally with coverage.py and pytest-cov, see our companion guide on checking test coverage in Django code with Coveralls.

Key takeaways

  • Coveralls is the dashboard, not the measurer. coverage.py/pytest-cov produce the numbers; Coveralls stores, visualizes, and trend-tracks them across CI runs and pull requests.
  • GitHub Actions is the modern path. The old Travis-only, Python 2 recipe is obsolete — run tests in an Actions workflow and upload the report from there.
  • Two upload options: the coverallsapp/github-action (least config) or the coveralls Python package (coveralls --service=github).
  • Public/open-source repos are free; private repos need a paid plan — the same tiering Codecov uses.
  • PR checks plus a README badge turn coverage into a visible, enforceable team signal.

How do I connect a Django repo to Coveralls?

  1. Sign in at coveralls.io with your GitHub or Bitbucket account.
  2. Click Add Repos and toggle on the repository you want to track.
  3. For public repositories that is all you need — Coveralls reads the build token from the GitHub Action automatically. For private repositories, copy the repo token Coveralls shows you and store it as an Actions secret (for example COVERALLS_REPO_TOKEN).
  4. Add a coverage step to your CI (next section) and push a commit. The first report appears on your Coveralls dashboard seconds after the build finishes.

What does a GitHub Actions workflow for Coveralls look like?

Create .github/workflows/tests.yml. The workflow installs dependencies, runs the Django test suite under coverage, emits an LCOV report, and hands it to the Coveralls action:

name: tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

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

      - name: Run Django tests with coverage
        run: |
          coverage run --source='.' manage.py test
          coverage lcov          # writes ./coverage.lcov

      - name: Upload coverage to Coveralls
        uses: coverallsapp/github-action@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          file: coverage.lcov

GitHub Actions injects GITHUB_TOKEN automatically, so public repositories need no extra secrets. The coverallsapp/github-action reads coverage.lcov by default; point file: at wherever your report actually lands.

How do I upload coverage with the coveralls Python package?

If you run your suite with pytest or use a CI other than Actions, the coveralls Python package is the alternative. Install it, run tests under coverage, then call coveralls --service=github with a GITHUB_TOKEN in the environment:

      - name: Install dependencies
        run: pip install -r requirements.txt pytest pytest-cov coveralls

      - name: Run tests with pytest
        run: pytest --cov=. --cov-report=        # writes the .coverage data file

      - name: Upload coverage to Coveralls
        run: coveralls --service=github
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Which Coveralls upload method should I pick?

Method Extra install Upload step Best for
coverallsapp/github-action None uses: coverallsapp/github-action@v2 Most GitHub Actions projects; least config
coveralls Python package pip install coveralls coveralls --service=github Non-Actions CI, or custom Python upload flows

Both send the same data to coveralls.io — pick one per workflow, not both.

How do I configure what coverage measures?

Tell coverage which code counts and which to ignore, so migrations, settings, and test files do not dilute your numbers. A minimal .coveragerc in your project root:

[run]
source = .
omit =
    */migrations/*
    */tests/*
    */venv/*
    manage.py
    */settings/*
    */wsgi.py
    */asgi.py

[report]
show_missing = True

Prefer pyproject.toml? Use the [tool.coverage.run] table instead:

[tool.coverage.run]
source = ["."]
omit = [
    "*/migrations/*",
    "*/tests/*",
    "manage.py",
    "*/wsgi.py",
    "*/asgi.py",
]

[tool.coverage.report]
show_missing = true

Keep this list short — for a deeper walkthrough of source, omit, branch coverage, and the different --cov-report formats, see the companion coverage guide linked at the top.

How do I merge coverage from a parallel or matrix build?

When you test across several Python or Django versions with a build matrix, each job produces only a partial report. Coveralls stitches them together if you flag every matrix job with parallel: true and add a final job that sends parallel-finished: true:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12"]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pip install -r requirements.txt coverage
      - run: |
          coverage run --source='.' manage.py test
          coverage lcov
      - name: Upload coverage (parallel)
        uses: coverallsapp/github-action@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          file: coverage.lcov
          flag-name: py-${{ matrix.python-version }}
          parallel: true

  finish:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Tell Coveralls the build is finished
        uses: coverallsapp/github-action@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          parallel-finished: true

How do I add the coverage badge to my README?

Coveralls generates a badge per repository and branch. Copy the Markdown from your repo's Coveralls page, or build it yourself — replace your-org/your-repo and the branch name:

[![Coverage Status](https://coveralls.io/repos/github/your-org/your-repo/badge.svg?branch=main)](https://coveralls.io/github/your-org/your-repo?branch=main)

Commit that to README.md and the badge refreshes after every build, giving contributors an at-a-glance coverage figure. Pair it with PR checks — set a minimum-coverage or no-drop rule in your Coveralls repo settings — so a regression blocks the merge instead of slipping through.

What should I do before turning Coveralls on?

Coveralls only reflects the tests you actually write — a green badge over a thin suite is false comfort. Before enabling it, make sure your Django views, forms, and models carry real assertions; our guide to Django unit test cases with forms and views is a solid starting point. If you are still on Travis CI, the upload step is nearly identical — see setting up Travis CI for a Django project — but new projects should default to GitHub Actions.

Coveralls vs Codecov: which should I choose?

Codecov is the most popular alternative to Coveralls, and for many teams it is the more feature-rich option — be honest about what you actually need. Both are SaaS coverage dashboards built around the same core workflow; the differences sit at the margins:

Feature Coveralls Codecov
Hosting SaaS (coveralls.io) SaaS (codecov.io)
Public / open-source repos Free Free
Private repos Paid plan Paid plan
PR comments & checks Yes Yes (richer analytics)
Report formats LCOV, Cobertura, and more LCOV, Cobertura, XML, and more
GitHub Action coverallsapp/github-action codecov/codecov-action
Matrix/parallel merge parallel + parallel-finished flags, merged automatically
README badge Yes Yes

If you want the simplest setup and only need a badge plus basic PR feedback, Coveralls is hard to beat. If you want richer PR analytics, component/flag breakdowns, and broader report-format support, evaluate Codecov.

Where does coverage fit in a real QA strategy?

Coverage is a useful signal, not a goal — 100% line coverage with weak assertions still ships bugs. Treat Coveralls as one layer alongside meaningful unit, integration, and end-to-end tests. If you would rather have a team own this end to end, MicroPyramid has done it across 50+ projects since 2014 as part of our software testing services.

Frequently Asked Questions

Is Coveralls free for Django projects?

Coveralls is free for public, open-source repositories; private repositories require a paid plan. That is the same tiering Codecov uses, so the decision between the two rarely comes down to access. You only need a GitHub or Bitbucket account to start tracking a public Django repo.

Do I still need Travis CI to use Coveralls?

No. Travis CI still works, but GitHub Actions is the canonical CI for new Django projects in 2026, and the Coveralls upload is a single action or command either way. Migrate the old Travis-and-Python-2 recipe to an Actions workflow like the one above; the coverage commands themselves are unchanged.

What is the difference between coverage.py and Coveralls?

coverage.py — and pytest-cov, which wraps it — measures coverage on your machine or CI runner and writes a report file. Coveralls is the hosted service that ingests that report, keeps history, draws the trend graph, comments on pull requests, and serves the README badge. You need both: one to measure, one to display.

How do I make a pull request fail when coverage drops?

Set a coverage rule in your Coveralls repository settings — for example, fail if total coverage decreases or falls below a chosen threshold. Coveralls then posts a commit status that GitHub treats as a required check, so a coverage regression blocks the merge until it is fixed.

Why does Coveralls show 0% or "no coverage report found"?

The usual causes are a report that was never generated, a wrong file path, or a source setting that does not match your package layout. On pull requests from forks the token can be missing — use coverallsapp/github-action, which handles fork tokens, or restrict uploads to same-repo branches.

Can I report coverage from a matrix of Python and Django versions?

Yes. Mark each matrix job with parallel set to true when it uploads, then add one final job that sends parallel-finished set to true. Coveralls waits for every parallel job, merges their partial reports, and publishes a single combined coverage number for the build.

Share this article