How to deploy Django project to Heroku using Docker

Last updated on Sep 04 2019 by Michael Yin

Introduction

In this Django Heroku guide, I will talk about how to deploy Django project to Heroku using Docker.

So after you read it, you will get:

  1. The difference between Heroku Buildpacks and Heroku Container.

  2. How to serve Django static assets and media files in Heroku.

  3. How to test Docker image for Django project in local env.

  4. How to build front-end stuff for Django project when deploying.

The source code of this tutorial is django-heroku. I would appreciate that if you could give it a star.

Heroku Buildpacks and Heroku Container

There are mainly two ways for you to deploy Django project to Heroku

One way is the buildpacks.

Buildpacks are responsible for transforming deployed code into a slug, which can then be executed on a dyno

You can see Buildpacks as some pre-defined scripts maintained by Heroku team which deploy your Django project. They usually depend on your programming languages.

Buildpacks is very easy to use so most Heroku tutorial would like talk about it.

Another way is using Docker.

Docker provides us a more flexible way so we can take more control, you can install any packages as you like to the OS, or also run any commands during the deployment process.

For example, if your Django project use NPM as front-end solution (this is popular now), you want to npm install some dependency packages during the deployment process or wnat to run custom npm build command, Docker seems more clean solution even buildpacks can do this. (buildpacks can also do this but still have some limitations)

What is more, Docker let you deploy project in a way most platforms can support, it can save time if you want to migrate it to other platforms such as AWS, Azure in the future.

Build manifest and Container Registry

Some people are confused about Build manifest and Container Registry in Heroku Docker doc. So let me explain here.

Container Registry means you build docker in local, and then push the image to Heroku Container Registry. Heroku would use the image to create a container to host your Django project.

Build manifest means you push Dockerfile and Heorku would build it, run it in standard release flow.

What is more, Build manifest support some useful Heorku built-in features such as Review Apps, Release.

If you have no special reason, I strongly recommend using Build Manifest way to deploy Django.

Docker Build vs Docker Run

So what is the difference between Docker build and Docker run

Docker build builds Docker images from a Dockerfile.

A Dockerfile is a text document that contains all the commands a user could call on the command line to build an image.

Docker run create a writeable container layer over the specified image,

So we should first use docker build to build the docker image from Dockerfile and then create container over the image.

Step1: Start to write Dockerfile

Now you already have basic understanding of the Heroku docker, so now let's learn more about Dockerfile,

Here I would use django_heroku as an example to show you how to write Dockerfile.

# Please remember to rename django_heroku to your project directory name
FROM python:3.6-stretch

# WORKDIR sets the working directory for docker instructions, please not use cd
WORKDIR /app

# sets the environment variable
ENV PYTHONUNBUFFERED=1 \
    PYTHONPATH=/app \
    DJANGO_SETTINGS_MODULE=config.settings.production \
    PORT=8000 \
    WEB_CONCURRENCY=3

EXPOSE 8000

# Install operating system dependencies.
RUN apt-get update -y && \
    apt-get install -y apt-transport-https rsync gettext libgettextpo-dev && \
    curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
    apt-get install -y nodejs &&\
    rm -rf /var/lib/apt/lists/*

# start to compile front-end stuff
WORKDIR django_heroku/static_src

# Install front-end dependencies.
COPY ./django_heroku/static_src/package.json ./django_heroku/static_src/package-lock.json ./
RUN npm install

# Run custom npm commadn to compile static assets such as js, SCSS
COPY ./django_heroku/static_src/ ./
RUN npm run build:prod

# Install Gunicorn.
RUN pip install "gunicorn>=19.8,<19.9"

# start to install backend-end stuff
WORKDIR /app

# Install Python requirements.
COPY requirements.txt .
RUN pip install -r requirements.txt

# Copy application code.
COPY . .

# Install assets
RUN python manage.py collectstatic --noinput --clear

# Run application
CMD gunicorn config.wsgi:application
  1. Please note that WORKDIR docker instruction, it would sets the working directory for docker instructions (RUN, COPY, etc.) It is very like the cd command in shell, but you should not use cd in Dockerfile.

  2. Since you already know what is docker run and docker build, I want to say nearly all docker instructions above would be executed in docker build. The only exception is the last CMD, it would be excuted in docker run.

  3. We use ENV to set the default env variable.

  4. As you can see, I use RUN npm run build:prod to help me build front-end assets.

  5. If you use pipenv in your project, you can change the pip install command part.

Step2: Build Docker image

After you create Dockerfile and put it at root of your Django project, it is recommended to test it in local, this can save you time if something is wrong.

PS: If you have not installed Docker, please check this Docker install Doc

$ docker build -t django_heroku:latest .

Step3: Docker run

If the docker image has been built without error, here we can keep checking in local env

$ docker run -d --name django-heroku-example -e "PORT=9000" -p 9000:9000 django_heroku:latest
# Now visits http://127.0.0.1:9000/admin/
  1. In Dockerfile, we use ENV to set default env variable in docker run, but we can still use -e "PORT=9000" to overwrite the env in run command.

  2. -p 9000:9000 let us can visits the 9000 port in container through 9000 in host machine.

  3. Let's check the project files now

$ docker exec django-heroku-example ls /app

Dockerfile
config
db.sqlite3
django_heroku
manage.py
requirements.txt
static_root

As you can see, a local db.sqlite3 is created because we did not set remote DB ENV. I will talk about it in a bit.

After you finish testing, remember to stop and remove the local docker container

$ docker stop django-heroku-example
$ docker rm django-heroku-example

Step4: Serving static assets on Heroku

Django only serve media files and static assets in dev mode, so I will show you how to serve them on Heroku in production mode.

To serve static assets, we need to use a 3-party package. whitenoise.

$ pip install whitenoise

Edit MIDDLEWARE in settings/base.py, put it above all other middleware apart from Django’s SecurityMiddleware:

MIDDLEWARE = [
   'django.middleware.security.SecurityMiddleware',
   'whitenoise.middleware.WhiteNoiseMiddleware',
   # ...
]

Set in settings/production.py

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

That is all the config you need.

  1. After python manage.py collectstatic --noinput --clear in Dockerfile executed in docker build, all static assets would be put to static_root directory.

  2. After docker run excecuted on Heroku, Django can serve static assets with the help of whitenoise

Step5: Serving media files on Heroku

Because all changes to docker container would be lost during redploy. So we need to store our media files to some other place instead of Heroku Docker container.

The popular solution is to use Amazon S3 storage, because the service is very stable and easy to use.

  1. If you have no Amazon service account, please go to Amazon S3 and click the Get started with Amazon S3 to signup.

  2. Login AWS Management Console

  3. In the top right, click your company name and then click My Security Credentials

  4. Click the Access Keys section

  5. Create New Access Key, please copy the AMAZON_S3_KEY and AMAZON_S3_SECRET to notebook.

If you are new to Amazon and have no idea what is IAM user, you can skip it and set permissions later.

Next, we start to create Amazon bucket on S3 Management Console, please copy Bucket name to notebook.

Bucket in Amazon S3 is like top-level container, every site should have its own bucket, and the bucket name are unique across all Amazon s3, and the url of the media files have domain like {bucket_name}.s3.amazonaws.com.

Now let's config Django project to let it use Amazon s3 on Heroku.

$ pip install boto3
$ pip install django-storages

Add storages to INSTALLED_APPS in settings/base.py

Add config below to settings/production.py

AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_S3_FILE_OVERWRITE = False

MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
  1. To secure your Django project, please set AWS_STORAGE_BUCKET_NAME, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in env instead of project source code (I will show you how to do it in Heroku in a bit)

  2. AWS_S3_FILE_OVERWRITE plesae set it to False, so this can let the storage handle duplicate filenames problem. (I do not understand why so many blog posts did not mention this)

Step6: Remote DB support

Heroku support many different dbs, you can choose what you like and add it to your Heroku instance. (I recommend PostgreSQL)

In Heroku, the DB connection string is attached as ENV variable. So we can config our settings in this way.

pip install dj-database-url

Set in settings/production.py

import dj_database_url

if "DATABASE_URL" in env:
    DATABASES['default'] = dj_database_url.config(conn_max_age=600, ssl_require=True)

Here dj_database_url would convert DATABASE_URL to Django db connection dict for us.

Step7: heroku.xml

heroku.yml is a manifest you can use to define your Heroku app.

Please create a file at the root of the directory

build:
  docker:
    web: Dockerfile
release:
  image: web
  command:
    - django-admin migrate --noinput
  1. As you can see, in build stage, docker would build web image from the Dockerfile.

  2. In release stage, migrate command would run to help us sync our database.

Step8: Deploy the Django project to Heroku

Now, let's start deploy our Django project to Heorku.

First, we go to Heroku website to login and create a app.

After we create the app, we can get the shell command which can help us deploy the project to Heroku.

Then we start to config and deploy in terminal.

$ heroku login
$ git init
$ heroku git:remote -a django-heroku-docker
$ heroku stack:set container -a django-heroku-docker

# git add files and commit
$ git push heroku master
  1. heroku stack:set container is important here because it would tell Heroku to use container instead of buildpacks to deploy the project.

  2. You can find the domain of your Heroku app in settings tab. (Heroku has free plan so you can test and learn as you like)

Step9: Add DB add-on

Now you can add db add-on to your Heroku instance, so data of your Django project would be persistent.

  1. Go to the overview tab of your Heroku project, click Configure Add-ons

  2. Search Heroku Postgres and click Provision button.

  3. Now go to settings tab of your Heroku project, click the Reveal Config Vars

  4. You will see DATABASE_URL, it is ENV variable of your Heroku project and you can add AWS S3 config to make the Django project can serve media files.

NOTE: Heroku CLI is very powerful, and the add-on operation can also be done in terminal, considering readers of this post have not much expereince on Heroku, screenshots might be better here.

Tips

You can add code below to your Dockerfile. (put it on top)

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
  1. PYTHONDONTWRITEBYTECODE would make Python not write .pyc file.

  2. PYTHONUNBUFFERED would make terminal output more better and not buffered.

You Django project should also work fine even you did not set them in your Dockerfile.

Conclusion

In this Django Heorku tutorial, I talked about how to deploy Django project to Heroku using Docker.

You can find the source code django-heroku. I would appreciate that if you could give it a star.

So what should you go next?

  1. Take a look at how to use NPM as front-end solution with Django

  2. In the next Django Heroku tutorial, I will talk about how to deploy from Gitlab to Heroku and I will also give you some useful tips.

  3. If you still have any question, please feel free to contact us.

Thx.

Michael is a passionate Python developer from China and love writing code, articles about Django and Wagtail CMS.

Michael Yin

Full Stack Developer

Need any Help in your Project?

Sign up to our newsletter