Deploy Django on Heroku the Easy Way

Rubén Chuliá Mena
7 min readJun 9, 2021

--

My momma always said, ‘Deploying is like a box of chocolates. You never know what the hell is going to happen.’

Deploying a Django application on Heroku should be easy and simple. And, to be honest, it is very simple. It is amazing how easy and cheap (actually, free) is to have your application deployed on production. However, it is complicated enough so the Internet is full of tutorials about how to do it.

Once you know how to do it, the information on Heroku development page suddenly seems obvious. But the first time… Oh my… Prepare yourself for wanting to throw your laptop through the window.

It’s not that it is a complex task. It is just that one line of code can make everything break, and Heroku build logs aren’t going to tell you what is going on. You ask them: hey, what’s wrong?, and they will say nothing. Really, what happens?, and the answer is you should know.

I am going to share the way of doing it that has worked the best for me. It is not the only way, but this one just works.

The most problematic issue: static files

What is so difficult about making Django and Heroku get along? Simple answer: static files.

Static files are all those non-HTML files (CSS, JavaScript, images and other files) that are part of your frontend. Without them, your web page would look boring as ****. How does the browser know that it should render the title of your page in pink? Because, in the header of your HTML file, there is probably a line that tells the browser what CSS file (the file that contains the visual style of your web page) to import:

<link rel=”stylesheet” type=”text/css” href=”/static/css/my_pink_style.css”>

or some other similar line.

When the browser receives a line like that, it will perform additional HTTP requests to the backend in order to download those files.

So, what’s the problem? The problem is that Django does not know how to serve static files.

Wait, what?

That’s right.

But I run Django locally and there is no problem with my web page style or animations.

That is right, but it just happens because in the settings.py file, the DEBUG flag is set to True. In debug mode, Django implements a server for static files, but the moment you disbable it,

# settings.py
DEBUG = False

it will stop serving static files and, in addition to a very ugly page that renders the plain HTML, in the browser console you will get some errors similar to

GET http://127.0.0.1:8000/static/admin/js/nav_sidebar.js net::ERR_ABORTED 404 (Not Found)

In other words, Django does not know anything about the static URL.

Usual Solution: Dedicated Web Server

The issue with serving static files in production is usually handled outside Django, by configuring the server manually. One would typically collect all static files in one folder by running the Django command

python manage.py collectstatic

This command will scan all folders where static files are placed (which folders it looks static files for can be configured in the STATICFILES_DIRS variable in settings.py) and copy them into the folder specified by the STATIC_ROOT variable. The usual configuration is:

# settings.py 
STATIC_ROOT = os.path.join(BASE_DIR, ‘staticfiles/’)

Then you must configure the web server (maybe the same server it’s running the Django application, but usually a different dedicated web server) to serve the collected files when a request contains your static URL ( STATIC_URL variable in settings.py), in the URL. For example, this can be done with an Nginx server or an Apache. In general, every production setup is different and will require slightly different steps.

More Compact Solution: WhiteNoise as Middleware

Heroku is a PaaS (platform as a service). This type of services allow developers to focus on their applications and not worry about infrastructure: server configuration, scalability… Therefore, we must look for another way to serve static files. The magic solution is called Whitenoise.

Whitenoise is a web middleware. According to the Django documentation:

Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or output.

In plain words, it is a piece of software that is executed before the request arrives to the view function in Django, and after the response is generated, so it can apply some preprocessing or postprocessing. This means that with a middleware we can check if the request URL is defining a static file (i.e. contains the STATIC_URL) and in that case we don't forward the requiest to Django because it does not know what to do with that. Instead, we perform some other action to serve the requested file.

That is precisely what Whitenoise does (in addition to other cool functionalities like serving compressed content). They state it clearly on their documentation:

With a couple of lines of config WhiteNoise allows your web app to serve its own static files, making it a self-contained unit that can be deployed anywhere without relying on nginx, Amazon S3 or any other external service. (Especially useful on Heroku, OpenShift and other PaaS providers.)

This is exactly what we want. Installing and configuring WhiteNoise is not difficult and there are many tutorials that already explain how to do it. But in Heroku, things can be even simpler than that.

Fancy solution in Heroku: django-heroku

Deploying on Heroku requires more than just taking care of static files. You must also configure databases, logging, the allowed_hosts… There is a single library that takes care of all of that for you: django-heroku.

Once you install it, it will install automatically WhiteNoise and will configure all necessary variables automatically. Then, the only modifications you must include in your settings.py are:

# settings.py
import django_heroku
DEBUG = False
django_heroku.settings(locals())

That’s it?

That’s it.

In case you want to use your own configuration for, let’s say, logging, you can disable the automatic configuration for that area:

django_heroku.settings(locals(), logging=False)

Deploying

Once you have your settings file modified in the previous way, you are almost there.

You also need to:

  • Add django-heroku to requirements.txt
  • Add gunicorn to requirements.txt
  • Create Procfile
  • Deploy

You must install Gunicorn, the production web server that Heroku recommends for Django applications.

$ pip install gunicorn

Once installed, add it and django-heroku to requirements.txt with:

$ pip freeze > requirements.txt

Then you also need a Procfile for declaring your applications process types and entry points. A simple yet perfectly working Procfile is

release: python manage.py migrate 
web: gunicorn my_django_application.wsgi — log-file -

Finally, in order to deploy your code to Heroku, there are multiple options (we assume you have a Heroku account and have created a Heroku app). You could push your code to a GitHub repository and then you can use Heroku’s Connect to GitHub function. However, a faster way is to just commit your changes locally and push your changes to Heroku using Heroku Git.

To configure Heroku Git, you must first install Heroku CLI, and log in

$ heroku login

Then you can add a remote to your local repository with

$ heroku git:remote -a my_heroku_app_name

The last step is to just do the push

$ git push heroku master:main

and wait for the process to complete. If it has gone well, the output should look somewhat similar to

$ git push heroku master:main
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 8 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 496 bytes | 496.00 KiB/s, done
Total 6 (delta 4), reused 0 (delta 0)
remote: Compressing source files… done.
remote: Building source:
remote:
remote: — — -> Building on the Heroku-20 stack
remote: — — -> Using buildpack: heroku/python
remote: — — -> Python app detected
remote: — — -> No Python version was specified. Using the same version as the last build: python-3.9.5
remote: To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes
remote: — — -> No change in requirements detected, installing from cache
remote: — — -> Using cached install of python-3.9.5
remote: — — -> Installing pip 20.2.4, setuptools 47.1.1 and wheel 0.36.2
remote: — — -> Installing SQLite3
remote: — — -> Installing requirements with pip
remote: — — -> $ python manage.py collectstatic — noinput
remote: 129 static files copied to ‘/tmp/build_aafe4c62/staticfiles’, 411 post-processed.
remote:
remote: — — -> Discovering process types
remote: Procfile declares types -> release, web
remote:
remote: — — -> Compressing…
remote: Done: 60.8M
remote: — — -> Launching…
remote: ! Release command declared: this new release will not be available until the command succeeds.
remote: Released v13
remote: https://my-heroku-app-name.herokuapp.com/ deployed to Heroku remote:
remote: Verifying deploy… done.
remote: Running release command…
remote:
remote: Operations to perform:
remote: Apply all migrations: admin, auth, contenttypes, sessions remote: Running migrations:
remote: No migrations to apply.
remote: Waiting for release…. done. To https://git.heroku.com/my-heroku-app-name.git 4b7d12e..7fd2a31 master -> main

It is very important that you see that the collectstatic command has been executed successfully

remote: — — -> $ python manage.py collectstatic — noinput
remote: 129 static files copied to ‘/tmp/build_aafe4c62/staticfiles’, 411 post-processed.

If you don’t see it, there is something that has gone wrong. Heroku build log is not very expressive regarding this issue, and if you don’t pay attention, you might think everything was fine. But then you go to the web page https://my-heroku-app-name.herokuapp.com/ (it is just an example I used, just use your app URL) and see some puzzling error like a 500 Internal Error and have no idea of what could have happened.

However, unless something tricky happens, the described process should be enough to deploy successfully your Django application to Heroku.

Originally published at https://www.listeningtothedata.com.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Rubén Chuliá Mena
Rubén Chuliá Mena

Written by Rubén Chuliá Mena

Artificial Intelligence Engineer with experience in ML for bank and insurance sector. I strive with passion to make a meaningful impact in the industry.

Responses (1)

Write a response