After a long research I have finally found a good solution to deploy Django settings into several stages, which is production, development and common shared settings.
I have also found a good way to deploy Django overall, which I’ll be writing soon.
Just to clarify, there is not standard way of deploying Django, however I think the method I put together is a very good way for deployment.
Where should the settings be stored?
The settings are better accommodated within a settings module rather than a single settings.py
file.
I’ve seen a lot of articles where people are storing everything in one single settings.py
file and then at the bottom they include:
try:
from settings_local import *
except ImportError:
pass
Which means that settings variables from settings_local.py
will override settings from settings.py
. There is two options here and they are not good enough specially if you are using git or any other version control software.
Option 1:
Don’t ever commit settings_local.py
, but in this case you will have to keep your credentials and other sensitive information in settings.py
, not good enough as they are not environment independent.
Option 2:
Same as above, however create a separate settings_local.py
in the production environment and add the credentials and sensitive information there. Not good, because you can not version control and you will have to keep the settings remotely up-to-date, hard to keep track of.
How can I properly implement the settings to accommodate both environments?
This is where the settings module comes in very handy, let’s break it down and create our settings module.
First let’s see the Django directory structure when we create a new app with:
django-admin startproject my_app
Structure:
my_app/
manage.py
my_app/
__init__.py
settings.py
urls.py
wsgi.py
As you can see we have a single settings.py
file.
Now let’s go ahead and create the settings module directory.
Note: The settings directory needs to be at the same level as the settings.py
and don’t forget to create the file __init__.py
, otherwise python will not recognise as a module.
Creating the directory:
cd /Users/<user>/webprojects/my_app/my_app
mkdir settings
touch settings/__init__.py
Now we have the following structure:
my_app/
manage.py
my_app/
__init__.py
settings.py
settings/
__init__.py
urls.py
wsgi.py
As you can see, we now have the settings/
directory and it has the __init__.py
, which we need to make it a module.
Creating the common shared settings file:
touch settings/common.py
Copy everything from settings.py into common.py:
cat settings.py > settings/common.py
Now we have everything from settings.py in our common.py file. Now we can get rid of settings.py because we no longer need it.
rm -rf settings.py
Now we can create the environment independent settings files into our settings module:
touch settings/dev.py
touch settings/prod.py
The settings module is now complete and we have the following structure:
my_app/
manage.py
my_app/
__init__.py
settings/
__init__.py
common.py
dev.py
prod.py
urls.py
wsgi.py
Now we have an environment independent settings module, which is maintainable in both environments.
All you have to do now is include in dev.py
and prod.py
at the very top:
from .common import *
You can now override any settings variable according to the environment you are going to use.
Note: When you want to run the server or execute anything from manage.py
, you will need to specifically tell Django that you want to load the specific settings.
To run the server on the local environment:
python manage.py runserver --settings=settings.dev
and for production:
python manage.py runserver --settings=settings.prod
What sensitive information should be separate from the main settings?
Now we have reached an important question, because the Internet is a hostile place, we don’t want to expose sensitive information and we wan’t to keep this information as hidden as possible from prying eyes.
This applies to database, email, secret key and anything with authentication related information.
Again there are a lot of article there suggesting you create a separate file for sensitive information and read it from settings.
Creating a file at /etc/my_app_settings.txt and setting strict permission is not a bad idea, however I think that environment variables are simpler and good for our use-case.
A good place to keep these environment variables is in the ~/bash_profile
, so let’s go ahead and create:
Note: This will only be applied to the production environment.
vim ~/.bash_profile
and we are going to add:
# My_App specific environment variables
export MY_APP_SECRET_KEY='<The app secret key>'
export MY_APP_DB_USER=''
export MY_APP_DB_NAME=''
export MY_APP_DB_PASSWORD=''
export MY_APP_DB_HOST='127.0.0.1'
export MY_APP_EMAIL_HOST='smtp.gmail.com'
export MY_APP_EMAIL_HOST_USER=''
export MY_APP_EMAIL_HOST_PASSWORD=''
export MY_APP_EMAIL_PORT=587
You might need to log-out and log-back-in for the variables to take effect or simply:
source ~/.bash_profile
Once the environment variables are in place we can go to our prod.py
and implement the following:
import os
SECRET_KEY = os.environ.get('MY_APP_SECRET_KEY')
ALLOWED_HOSTS = [
'domain.com',
]
########## APP CONFIGURATION
INSTALLED_APPS += (
'gunicorn',
)
########## END APP CONFIGURATION
########## DATABASE CONFIGURATION
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.environ.get('MY_APP_DB_NAME'),
'USER': os.environ.get('MY_APP_DB_USER'),
'PASSWORD': os.environ.get('MY_APP_DB_PASSWORD'),
'HOST': os.environ.get('MY_APP_DB_HOST'),
'PORT': '5432',
}
}
########## END DATABASE CONFIGURATION
########## EMAIL SETTING
EMAIL_HOST = os.environ.get('MY_APP_EMAIL_HOST'),
EMAIL_HOST_USER = os.environ.get('MY_APP_EMAIL_HOST_USER'),
EMAIL_HOST_PASSWORD = os.environ.get('MY_APP_EMAIL_HOST_PASSWORD'),
EMAIL_PORT = os.environ.get('MY_APP_EMAIL_PORT'),
EMAIL_USE_TLS = True
########## END EMAIL SETTING
Note: We are currently using os.environ.get('')
to get the environment variables, so you need to import os
for it to work and the settings above is just an example, feel free to adjust as needed.
And that’s it.