Is your Django project ready for deployment and are you wondering how to best deploy it? You might’ve heard a lot of suggestions and most if it are about using Docker. You’ve heard Docker before and you know it’s one of the ideal ways to deploy your application but you don’t have any idea how to do it. It might even sound like a dream to do it this way and it might seem that it’s hard to figure it out.
Don’t worry, you just need to experience how to do it and it will get easier as you develop in Docker more.
In this post, I’ll guide you through how you can containerize your Django project if you are a beginner and make it production ready.
Prepare your Django Project
Here’s what you need to prepare to get started:
- Your app is running without errors
- Use os.getenv() instead of hard coding the values in settings.py as discussed in #2 of 5 Tips For A Solid Django Development – Deployment Flow
- You are using pipenv and have it installed
- You have Gunicorn installed in your virtual environment (
pipenv install gunicorn)
- gunicorn is a WSGI webserver which serves your Python application i.e. Django
Configure Django to manage static files
Next, we need to configure Django to manage the static files of your app — the CSS, JS, images, etc. We are doing this because we want Nginx to serve the static files which it does efficiently and securely.
To do this, we first need to import os in settings.py:
""" Django settings for core project. Generated by 'django-admin startproject' using Django 3.1.2. For more information on this file, see https://docs.djangoproject.com/en/3.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.1/ref/settings/ """ from pathlib import Path import os # add this
After that, we need to add the STATIC_ROOT value:
STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static')
Lastly, create a directory named “
static” in the same path as
manage.py. Also, put a blank file inside named
This is not required but I do this because I encountered issues with AWS ECS before where Django’s collectstatic command wasn’t able to create the directory and write the static files into it.
If you want to know more about how Django manages static files, this page helps a lot: https://docs.djangoproject.com/en/3.1/howto/static-files/
Create an entrypoint.sh file in the root directory of your project
entrypoint.sh is a script that executes when you run your Docker image
#!/bin/sh python manage.py migrate --no-input python manage.py collectstatic --no-input --clear exec "$@"
Let’s go through what the lines mean.
python manage.py migrate --noinput
Running migrate applies the migrations which means synchronizing the database state and with the current models. We’re running migrate to make sure that the DB and models are always in sync.
The parameter “–no-input” means it will set the interactive shell to False and will always choose True/Yes to the questions. We’re using this parameter so that our application won’t stop in case there’s a prompt from the interactive Django shell.
python manage.py collectstatic --no-input --clear
This command collects the static files and puts them into STATIC_ROOT to be served by Nginx as mentioned above. The –clear option means that we want to remove stale static files. We want to do this because we want our static files to be served by Nginx. More info about collectstatic can be found here.
Create a Dockerfile file
A Dockerfile is a file which contains all commands to build the image.
FROM python:3.8-slim AS base # Setup env ENV LANG C.UTF-8 ENV LC_ALL C.UTF-8 FROM base AS python-deps # Install pipenv and compilation dependencies RUN pip install pipenv # Install python dependencies in /.venv COPY djangoapp/Pipfile . COPY djangoapp/Pipfile.lock . RUN PIPENV_VENV_IN_PROJECT=1 pipenv install --deploy RUN /.venv/bin/python -m pip install --upgrade pip FROM base AS runtime # Create and switch to a new user RUN apt-get update && apt-get install -y make curl RUN useradd --create-home appuser WORKDIR /home/appuser USER appuser # Copy virtual env from python-deps stage COPY --chown=appuser:appuser --from=python-deps /.venv /.venv ENV PATH="/.venv/bin:$PATH" # Install application into container COPY --chown=appuser:appuser djangoapp /home/appuser EXPOSE 8000 ENTRYPOINT ["sh", "entrypoint.sh"] CMD ["gunicorn", "djangoapp.wsgi:application", "--bind", "0.0.0.0:8000", "--timeout", "300", "--worker-tmp-dir", "/dev/shm", "--workers=2", "--threads=4", "--worker-class=gthread"]
It might be a lot right now but to summarize, it builds the image instructing it to:
- Build the image using python:3.8-slim as the base
- Installs pipenv and uses it to install the virtual environment and the Python dependencies
- Copies your application files and source to /home/appuser
- When running, execute entrypoint.sh first
- Then, run the application using Gunicorn, run it via port 8000 and use 2 workers and 4 threads (You can modify these. Fore more info on how many worker, threads and what other worker classes to use, please refer to: Gunicorn – Design)
We’ll discuss more about Dockerfiles in a future post.
Building the image
Make sure you’re in the directory where Dockerfile is found.
docker build --tag <image_name>:latest .
The –tag parameter is the ID of the specific image name that you’ve built. You’ll be using the value when running the docker image.
Running the image
Just for clarify the docker terms:
- Image – your application all packaged up along with the stuff you instructed in the Dockerfile
- Container – the running instance of your image
You can run the image by executing the following in the command line:
docker run -e "Key=Value" -e "AnotherKey=AnotherValue" -p 80:8000 <image_name>:latest
Here’s what the parameters mean:
- -e This defines the environment variables used by your application. Remember when we used environment variables instead of hard coding the values in settings.py? We are doing this because once the image is built, we can’t modify settings.py anymore. We need a way to set the values in settings in case there are changes without having to rebuild the image.
- -p Tells docker to forward the host port 80 to the container’s port 8000. We instructed Gunicorn to listen to port 8000 in our Dockerfile
That’s it, your Django application is now Dockerized. I’ve also created a GitHub repository for your reference: https://github.com/asumawang/django-docker-demo you can use it as a guide on how to easily containerize your Django webapp.