I’ve written about the awesome python-decouple module before, now here is a step-by-step description on how to leverage this module to manage your Django settings.

The problem

Django has very easy shortcuts to create projects and “apps” within that project. When you start a project with django-admin startproject blah . it creates a folder named after the project with some basic files and a manage.py to do everything else with your project. If you just did that you can run a development server with manage.py runserver, that command will start up a server on localhost port 8000 which you can use to access your django project while developing. It also implements basic reload-on-change, that is, everytime you change some (python) file the project is using, the server will restart. By default you will get a demo page telling you that your Django project is working.

So far so good. The basic setup of the server, including where and how and even which database to use, what “apps” to load, which middleware you’re using and so on is defined in a file called settings.py. And amongst others, this file also includes settings that should never ever be put on a public site, because with these settings your project will be vulnerable to outside attacks. Of course I’m not only thinking about the database settings, but the all-important SECRET_KEY which wouldn’t be very secret in a public git repository (to name just one example).

The solution

.env

For starters, create a file called .env right next to your manage.py. In this file, just put all the secret stuff you want to keep from prying eyes. Passwords, server names, database names, the secret key or even the debug setting (but I don’t recommend just that, the better way to fix stuff like that is to give your running environment a name like “dev”, “prod” or something like that). After this you have a .env that looks something like this:

ENV=prod
SECRET_KEY=chohyaCei4iex0Aekiu7aephEeSouju1niezeihoh5xee7oh
DB_USER=myprojectuser
DB_PASS=UiQuae3ache8loo4
DB_TYPE=mysql
DB_HOST=localhost
DB_PORT=3306

Note: I recommend you generate your secrets (passwords and keys and such) with something other than the builting django key generator some like to use. The reason is that that generator is not really suited since it uses metacharacters that can wreak havoc with your settings file or even python itself. I’ve had the key generator generate keys with both $ (bad for shells), % (bad for python since it then wants replace it with something outside that string) and ‘=’ (again, bad for shells). Just keep it simple and generate long enough strings with only characters, numbers and maybe a selected few special characters. The length and entropy of the generated string are more important than the inability to read them or use them directly in programs…

I recommend pwgen - as simple as possible and as complex as needed. The strings in my example were generated with pwgen 48 and pwgen 16 respectively.

And, very important, put .env in your .gitignore or whatever your code management software uses. You don’t want this file to be put in your code repository, not even once. It’s also a good practice to provide a .env-example or similar, with dummy values, just so whoever uses your code next knows what they have to set to run it.

If you are using a sane OS to develop on (with symbolic links), having several .env files around for eg. development and production, just symlinking them to .env is one practice I have used a few times. And before releasing you at least want to check that production works as good as the development environment, that goes double if you are using different databases or import extra modules to help with debugging.

modification of settings.py

Now would be a good time to install decouple (remember that it’s called python-decouple on pypi, somebody else used decouple for his mediator module to decouple event handlers. And got one star for it on github…). At the time of me writing this:

$ pip install python-decouple
Collecting python-decouple
  Downloading python_decouple-3.7-py3-none-any.whl (9.9 kB)
Installing collected packages: python-decouple
Successfully installed python-decouple-3.7

or if you use poetry:

$ poetry add python-decouple
Using version ^3.7 for python-decouple

Updating dependencies
Resolving dependencies... (0.4s)

Writing lock file

Package operations: 1 install, 0 updates, 0 removals

  • Installing python-decouple (3.7)

In settings.py import the decouple module:

from decouple import config

Then replace this:

SECRET_KEY = 'django-insecure-t_5%ln&hn%raf5daqbgr(37yba!r)aunsjg+ez(@%s%zj(ancp'

with this:

SECRET_KEY = config('SECRET_KEY', default='changeme')

(btw. since the string is insecure it can just include “insecure” or as in my case “changeme” by default, no need to do any key generation for insecure keys).

Do the same for your database settings, in one project I have used something like this:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': config('DATABASE_NAME', default='mydb'),
        'USER': config('DATABASE_USER', default='root'),
        'PASSWORD': config('DATABASE_PASSWORD', default=''),
        'HOST': config('DATABASE_HOST', default='localhost'),
        'PORT': config('DATABASE_PORT', default='3306'),
    }
}

If you want you can make this database-agnostic and have different settings for eg. mysql, postgres and sqlite3. I thought of it in the above example, but at least this code doesn’t implement that, it’s hardcoded to mysql. And because most people don’t include both mysqlclient and psycopg2 in their projects, that’s probably ok.

Conclusion

python-decouple makes it very easy to put your secrets where nobody sees them. Django’s basic setup doesn’t, and in these times where “everything” is visible in public repositories (at least in the open-source world) protecting your secrets is important.

Also, and I can not stress this enough, if you did by accident publish your secrets, removing them and pushing an update to git is not going to help. Your secret is in your repository’s history, and it will stay there forever. Change the corresponding item now or face the consequences (because your project will be attacked, it’s just a matter of time).