Django settings management with decouple
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).