Serving static and media files using Nginx in Django.
What is Nginx?
Nginx is a web server that can also be used as a reverse proxy, load balancer, mail proxy, and HTTP cache.
Static files ->Files that are not affected by the server code. For example, CSS and Image files used in a landing page.
Media files -> Files uploaded by users.
Setup Django
- Install Django and create an app
# install django
pip install django
# install gunicorn - python WSGI HTTP server
pip install gunicorn
# create project
django-admin startproject project .
# create app
python manage.py startapp myapp
# inside settings.py
# add myapp to INSTALLED_APPS
INSTALLED_APPS = [
..., # default apps
"myapp",
]
2. Inside myapp/models.py create a model
The purpose of this model is to test if NGINX will serve media files.
# myapp/models.py
from django.db import models
class FootballPlayer(models.Model):
name = models.CharField(max_length=200)
img = models.ImageField()
3. Setup up settings for static and media files
# settings.py
STATIC_URL = "static/" # e.g. localhost:80/static/styles.css
MEDIA_URL = "media/" # e.g. localhost:80/media/image.jpg
# directory where all static files of the app are going to be put
STATIC_ROOT = "/vol/static"
# directory where all files uploaded by users(media files) are going to be put
MEDIA_ROOT = "/vol/media"
Setup Docker and docker-compose
- Create a Dockerfile for the Django application
# Dockerfile
FROM python:3.11.5-slim-bullseye
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
WORKDIR /app
COPY . /app/
COPY ./requirements.txt /app/
RUN pip install -r requirements.txt
ENTRYPOINT [ "sh", "-c", "./scripts/start.sh" ]
2. Create a scripts directory in the project root directory. Inside it, create a start.sh file.
This bash script will run once we spin up a Django container.
#!/bin/sh
python manage.py makemigrations
python manage.py migrate
# collects all static files in our app and puts it in the STATIC_ROOT
python manage.py collectstatic --noinput
gunicorn project.wsgi -b 0.0.0.0:8000
3. Create a proxy directory in the project's root directory.
Setup NGINX
a) Create default.conf.tpl
In Nginx, we use server block to set up configuration for virtual server.
For this case, we have one server block listening for connections in port 80.
Requests with the prefix /static will be directed to /vol/static, where we will put all our static files.
Requests with the prefix /media will be directed to /vol/media, where we will put all our media files.
Any other request without prefix /static or /media will be forwarded to http://app:8000, where our web application runs.
# proxy/default.conf.tpl
server {
listen ${LISTEN_PORT};
location /static {
alias /vol/static;
}
location /media {
alias /vol/media;
}
location / {
proxy_pass http://${APP_HOST}:${APP_PORT};
include /etc/nginx/proxy_params;
}
}
b) Create proxy params
These parameters are used to forward information to the application you’re proxying to. For example, here request headers such as Host are forwarded to the server we are proxying to.
# proxy/proxy_params
proxy_set_header Host $http_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;
c) Create a Dockerfile
# proxy/Dockerfile
FROM nginx
ENV APP_HOST=app
ENV APP_PORT=8000
ENV LISTEN_PORT=80
COPY ./start.sh /start.sh
RUN chmod +x /start.sh
RUN touch /etc/nginx/conf.d/default.conf
COPY ./proxy_params /etc/nginx/
COPY ./default.conf.tpl /etc/nginx/
ENTRYPOINT [ "sh", "-c", "/start.sh" ]
d) Create start.sh script
This bash script is executed when we spin a container.
envsubst command substitutes all environment variables in /etc/nginx/default.conf.tpl with their values then the output is redirected to /etc/nginx/conf.d/default.conf
#!/bin/sh
set -e
envsubst < /etc/nginx/default.conf.tpl > /etc/nginx/conf.d/default.conf
# start nginx with the dameon running in the foreground
nginx -g "daemon off;"
4. Create a docker-compose.yml file in the project’s root directory.
This file will start two services i.e. app and proxy, each running in a docker container.
The two services can communicate using their service name since they are connected to the same docker network. For example, when we want to forward an HTTP request from the proxy service to the app service, we will use http://app:8000
Docker volumes are used to persist data generated and used by Docker containers.
In this case, we will map all the static files and media files generated by our application to a docker volume then all those static and media files will be mapped from the docker volume to a path in the nginx container.
For static files, we map them to /vol/static/ path of the nginx server.
For media files, we map them to /vol/media/ path of the nginx server.
# docker-compose.yml
version: "3.8"
services:
app:
build:
context: .
restart: always
volumes:
- static-data:/vol/static
- media-data:/vol/media
proxy:
build:
context: ./proxy
restart: always
volumes:
- static-data:/vol/static
- media-data:/vol/media
ports:
- 80:80
depends_on:
- app
volumes:
static-data:
media-data:
Spin up the services
docker-compose up
Visit localhost:80/admin to check if the static files are served.
Create a superuser to log in
docker ps
# use container id of the app service
docker exec -it <container_id> bash
# inside the terminal of the app service
python manage.py createsuperuser
Login to the admin dashboard and create a FootballPlayer object. Afterward, click the link to the image to check if nginx served the image uploaded.
GitHub code -> https://github.com/CodeWithRayOrg/django-nginx-staticfiles-n-mediafiles
Watch video here -> https://youtu.be/fXdIQvser5Q
Resources
https://nginx.org/en/docs/http/ngx_http_core_module.html — Learn about directives.
http://nginx.org/en/docs/http/request_processing.html — How nginx processes a request
https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-server-blocks-virtual-hosts-on-ubuntu-16-04 — How to setup Nginx server blocks
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#proxies — Learn about the headers that are proxied to the application