I am deploying a Django app to Heroku using Docker. When I put RUN manage.py collectstatic --noinput
in the Dockerfile, it fails because there is no value set for the environment variable DJANGO_SECRET_KEY
. My understanding is that this is because config vars aren't available during build time.
When I run collectstatic as a release command, it works without error, and successfully copies the static files. However, when I hit the app url, it returns a 500 error because the static files can't be found. I believe this is because the release command is run as a dyno on an ephemeral filesystem, and the copied files are therefore not found.
It seems to be a catch-22. Putting collectstatic in the Dockerfile fails because there are no config variables available, but putting it as a release command fails because only file changes from the build phase are saved?
What to do?
Here are my collectstatic settings in settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
...
]
...
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
STATICFILES_STORAGE = 'backend.storage.WhiteNoiseStaticFilesStorage'
Dockerfile
# Pull base image
FROM python:3.7-slim
# Set environment varibles
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# Set work directory
RUN mkdir /code
WORKDIR /code
# Install dependencies
RUN pip install pipenv
COPY Pipfile Pipfile.lock /code/
RUN pipenv install --system
# Copy project
COPY . /code/
## collect static files
RUN mkdir backend/staticfiles
# This fails because DJANGO_SECRET_KEY can't be empty
RUN python manage.py --noinput
heroku.yml
build:
docker:
web: Dockerfile
run:
web: gunicorn backend.config.wsgi:application --bind 0.0.0.0:$PORT
Heroku Container Registry allows you to deploy your Docker images to Heroku. Both Common Runtime and Private Spaces are supported. If you would like Heroku to build your Docker images, as well as take advantage of Review Apps, check out building Docker images with heroku. yml.
Hopefully, by the end of this guide, you'll be able to host your Django project on Heroku, which is a great cloud service, especially because Heroku is a cloud PaaS (Platform as a Service) and also offers a free plan with which you can deploy your project with a full database provision.
After confirming with Heroku support, this does indeed appear to be a bit of a catch-22.
The solution was to put collectstatic
in the Dockerfile so that it runs during build time and the files persist.
We got around not having a secret key config var by setting a default secret key using the get_random_secret_key
function from Django.
The run phase uses the secret key from the Heroku config vars, so we aren't actually changing the secret key every time -- the default only applies to the build process. collectstatic
doesn't index on the secret key, so this is fine.
In settings.py
from django.core.management.utils import get_random_secret_key ... SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', default=get_random_secret_key())
I don't use heroku so can't test, but you should be able to run collect static before you run the app;
Dockerfile
# Pull base image FROM python:3.7-slim # Set environment varibles ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 # Set work directory WORKDIR /code/ # Install dependencies RUN pip install pipenv COPY Pipfile Pipfile.lock . RUN pipenv install --system # Copy project COPY . . # Collect static files RUN python manage.py collectstatic --noinput # run gunicorn CMD gunicorn hello_django.wsgi:application --bind 0.0.0.0:$PORT
You could also not run collectstatic
in your dockerfile, or event run the application because these can be ran by heroku.yml
, for example;
build: docker: web: Dockerfile config: DJANGO_SETTINGS_MODULE: project.settings run: web: gunicorn backend.config.wsgi:application --bind 0.0.0.0:$PORT release: image: web command: - python manage.py collectstatic --noinput
You also shouldn't need to mkdir
for your working directory. Just set WORKDIR /code/
early in your dockerfile and after that things will run based on that directory.
There's a decent article on this here; https://testdriven.io/blog/deploying-django-to-heroku-with-docker/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With