DEV Community

Cover image for How to deploy Django in a subdirectory with Docker, NGINX and Whitenoise
Daniel
Daniel

Posted on • Originally published at daniel.es

How to deploy Django in a subdirectory with Docker, NGINX and Whitenoise

Introduction

If you are here, you've probably faced the same challenged as I did and spent countless hours on google trying to find how to make a django application work in a subdirectory.

Look no further, here's all you need to know to make it work.

My setup

I want my frontend to be served in the root / of my domain and the backend to be served in the /api subdirectory of the same domain:

https://example.com/ -> Frontend
https://example.com/api/ -> Backend
Enter fullscreen mode Exit fullscreen mode

I'm going to quickly run you through my setup so you can understand the context of this article.

  • I have a monorepo with two folders: backend and frontend
  • backend: django REST API that serves data and an admin page
  • frontend: React frontend that consumes the API
  • Docker to containerize the application
  • Nginx to serve the Frontend application and serve the django API in a subdirectory
project/
├── backend/
│   ├── Dockerfile
│   └── other django stuff...
├── frontend/
│   ├── Dockerfile
│   ├── default-nginx.conf
│   └── other frontend stuff...
└── docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

This is my sample docker compose file:

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "8000:8000"

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "8080:80"
    depends_on:
      - backend
Enter fullscreen mode Exit fullscreen mode

The problem

When I tried to deploy the application to a subdirectory, I faced a few challenges:

  1. The API was not working
  2. The static files were not being served
  3. The admin page was not working

The solution

1. Configure django

First, we need to configure Django to let it know that we are running in a subdirectory.

Add the following to your settings.py:

FORCE_SCRIPT_NAME = '/api'
Enter fullscreen mode Exit fullscreen mode

Important!

Having the leading slash is important to make the static files settings below work correctly.

FORCE_SCRIPT_NAME is a setting that tells Django to prepend the given value to all URLs generated by Django.

You can find more documentation here.

2. Configure Static Files

We need to configure Django to serve the static files correctly in the subdirectory, otherwise, they won't be found.

You're probably familiar with the following settings in settings.py:

STATIC_ROOT = BASE_DIR / "staticfiles"
STATIC_URL = "/static"
Enter fullscreen mode Exit fullscreen mode

You need to modify it to include the subdirectory:

STATIC_ROOT = BASE_DIR / "staticfiles"
STATIC_URL = "{}/static/".format(FORCE_SCRIPT_NAME)
Enter fullscreen mode Exit fullscreen mode

If you are using Whitenoise you will also need to include the following setting:

WHITENOISE_STATIC_PREFIX = "/static/"
Enter fullscreen mode Exit fullscreen mode

There is a PR that has been merged that allows you to not have to set this setting, but it's not released as of the time of writing this article.

You can find the PR here.

3. Admin login

The settings above should make the admin page work, but some people have reported encountering the admin page redirecting them to the root of the domain after login.

There's a setting that allows you to override the default admin login redirect:

LOGIN_REDIRECT_URL = '/api/admin/'
Enter fullscreen mode Exit fullscreen mode

You can find more information about this setting here.

4. Configure Nginx

Here comes to most tricky part in my search, it took me a while to figure out how to configure Nginx to serve the backend in a subdirectory.

I will explain the configuration in detail, but here's the final configuration:

server {
    listen 80;
    server_name localhost;

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

    # React frontend
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Django API
    location /api/ {
        proxy_pass http://backend:8000/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
Enter fullscreen mode Exit fullscreen mode

I will assume you have basic knowledge of how nginx works so I will ignore the basic configuration and go straight to the relevant parts.

For context, I have built my frontend to /usr/share/nginx/html previously. Nginx is serving those files in the root of the domain.

Let's break it down:

  • location /api/: This is the location block that will serve the Django API in the subdirectory /api/. IMPORTANT! Note that it has both a trailing and leading slash. This is important to make django work correctly in a subdirectory.
  • proxy_pass http://backend:8000/: This is the most important part. It tells Nginx to proxy all requests to /api/ to the backend service running with Docker on port 8000. Cool thing about docker is that we can reference the service by its name, in this case backend.

Now to the headers:

  • Host $host: This sets the Host header to the value of the host header of the request. It basically passes the host header from the client to the backend.
  • X-Real-IP $remote_addr: This sets the X-Real-IP header to the value of the remote address of the client. This is useful for the backend to know the real IP of who is making the request.
  • X-Forwarded-For $proxy_add_x_forwarded_for: This header maintains a chain of IP addresses a request has traversed. It's similar to X-Real-IP but provides more comprehensive information.
  • X-Forwarded-Proto $scheme: This header informs the backend about the protocol (HTTP or HTTPS) used by the client to connect to Nginx. Without this, your Django application wouldn't know if the original request was secure (HTTPS) or not.

Conclusion

That's it! You should now have your Django application running in a subdirectory with Docker and Whitenoise.

I really hope this article saves you some time and headaches!

Top comments (0)