Build Django Images With Cloud Native Buildpacks

Build production Django container images without a Dockerfile using Cloud Native Buildpacks and the pack CLI: choose a builder, configure Python version and start command with a Procfile, run collectstatic, rebase images for OS patches, and use it in CI.

Problem statement

Writing and maintaining a Dockerfile for every Django service means keeping base images patched, layers ordered for caching, users non-root, and builds reproducible. Cloud Native Buildpacks (CNB) turn source code into OCI images automatically: they detect Python, install dependencies, set a start command, and produce images you can rebase onto a patched OS layer without rebuilding. Heroku, Google Cloud Run source deploys, and several platforms use them internally; with the pack CLI you can use them anywhere.

Buildpacks for DjangoSource coderequirements, Procfilepack builddetect and buildOCI imagelayered, non-rootpack rebasepatch OS layerBuildpacks replace the Dockerfile with detection and standard layers you can rebase.
  1. Install the pack CLI and pick a builder, such as heroku/builder:24 or paketobuildpacks/builder-jammy-base.
  2. Pin Python with .python-version (or the method your builder documents) and keep requirements.txt, Poetry, or uv lock files in the repo.
  3. Add a Procfile with web: gunicorn config.wsgi --bind 0.0.0.0:${PORT:-8000}.
  4. Build: pack build ghcr.io/acme/shop:$SHA --builder heroku/builder:24 --publish.
  5. Apply OS security patches without rebuilding: pack rebase ghcr.io/acme/shop:$SHA --publish.

Step-by-step solution

1. Install pack and choose a builder

brew install buildpacks/tap/pack   # or download the release binary
pack builder suggest

A builder bundles buildpacks with a build image and a run image. Two common choices for Python:

  • Heroku builder (heroku/builder:24): same Python buildpack as Heroku, supports pip, Poetry, and uv.
  • Paketo builder (paketobuildpacks/builder-jammy-base): CNCF community buildpacks, supports pip, Pipenv, Poetry, and Conda.

2. Prepare the project

.python-version     # e.g. 3.12
requirements.txt    # or poetry.lock / uv.lock
Procfile
# Procfile
web: gunicorn config.wsgi --bind 0.0.0.0:${PORT:-8000} --workers 3
worker: celery -A config worker -l info

The Procfile defines process types; the web type becomes the image's default command, and other types are available as alternative entrypoints (for example --entrypoint worker in Docker, or the launcher with the process name).

who maintains the image recipeCloud Native Buildpacks✓no Dockerfile to maintain✓rebase OS without rebuild✕less control over layersDockerfile✓full control, any package✓works with any builder✕you patch and tune itBuildpacks suit standard apps; Dockerfiles suit unusual system needsdjango-deployment.com
Apps needing GDAL, WeasyPrint, or other system libraries often outgrow default builders.

3. Build and run collectstatic

The Heroku Python buildpack runs collectstatic automatically when it finds Django, unless DISABLE_COLLECTSTATIC=1. Paketo does not by default; add a build step through a project.toml or run it at container start. Settings must import without runtime secrets during the build:

pack build ghcr.io/acme/shop:$(git rev-parse --short HEAD) \
  --builder heroku/builder:24 \
  --env DJANGO_SETTINGS_MODULE=config.settings.build \
  --publish

--publish pushes straight to the registry, which is faster in CI and keeps the layer cache in the registry.

4. Run the image

docker run --rm -p 8000:8000 --env-file .env ghcr.io/acme/shop:3f2a1c9
docker run --rm --env-file .env --entrypoint worker ghcr.io/acme/shop:3f2a1c9
docker run --rm --env-file .env --entrypoint launcher ghcr.io/acme/shop:3f2a1c9 python manage.py migrate

Images run as a non-root user by default, which suits Kubernetes and most platforms.

5. Patch the OS with rebase

When the run image gets security updates, rebase swaps the OS layers under your app layers without running the build again:

pack rebase ghcr.io/acme/shop:3f2a1c9 --publish

This takes seconds because nothing is recompiled. Schedule it, or rebuild regularly from CI, so images do not age with known vulnerabilities.

what pack rebase can replaceApp sourceyour codeDependencies layersite-packages, cachedPython runtime layerfrom the buildpackRun image (OS)swapped by pack rebaseThe OS layer is independent, so patches do not need a rebuilddjango-deployment.com
Python itself is updated by rebuilding, not by rebase.

Explanation

The CNB lifecycle runs in phases: detect chooses buildpacks based on files in the repo, build lets each buildpack contribute layers (Python, dependencies, app), and export writes an OCI image with those layers on top of a run image. Because layers are created by buildpacks with stable boundaries, the OS layer can be replaced later with rebase. The approach standardizes images across services and teams, at the cost of less control than a Dockerfile.

Edge cases / notes

  • System packages. Default builders do not install apt packages. Libraries like GDAL require custom buildpacks, an apt buildpack, or a Dockerfile.
  • Build-time settings. Use a dedicated build settings module so collectstatic does not need database or secret variables.
  • Image size. Buildpack images are typically larger than a tuned slim Dockerfile; acceptable for most platforms.

Next steps

FAQ

Do buildpacks work with Poetry or uv?

The Heroku Python buildpack supports pip, Poetry, and uv; Paketo supports pip, Pipenv, Poetry, and Conda. Check the builder's documentation.

How do I run migrations with a buildpack image?

Use the launcher entrypoint: --entrypoint launcher image python manage.py migrate, or a Procfile process type.

What does pack rebase do?

It replaces the OS base layers of an image with an updated run image, without rebuilding your app layers.

Can I add system packages?

Not with the default Python buildpacks. Use an apt buildpack, a custom buildpack, or a Dockerfile.

Keep going