Setting up a Django app on Heroku
Last updated March 4, 2017
This is a simple guide to setting up a Django project on Heroku.
{% raw %}
The first step is to create a virutal environment in a new directory:
$ mkdir proj && cd proj
$ virtualenv -p python3 .
$ source bin/activate
(proj) $ mkdir src
(proj) $ cd src
(proj) $ pip install django==1.10.5
(proj) $ django-admin.py startproject myproj .
(proj) $ ls
myproj manage.py
This sets up a virtual environment and creates an empty Django project. The next step is to create a settings module.
(proj) $ cd myproj
(proj) $ mkdir settings && cd settings
Next we want to add __init__.py
to settings to make it a python module.
src/myproj/settings/init.py
from .base import *
from .production import *
try:
from .local import *
except:
pass
Next we want to change the BASE_DIR
(base directory) in settings.py
:
settings.py
[...]
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
[...]
Next we need to move settings.py
into the settings
folder and rename it base.py
and then copy base.py
twice as local.py
and production.py
these three files will live in settings
.
(proj) $ mv settings.py settings
(proj) $ cd settings
(proj) $ mv settings.py base.py
(proj) $ cp base.py local.py
(proj) $ cp base.py production.py
Next we need to install PostgreSQL:
(proj) $ pip install psycopg2
(proj) $ pip install gunicorn dj-database-url
(proj) $ pip install django-crispy-forms
(proj) $ pip install pillow
At this point we can check to see if everything installed correctly:
(proj) $ pip freeze
appdirs==1.4.3
dj-database-url==0.4.2
Django==1.10.5
django-crispy-forms==1.6.1
gunicorn==19.7.1
olefile==0.44
packaging==16.8
Pillow==4.0.0
psycopg2==2.7.1
pyparsing==2.2.0
six==1.10.0
And then we can add these to a file in our base directory called requirements.txt
:
(proj) $ pip freeze > requirements.txt
Next we can run migrations and create a superuser:
(proj) $ python manage.py migrate
(proj) $ python manage.py createsuperuser
Next we need to initialize our git repository and create .gitignore
:
(proj) $ git init
We can put .gitignore
in our base directory and add the following:
myproj/settings/local.py
We also want to ignore several other python-related files in our directory. An easy way to do this is to add python gitignore. This can be found here.
Next we can make our first commit:
(proj) $ git add --all
(proj) $ git commit -m "initial commit"
The next step involves setting up Heroku. First we need to create a Procfile
in our base directory:
Procfile
web: gunicorn myproj.wsgi --log-file -
Next we can try to run Heroku locally, but first we need to add 0.0.0.0
to ALLOWED_HOSTS
in production.py
, base.py
and local.py
.
We should now see "It worked!" at 0.0.0.0:5000
, which tells us that everything is working properly.
Next we need to create the project on Heroku:
(proj) $ heroku login
(proj) $ heroku create my-unique-project-name-123
Now we can see our project at my-unique-project-name-123.herokuapp.com
, and it should say Heroku | Welcome to your new app!
Next we will want to add my-unique-project-name-123.herokuapp.com
to ALLOWED_HOSTS
in settings.py
.
The very last step is to add the specific version of Python to a file called runtime.txt
in our base directory:
(proj) $ python -V
Python 3.4.3
(proj) $ echo "python-3.4.3" > runtime.txt
Before we push to Heroku we need to change a setting on Heroku related to static files:
(proj) $ heroku config:set DISABLE_COLLECTSTATIC=1
Now we can finally push the git repository to Heroku:
(proj) $ git push heroku master
Now if we go to our site on heroku we should see:
Not Found
The requested URL / was not found on this server.
There is a helpful guide on deploying Python and Django apps on Heroku's website here.
Here's an important excerpt regarding databases:
For Django applications, a Heroku Postgres hobby-dev database is automatically provisioned. This populates the DATABASE_URL environment variable. No add-ons are automatically provisioned if a pure Python application is detected. If you need a SQL database for your app, add one explicitly:
$ heroku addons:create heroku-postgresql:hobby-dev
So we need to run this command:
(proj) $ heroku addons:create heroku-postgresql:hobby-dev
Next we need to access the terminal on our Heroku server:
(proj) $ heroku run bash
Next we need to add database-related information to production.py
under the DATABASES
section:
[...]
import dj_database_url
db_from_env = dj_database_url.config()
DATABASES['default'].update(db_from_env)
[...]
Now we can push to Heroku:
(proj) $ git push heroku master
Next we can run our migrations:
(proj) $ heroku run python manage.py makemigrations
Next we can run migrate
and createsuperuser
on our Heroku server:
(proj) $ heroku run python manage.py migrate && heroku run python manage.py createsuperuser
Now we should be able to login to the admin page with the account we just created, but CSS is still not working at this point. Here's how we configure static files to get CSS to work:
(proj) $ pip install whitenoise
whitenoise
is needed so Heroku can run our static files.
Next we want to make sure to include whitenoise
in requirements.txt
:
(proj) $ pip freeze > requirements.txt
Next we need to add the following item to the list of MIDDLEWARE
components. This needs to be added to BOTH production.py
AND base.py
in order for the local files to show on both our heroku site and the locally served site with heroku local web
:
MIDDLEWARE = [
[...]
'whitenoise.middleware.WhiteNoiseMiddleware',
[...]
]
Next we have a few more items to add to our settings files:
production.py
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
STATIC_ROOT = os.path.join(BASE_DIR, "live-static", "static-root")
STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
#STATIC_ROOT = "/home/cfedeploy/webapps/cfehome_static_root/"
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "live-static", "media-root")
And we also need to add some files to our base directory that will hold our static files:
(proj) $ mkdir static
(proj) $ echo "body {color:#000;}" > static/main.css
(proj) $ mkdir live-static
(proj) $ mkdir live-static/static-root
(proj) $ mkdir live-static/media-root
(proj) $
(proj) $
And we also need to add the following to the end of production.py
:
[...]
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
STATIC_ROOT = os.path.join(BASE_DIR, "live-static", "static-root")
STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
#STATIC_ROOT = "/home/cfedeploy/webapps/cfehome_static_root/"
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "live-static", "media-root")
base.py
and local.py
should have the following:
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
STATIC_ROOT = os.path.join(BASE_DIR, "live-static", "static-root")
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "live-static", "media-root")
Next we want to run collectstatic
locally:
(proj) $ python manage.py collectstatic
We also want to add a blank file to live-static/media-root
so that it becomes tracked in git:
(proj) $ echo "some text" > live-static/media-root/blank.txt
Next we can commit these changes and push to Heroku, and check to see if the static files are working in the admin panel. We also want to set DISABLE_COLLECTSTATIC
to 0
:
(proj) $ git add --all
(proj) $ git commit -m "added static files"
(proj) $ git push heroku master
(proj) $ heroku config:set DISABLE_COLLECTSTATIC=0
Now we should be able to see the admin panel with working CSS on both the live and local heroku sites.
Adding an app and configuring Bootstrap
Now that everything seems to be working we can start building our app.
Let's start by creating a new app:
(proj) $ python manage.py startapp pages
pages
will be the name of an app that we create here.
In pages/views.py
we can add a class-based view that will serve as the homepage:
from django.shortcuts import render
from django.views.generic import View
# Create your views here.
class HomeView(View):
def get(self, request, *args, **kwargs):
return render(request, 'pages/home.html', {})
We then update urls.py
to include our new view:
from django.conf.urls import url
from django.contrib import admin
from services.views import HomeView
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', HomeView.as_view(), name='home'),
]
Next we have to add pages
to INSTALLED_APPS
in both production.py
and local.py
.
Next we need to make some folders within our new pages
app:
(proj) $ mkdir page/templates && cd pages/templates
(proj) $ mkdir pages
(proj) $ touch home.html
Then we need to add the following to home.html
:
{% extends "base.html" %} {% block content %} {% endblock content%}
Next we need to update DIR
in TEMPLATES
in base.py
, local.py
and production.py
:
[...]
'DIRS': [os.path.join(BASE_DIR, 'templates')],
[...]
Next we need to add a templates
folder to the root of our project:
(proj) $ mkdir templates
(proj) $ touch tempates/base.html
Next we can add a basic Bootstrap template to base.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Bootstrap 101 Template</title>
<!-- Latest compiled and minified CSS -->
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous"
/>
<!-- Optional theme -->
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"
integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp"
crossorigin="anonymous"
/>
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<h1>Hello, world!</h1>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<!-- Latest compiled and minified JavaScript -->
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"
></script>
</body>
</html>
This html was taken from Bootstrap's 'Get Started' page.
Now we can run heroku local web
and confirm that we see "Hello, world!" from the Bootstrap template.
{% endraw %}