djangonginxnginx-reverse-proxydjango-media

Nginx responds 404 not found on Django media URL in preprod, dev ok


I have a quite standard Django application with a Vuejs frontend.

I have different environments (preprod/dev) in which I have file upload/download features.

For files, everything works fine because they are returned through standard API views in attachment (Content-Disposition: attachment). When it comes to images though, like profile pictures, there is a problem.

In development (DEBUG=True), I have this :

from django.conf import settings
from django.conf.urls.static import static
from django.urls import include, path

from backend.applications.base.views.authentication_views import LoginAPIView, LogoutAPIView

urlpatterns = [
    path("api/login", LoginAPIView.as_view()),
    path("api/logout", LogoutAPIView.as_view()),
    path("api/base/", include("backend.applications.base.urls")),
    path("api/contact/", include("backend.applications.contact.urls")),
    path("api/helpdesk/", include("backend.applications.helpdesk.urls")),
    path("api/inventory/", include("backend.applications.inventory.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)  # For serving media files when DEBUG=True

and images are correctly served (no nginx in dev mode, just frontend and backend dev servers django's runserver).

My preprod however, is made of a nginx container which serves my built vuejs frontend, and a backend container which contains my Django (DEBUG=False) application (which runs with gunicorn this time, like this : gunicorn backend.wsgi:application --bind 0.0.0.0:8000 --access-logfile="-").

Before trying to serve images, I had this nginx configuration :

http {
    client_max_body_size 5M;

    upstream backend_api {
        server backend:8000;
        # 'backend' is the name of the backend service in my docker-compose config
    }

    server {
        listen 80;

        include /etc/nginx/mime.types;

        root /usr/share/nginx/html;
        index index.html;

        location = /favicon.ico {
            access_log off;
            log_not_found off;
        }

        location /api {
            proxy_pass http://backend_api;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_redirect off;
        }

        location / {
            try_files $uri $uri/ /index.html;
        }
    }
}

Then I thought that /media requests should also be passed to the backend and I changed

location /api

into

location ~ ^/(api|media)/ {

My /api URLs are still handled correctly but /media URLs are answered by a 404 :

trying to load profile pictures of my user(s) in a kanban view (trying to load profile pictures of my user(s) in a kanban view).

Also trying directly http://localhost/media/base/users/8/picture.jpg directly in my browser doesn't work :

enter image description here enter image description here

From here I don't know what to do to solve the issue. If something is missing, mention it and I'll update the post.


Solution

  • Django does not serve static- and media files with runserver, you will need WhiteNoise for that. See http://whitenoise.evans.io/en/stable/ Whitenoise however is not suitable for serving user-uploaded media files. See http://whitenoise.evans.io/en/stable/django.html#serving-media-files

    (Optionally, skip whitenoise, and host static/media files through NGINX.)

    You really shouldn't be hosting your server with py manage.py runserver. This is not secure. See Why not use "runserver" for production at Django? and https://docs.djangoproject.com/en/dev/ref/django-admin/#runserver

    Use something like Gunicorn instead.

    See https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/gunicorn/

    (Or waitress, the windows alternative)

    https://pypi.org/project/django-waitress/

    To host static/media files with nginx, paste this into your nginx conf:

        location /media  {
            alias /PATH/TO/DIRECTORY; #Absolute path.
        }
    

    And in your settings.py, set the media root to that same directory.