Going to production

This section covers everything you need to know before going to production.



Before going to production make sure you have checked everything:

  1. Migrations are up-to-date

  2. Static files are all present

  3. There are no security or other django warnings

Checking migrations, static files, and security is done inside ci.sh script.

We check that there are no unapplied migrations:

python manage.py makemigrations --dry-run --check

If you have forgotten to create a migration and changed the model, you will see an error on this line.

We also check that static files can be collected:

DJANGO_ENV=production python manage.py collectstatic --no-input --dry-run

However, this check does not cover all the cases. Sometimes ManifestStaticFilesStorage will fail on real cases, but will pass with --dry-run option. You can disable --dry-run option if you know what you are doing. Be careful with this option, when working with auto-uploading your static files to any kind of CDNs.

That’s how we check django warnings:

DJANGO_ENV=production python manage.py check --deploy --fail-level WARNING

These warnings are raised by django when it detects any configuration issues.

This command should give not warnings or errors. It is bundled into docker, so the container will not work with any warnings.

Static and media files

We use /var/www/django folder to store our media and static files in production as /var/www/django/static and /var/www/django/media. Docker uses these two folders as named volumes. And later these volumes are also mounted to caddy with ro mode so it possible to read their contents.

To find the exact location of these files on your host you will need to do the following:

docker volume ls  # to find volumes' names
docker volume inspect VOLUME_NAME

Sometimes storing your media files inside a container is not a good idea. Use CDN when you have a lot of user content or it is very important not to lose it. There are helper libraries to bind django and these services.

If you don’t need media files support, just remove the volumes.


We do run migration in the gunicorn.sh by default. Why do we do this? Because that’s probably the easiest way to do it. But it clearly has some disadvantages:

  • When scaling your container for multiple nodes you will have multiple threads running the same migrations. And it might be a problem since migrations do not guarantee that it will work this way.

  • You can perform some operations multiple times

  • Possible other evil things may happen

So, what to do in this case? Well, you can do whatever it takes to run migrations in a single thread. For example, you can create a separate container to do just that. Other options are fine as well.


Sometimes using postgres inside a container is not a good idea. So, what should be done in this case?

First of all, move your database docker service definition inside docker-compose.override.yml. Doing so will not affect development, but will remove database service from production. Next, you will need to specify extra_hosts to contain your postgresql address. Lastly, you would need to add new hosts to pg_hba.conf.

Here is a nice tutorial about this topic.


Let’s Encrypt

We are using Caddy and Let's Encrypt for HTTPS. The Caddy webserver used in the default configuration will get you a valid certificate from Let's Encrypt and update it automatically. All you need to do to enable this is to make sure that your DNS records are pointing to the server Caddy runs on.

Read more: Automatic HTTPS in Caddy docs.

Caddyfile validation

You can also run -validate command to validate Caddyfile contents.

Here’s it would look like:

docker compose -f docker-compose.yml -f docker/docker-compose.prod.yml \
  run --rm caddy -validate

This check is not included in the pipeline by default, because it is quite long to start all the machinery for this single check.

Disabling HTTPS

You would need to disable https inside Caddy and in production settings for Django. Because Django itself also redirects to https. See docs.

You would also need to disable manage.py check in docker/ci.sh. Otherwise, your application won’t start, it would not pass django’s security checks.

Disabling WWW subdomain

If you for some reason do not require www. subdomain, then delete www.{$DOMAIN_NAME} section from Caddyfile.

Third-Level domains

You have to disable www subdomain if your app works on third-level domains like:

  • kira.wemake.services

  • support.myapp.com

Otherwise, Caddy will server redirects to www.example.yourdomain.com.

Further reading