Skip to content

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

Published: at 09:04 AM

Cover

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

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

project/
├── backend/
│   ├── Dockerfile
│   └── other django stuff...
├── frontend/
│   ├── Dockerfile
│   ├── default-nginx.conf
│   └── other frontend stuff...
└── docker-compose.yml

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

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'

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"

You need to modify it to include the subdirectory:

STATIC_ROOT = BASE_DIR / "staticfiles"
STATIC_URL = "{}/static/".format(FORCE_SCRIPT_NAME)

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

WHITENOISE_STATIC_PREFIX = "/static/"

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/'

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;
    }
}

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:

Now to the headers:

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!