---
slug: "django-docker-chane-image-alpine-uwsgi-to-debian-daphne"
title: "Changed Django's Docker Environment from Alpine + uWSGI to Debian + Daphne → Ended up with uvicorn After All"
description: "Why switch a Django Docker image from Alpine + uWSGI to Debian + Daphne (ASGI), with example Dockerfile and Kubernetes manifest changes."
url: "https://www.ytyng.com/en/blog/django-docker-chane-image-alpine-uwsgi-to-debian-daphne"
publish_date: "2022-09-03T11:03:29Z"
created: "2022-09-03T11:03:29Z"
updated: "2026-05-11T13:21:29.421Z"
categories: ["Django", "Docker", "Python"]
keywords: ""
featured_image_url: "https://media.ytyng.com/resize/20230812/d6989469f6624bb59f440de7a9fc82a4.png.webp?width=768"
has_video: false
has_music: false
video_urls: []
music_urls: []
lang: "en"
---

# Changed Django's Docker Environment from Alpine + uWSGI to Debian + Daphne → Ended up with uvicorn After All

<p>Up until now, I have often created Django images with Alpine Linux + uWSGI. However, there's a known issue where executing Python on Alpine Linux is slow.</p>
<ul>
<li><a href="https://applingo.tokyo/article/6860" target="_blank">Reasons why you shouldn't use Alpine Linux as a base image when using Python with Docker</a></li>
<li><a href="https://qiita.com/kawamou/items/8c3c13c40929dbaaaae3" target="_blank">Issues with running Python on an Alpine image - Qiita</a></li>
</ul>
<p>Additionally, I was using uWSGI as the HTTP server running on Alpine, but its configuration is complex, and it felt redundant for servicing with Kubernetes.</p>
<p>Therefore, I decided to change the environment for the Django service.</p>
<p>The base image was changed from Alpine to a <strong>Debian</strong>-based Python, and the HTTP server was switched to <strong>Daphne</strong>.</p>
<p>Note: Daphne was later replaced with <strong>Uvicorn</strong> because it couldn't handle concurrent requests.</p>
<h1>Changes to the Docker Image</h1>
<h3>Switch from Alpine to Python (Debian)</h3>
<p>I adopted a multi-stage build approach where I use the full Python image for pipenv install (pipenv sync) and copy the artifacts to a public stage based on the slim Python image.</p>
<p>Previously, when I built the image including source code, dependencies, and uWSGI on Alpine, the resulting image size was 277MB. With a multi-stage build using python:3.10-bullseye and python:3.10-slim-bullseye, the final image size was 313MB. Although the size increased slightly, it was almost the same.</p>
<p>The Dockerfile is provided below.</p>
<h1>Changes to the HTTP Server</h1>
<h3>Switch from uWSGI to Daphne</h3>
<p>uWSGI is a very good library, but it has many tuning parameters, which can be exhausting over time.</p>
<p>Due to its characteristics, uWSGI tends to increase memory usage with each response. To prevent this, you can restart workers after a certain number of requests (max-requests). Is this a memory leak? No, it's garbage collection (GC).</p>
<p>When a worker restarts, it becomes temporarily unavailable, but as long as other workers are alive, overall service downtime can be avoided. However, since this occurs every few requests, it often happens in close succession, causing service interruptions.</p>
<p>There is an option (max-requests-delta) to stagger the restart threshold for each worker, which should avoid service interruptions. However, this setting is not available in the latest build. Despite being documented years ago, it has never worked as expected (I thought it was working).</p>
<ul>
<li><a href="https://qiita.com/mushoku_toumei/items/6bda4e3e077218fbd8d8" target="_blank">Is your uWSGI really using max-requests-delta? - Qiita</a></li>
<li><a href="https://qiita.com/__m/items/7dcd44e4aeed0b66d4a1" target="_blank">uWSGI's max-requests-delta is not working - Qiita</a></li>
</ul>
<p>Therefore, I decided to switch to a different application server.</p>
<p>As candidates, besides the classic gunicorn, there are Uvicorn and Hypercorn used with FastAPI, as well as Daphne developed by the Django team. This time, I chose Daphne.</p>
<p>Daphne, Uvicorn, and Hypercorn all support ASGI and are the mainstream servers for Django 3 and later.</p>
<p>Since I am using Django, I opted for Daphne, developed by the Django team.</p>
<ul>
<li><a href="https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/daphne/" target="_blank">How to use Django with Daphne | Django documentation | Django</a></li>
</ul>
<h3>Note: Switched from Daphne to Uvicorn</h3>
<p>Daphne processes consecutive requests sequentially as coroutines. While this is fine for entirely async views, existing services are not set up this way, leading to issues with handling concurrent requests.</p>
<p>Since many applications run in a single pod, the responsiveness was not great, so I switched to Uvicorn, which makes starting multiple workers easier.</p>
<h1>Serving Static Content</h1>
<p>Switching from uWSGI to Daphne presents a challenge for serving static content.</p>
<p>For large-scale services used by many customers, static content is often served using a configuration like CloudFront + S3. In these cases, it's not a problem. However, for smaller services like internal tools or management sites, a simpler static file service is desired.</p>
<p>uWSGI has a feature called static-map for easily serving static files, which is perfect for internal tools, and I used it frequently. However, Daphne does not support serving static files.</p>
<p>One option is to use Nginx before Daphne, distributing static content and Django requests within Nginx. However, I wanted to avoid adding more daemons, so I looked for another solution.</p>
<p>A relatively recent and popular solution seems to be an application called WhiteNoise.</p>
<ul>
<li><a href="https://whitenoise.evans.io/en/stable/" target="_blank">WhiteNoise documentation</a></li>
</ul>
<p>WhiteNoise is a static content server written in Python that can be easily integrated as middleware in Django.</p>
<p>Running a static content server in Python might seem nonsensical, but the answer to this is in the official documentation.</p>
<p><a href="https://whitenoise.evans.io/en/stable/#infrequently-asked-questions" target="_blank">https://whitenoise.evans.io/en/stable/#infrequently-asked-questions</a></p>
<p>As an alternative to S3 or Nginx, it perfectly fits my needs.</p>
<h1>Dockerfile</h1>
<p>The Dockerfile is as follows:</p>
<pre>FROM python:3.10-bullseye AS builder

# Copy Pipfile
COPY Pipfile /tmp/Pipfile
COPY Pipfile.lock /tmp/Pipfile.lock

# pipenv sync
# For some projects: pipenv install --system --ignore-pipfile --deploy
RUN python3 -m pip install pipenv \
  && PIPENV_PIPFILE=/tmp/Pipfile pipenv sync --system \
  && python3 -m pip install uvicorn
# Previously: && python3 -m pip install daphne


FROM python:3.10-slim-bullseye

# Copy necessary SOs for MySQL client from the builder stage
COPY --from=builder \
  /usr/lib/x86_64-linux-gnu/libmariadb.a \
  /usr/lib/x86_64-linux-gnu/libmariadb.so.3 \
  /usr/lib/x86_64-linux-gnu/
COPY --from=builder /usr/lib/x86_64-linux-gnu/libmariadb3/ \
  /usr/lib/x86_64-linux-gnu/libmariadb3/
# Copy libraries installed by Pipenv from the builder stage
COPY --from=builder /usr/local/lib/python3.10/site-packages \
  /usr/local/lib/python3.10/site-packages
COPY --from=builder /usr/local/lib/python3.10/lib-dynload \
  /usr/local/lib/python3.10/lib-dynload
COPY --from=builder /usr/local/bin /usr/local/bin

# Create symbolic links for SOs
RUN ln -s /usr/lib/x86_64-linux-gnu/libmariadb.a \
  /usr/lib/x86_64-linux-gnu/libmariadbclient.a \
  && ln -s /usr/lib/x86_64-linux-gnu/libmariadb.so.3 \
  /usr/lib/x86_64-linux-gnu/libmariadb.so \
  && ln -s /usr/lib/x86_64-linux-gnu/libmariadb.so.3 \
  /usr/lib/x86_64-linux-gnu/libmariadbclient.so

COPY my_app /var/app/my_app
RUN chown -R nobody:nogroup /var/app

USER nobody
WORKDIR /var/app/my_app
RUN cd /var/app/my_app && python3 ./manage.py collectstatic --noinput
EXPOSE 8002
CMD ["uvicorn", \
  "my_app.asgi:application", \
  "--host", "0.0.0.0", \
  "--port", "8002", \
  "--workers", "4" \
]

# Previously: CMD ["daphne", "-b", "0.0.0.0", "-p", "8002", "my_app.asgi:application"]
</pre>
<p></p>
<h2>Running WhiteNoise with Django</h2>
<p>To host static files, add</p>
<pre>whitenoise.middleware.WhiteNoiseMiddleware</pre>
<p>to the MIDDLEWARE setting in Django.</p>
<p><a href="https://whitenoise.evans.io/en/stable/django.html" target="_blank">Using WhiteNoise with Django - WhiteNoise 6.2.0 documentation</a></p>
<h3>Extending Cache Duration</h3>
<p>The default cache header lifetime (max-age) for WhiteNoise is</p>
<pre>60 if not settings.DEBUG else 0</pre>
<p>which is quite short. I will change it to 7 days.</p>
<pre>WHITENOISE_MAX_AGE = 86400 * 7</pre>
<p>(Set this in the production settings)</p>
<h3>Media Server</h3>
<p>WhiteNoise does not serve Django's MEDIA_URL.</p>
<p><a href="http://whitenoise.evans.io/en/stable/django.html#serving-media-files" target="_blank">http://whitenoise.evans.io/en/stable/django.html#serving-media-files</a></p>
<p>The reasons are explained on the above page. Therefore, for handling media, you need to integrate with something like <a href="https://django-storages.readthedocs.io/en/latest/" target="_blank">django-storages</a> to set up a service using S3 or nginx.</p>
