Django Dockerfile for ASGI with Uvicorn (Production, Slim)

This Django Dockerfile serves your project through ASGI with Uvicorn on python:3.13-slim, which unlocks async views and long-lived connections. Dependencies are installed in a separate stage and static files are collected at build time. The app runs as a non-root user on port 8000.

CI status
Build passing
Image size (uncompressed)
155 MB
Build time
14 s
Runtime image
python:3.13-slim
Port
8000
Last built
2026-10-15
Dockerfile
# syntax=docker/dockerfile:1
FROM python:3.13-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 DJANGO_SETTINGS_MODULE=config.settings
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
RUN SECRET_KEY=build-only python manage.py collectstatic --noinput \
 && useradd --create-home --uid 10001 app
USER app
EXPOSE 8000
CMD ["uvicorn", "config.asgi:application", "--host", "0.0.0.0", "--port", "8000"]
.dockerignore
.git
__pycache__/
*.pyc
.venv/
.env
media/
staticfiles/
db.sqlite3
Dockerfile
.dockerignore

Before you build

How this Dockerfile works

  1. FROM python:3.13-slim AS builder

    Packages are installed in a disposable stage, so the final image contains only what the app needs to run.

  2. RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

    Installs Django, Uvicorn and your other dependencies into one directory without keeping pip's download cache.

  3. ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 DJANGO_SETTINGS_MODULE=config.settings

    Turns off .pyc files, streams logs immediately, and tells Django which settings module to load inside the container.

  4. RUN SECRET_KEY=build-only python manage.py collectstatic --noinput \

    Collects static files once at build time. The placeholder SECRET_KEY only satisfies settings for this command and never reaches runtime.

  5. && useradd --create-home --uid 10001 app USER app

    Creates an unprivileged user in the same layer and switches to it before the server starts.

  6. CMD ["uvicorn", "config.asgi:application", "--host", "0.0.0.0", "--port", "8000"]

    Starts Django's ASGI application under Uvicorn on all interfaces. Async views run on the event loop, and sync views run in a thread pool.

Build and run

terminal
docker build -t django-app .
docker run --rm -p 8000:8000 -e SECRET_KEY=change-me django-app

Supply the real SECRET_KEY, database URL and ALLOWED_HOSTS as environment variables at runtime, then open http://localhost:8000 to check the server responds.

Other Django Dockerfile variants

Gunicorn with WSGI workers remains the most common choice for classic request/response Django apps. ASGI with Uvicorn suits projects that use async views, server-sent events or WebSockets via Channels. Both are listed in the table below.

VariantRuntime imageImage sizeCI
Python slim + Gunicorn (multi-stage)python:3.13-slim155 MBPassing
Python slim + Uvicorn (ASGI)python:3.13-slim155 MBPassing

Troubleshooting

SynchronousOnlyOperation: You cannot call this from an async context
An async view calls the ORM directly. Use the async ORM methods, such as aget and acreate, or wrap the call with sync_to_async.
ImproperlyConfigured: STATIC_ROOT setting is missing during the build
collectstatic needs a destination directory. Set STATIC_ROOT in settings, for example to a staticfiles folder inside the project, and rebuild.
DisallowedHost error on the first request
Add the host name you browse to, such as localhost or your domain, to ALLOWED_HOSTS, ideally read from an environment variable.

FAQ

Do I need ASGI if my views are all synchronous?
No. Synchronous Django runs well under Gunicorn. ASGI pays off once you add async views, streaming responses or WebSockets.
Can I run several Uvicorn workers?
Yes. Add --workers to the Uvicorn command, or scale by running more containers. Each worker is a separate process with its own memory.
Where should migrations run?
Run them as a one-off step in your deploy pipeline using the same image, not on container start, so replicas don't race to migrate.