Notes
Last Updated: 2026-06-22
This guide provides a reusable Docker pattern for deploying a Django application to either a DigitalOcean Droplet or Google Cloud Run from the same multi-stage Dockerfile.
It is a reusable baseline for Django projects that follow the documented conventions, not a universally project-agnostic Dockerfile. Adapt the project layout, settings and WSGI module paths, system packages, and build-time requirements to the application.
The pattern assumes:
src/ application layout;pyproject.toml and uv.lock for dependency management;The Django project remains responsible for its application code, settings, dependencies, environment variables, database migrations, and external services.
my-django-project/
├── .dockerignore
├── Dockerfile
├── manage.py
├── pyproject.toml
├── uv.lock
└── src/
├── config/
│ ├── settings/
│ │ └── production.py
│ └── wsgi.py
├── app_a/
└── app_b/
The Docker image sets PYTHONPATH=/app/src, allowing Python to import
config.wsgi and application modules without a src. prefix.
Declare Django, Gunicorn, the database driver, and other runtime packages in
pyproject.toml, then commit the generated uv.lock.
For example:
[project]
dependencies = [
"django",
"gunicorn",
"psycopg[binary]",
]
The production settings module must permit collectstatic to run during the
image build. If those settings require additional environment variables, pass
safe build-time values or use a dedicated build settings module. Never bake
production secrets into the image.
uv.lock with
uv sync --frozen.# syntax=docker/dockerfile:1
ARG PYTHON_VERSION=3.12-slim
FROM python:${PYTHON_VERSION} AS base-core
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
UV_COMPILE_BYTECODE=1 \
UV_PYTHON_DOWNLOADS=0 \
PATH="/app/.venv/bin:$PATH" \
PYTHONPATH=/app/src
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Pin uv to a tested release or image digest for reproducible builds.
COPY --from=ghcr.io/astral-sh/uv:0.11.23 /uv /uvx /bin/
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev --no-install-project
COPY . .
ARG DJANGO_SETTINGS=config.settings.production
ENV DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS}
ARG WSGI_MODULE=config.wsgi
ENV DJANGO_WSGI_MODULE=${WSGI_MODULE}
RUN SECRET_KEY=build-time-dummy-key \
python manage.py collectstatic --noinput
FROM base-core AS droplet
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "config.wsgi:application"]
FROM base-core AS cloud-run
ENV PORT=8080
EXPOSE 8080
# sh expands runtime variables; exec makes Gunicorn PID 1.
CMD ["sh", "-c", "exec gunicorn --bind 0.0.0.0:${PORT} --workers 1 --threads 8 --timeout 0 ${DJANGO_WSGI_MODULE}:application"]
The Droplet command uses config.wsgi directly. If the project uses a
different module, change the command or provide a small entrypoint script that
expands DJANGO_WSGI_MODULE.
Because the Dockerfile uses COPY . ., add a .dockerignore file. A useful
starting point is:
.git
.gitignore
.venv
__pycache__/
*.py[cod]
*.sqlite3
*.env
.env*
build/
dist/
staticfiles/
media/
Adjust this list if the application intentionally includes any of these files. Do not copy local virtual environments, credentials, development databases, or user-uploaded media into the image.
docker build --target droplet -t myapp-droplet:latest .
docker build \
--target cloud-run \
-t REGION-docker.pkg.dev/PROJECT/REPOSITORY/myapp:latest \
.
These commands build local images. They do not deploy them.
Push the image to a registry accessible by the Droplet, pull it on the server, and run it with the required environment variables:
docker run --detach \
--name myapp \
--restart unless-stopped \
--env-file /path/to/production.env \
--publish 127.0.0.1:8000:8000 \
REGISTRY/myapp-droplet:TAG
Place a reverse proxy such as Nginx or Caddy in front of Gunicorn for public HTTP and HTTPS traffic.
Push the image to Artifact Registry, then deploy that registry image:
docker push REGION-docker.pkg.dev/PROJECT/REPOSITORY/myapp:TAG
gcloud run deploy SERVICE \
--image REGION-docker.pkg.dev/PROJECT/REPOSITORY/myapp:TAG \
--region REGION
Cloud Run injects the PORT environment variable. The container must listen
on 0.0.0.0 at that port. Configure secrets, database connectivity, service
accounts, migrations, and other deployment settings separately.
The supplied Gunicorn values are starting points, not universal production defaults:
--timeout 0 disables Gunicorn’s worker timeout. Use it
only when that behavior matches the application’s request and shutdown
requirements.Before publishing an image, check the Dockerfile and build both targets:
docker build --check --target droplet .
docker build --check --target cloud-run .
docker build --target droplet -t myapp-droplet:test .
docker build --target cloud-run -t myapp-cloud-run:test .
Then run each relevant image with representative production settings and verify: