Speed Up Django in GitHub Actions
Last updated
To speed up Django in GitHub Actions, cache your Python dependencies, run tests in parallel with pytest-xdist, and stop rebuilding the test database more often than you need. The default python manage.py test runs one process and applies every migration to a new database, which is slow once you have many migrations.
This guide covers install, database setup and test execution, with a workflow you can copy. For the general picture, read our guide to speeding up GitHub Actions.
Where the time goes
Check the step timings on a recent run. In a Django project the pieces are usually: install dependencies, create the test database (applying all migrations), and run the tests. If you have a frontend build or collectstatic, add that too. Test database setup is the item people forget; with hundreds of migrations it can take longer than the tests.
Cache dependencies
With pip, actions/setup-python has a cache input. Its README lists pip, pipenv and poetry, and the key comes from requirements.txt or pyproject.toml by default. Use cache-dependency-path if your files live elsewhere.
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: pip
cache-dependency-path: requirements/*.txt
- run: pip install -r requirements/test.txt
If you use uv, astral-sh/setup-uv has an enable-cache input and a cache-dependency-glob input whose defaults include uv.lock and pyproject.toml. Pin the action as its README shows, then run uv sync --locked. uv installs much faster than pip, which helps most on a cold cache.
If a package compiles from source in CI, switch to its binary build (for example psycopg[binary]) or pin a version that ships wheels.
Run tests in parallel with pytest-xdist
If you use pytest with pytest-django, add pytest-xdist and run with -n auto, which starts one worker per CPU core. pytest-django creates a separate test database per worker (test_db_gw0, test_db_gw1, and so on), so workers do not collide.
# pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = config.settings.test
addopts = -n auto --reuse-db --dist loadfile
--reuse-dbkeeps the test database between runs. In CI the database is fresh each run, so it matters little there, but it makes local runs much faster. After model changes, run once with--create-db.--dist loadfilekeeps tests from one file on one worker, which is safer when tests share module-level fixtures. The defaultloadmode balances better but can expose order dependence.- With Django's own runner,
python manage.py test --paralleldoes the same job with its own database cloning.
Our pytest slow tests guide covers finding slow tests with --durations=20 and fixture scope.
Postgres as a service
Do not test against SQLite if you deploy on Postgres; it hides bugs. Use a service container with a health check:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 20
services:
postgres:
image: postgres:17
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app
ports: ["5432:5432"]
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 5s --health-timeout 5s --health-retries 10
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/app
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: pip
- run: pip install -r requirements.txt
- run: pytest
A throwaway test database does not need durability. If you run Postgres yourself in CI you can start it with -c fsync=off -c synchronous_commit=off -c full_page_writes=off. Never use those settings for real data.
Speed up migrations in tests
Two approaches help with long migration histories:
- Squash old migrations with
squashmigrationswhen you no longer need the intermediate states. - Skip migrations in tests with a settings class that makes
MIGRATION_MODULESreturnNonefor each app, so tables are built straight from models. This is fast, but you stop testing migrations. Keep one CI job that runspython manage.py migrateon an empty database.
Also mock slow external calls such as email and HTTP.
Split across jobs
When one machine is not enough, shard files across a matrix. pytest-split and pytest-xdist combine well: each matrix job runs a slice chosen by recorded durations and uses -n auto inside. See test sharding in GitHub Actions and the test shard calculator.
What more CPU cores buy you
Django tests use the cores that -n auto finds. On a default hosted runner with 2 vCPU that means two workers. On 8 vCPU it means eight, as long as your tests are not waiting on the database. Postgres in a service container shares the same machine, so very high worker counts can make the database the limit. Watch for that rather than guessing.
We have no Django row in our benchmarks. The closest data is a SvelteKit + FastAPI + Postgres + Redis stack that went from 3m46s to 2m18s (1.6x, full run, 8 October 2026). It is a different framework, so read it as a hint that Python-heavy pipelines gain less than compiled builds.
To try your own suite, change one line to runs-on: [self-hosted, Linux, compiler-ci]. See pricing and compare. Related: the Django template, the Python template and GitHub Actions caching.
FAQ
Does pytest-xdist work with Django?
Yes, through pytest-django, which creates one test database per xdist worker. Use -n auto and make sure tests do not share files or external state.
What does --reuse-db do?
It keeps the test database instead of recreating it. It saves time locally. On a fresh CI machine the database does not exist yet, so it helps little there.
Should I use pip or uv in CI?
uv installs faster and has a built-in cache option in its setup action. pip with actions/setup-python caching is fine for small dependency sets.
How many xdist workers should I use?
-n auto matches CPU cores. If the database is the bottleneck, try a lower number such as half the cores.
Related
- Add a CI Speed Badge to Your READMEShow how fast your CI is with a README badge: the median GitHub Actions run time for your repository, updated automatically. Free for public repos.
- Speed Up .NET in GitHub ActionsSpeed up .NET builds in GitHub Actions: NuGet caching with setup-dotnet, --no-restore, --no-build, parallel dotnet test, and SQL Server services.
- Speed Up Bazel in GitHub ActionsSpeed up Bazel in GitHub Actions with setup-bazel, a disk cache, repository cache, a remote cache, and the right --jobs setting. Working YAML.
- Speed Up Elixir in GitHub ActionsSpeed up Elixir and Phoenix CI in GitHub Actions: deps and _build cache keyed on mix.lock and OTP, mix test --partitions, Postgres service, Dialyzer PLT.