yeiichi

Notes

View My GitHub Profile

Django Docker Deployment: DigitalOcean Droplet and Google Cloud Run

Last Updated: 2026-06-22

1. Introduction

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:

The Django project remains responsible for its application code, settings, dependencies, environment variables, database migrations, and external services.


2. Deployment Design

A. Expected Project Layout

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.

B. Application Requirements

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.

C. Design Policy


3. Multi-Target Dockerfile

# 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.


4. Docker Build Context

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.


5. Build Targets

A. DigitalOcean Droplet

docker build --target droplet -t myapp-droplet:latest .

B. Google Cloud Run

docker build \
    --target cloud-run \
    -t REGION-docker.pkg.dev/PROJECT/REPOSITORY/myapp:latest \
    .

These commands build local images. They do not deploy them.


6. Deployment Outline

A. DigitalOcean Droplet

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.

B. Google Cloud Run

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.


7. Runtime Tuning

The supplied Gunicorn values are starting points, not universal production defaults:


8. Validation

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:


9. Maintenance Notes