When you have an application that will be deployed on multiple sites or in multiple environments (e.g. local development, staging, QA, production), one of the easiest ways to deal with environment-specific variables like database name/user/password or secret key is to let them be set by the server environment.
To use Django as an example, in your settings.py you'd do something like:
import os
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.environ['DB_NAME'],
'USER': os.environ['DB_USER'],
...
}
}
When developing, you can then set DB_NAME and DB_USER environment variables in your project IDE and/or interpreter.
However, when you deploy remotely and Django is running under a web server like Apache or nginx using mod_wsgi, you have to have the web server pass the environment variables. [1] In an Apache VirtualHost configuration it would look like this:
<VirtualHost *:80>
ServerAdmin webmaster@example.com
ServerName example.com
ServerAlias example.com
SetEnv DB_NAME mydatabase
SetEnv DB_USER mydbuser
SetEnv DB_PASSWD sekrit
SetEnv DB_HOST localhost
DocumentRoot /path/to/public_html/
WSGIScriptAlias / /path/to/app/app_name/wsgi.py
Alias /static /path/to/public_html/static
Alias /media /path/to/public_html/media
ErrorLog /path/to/logs/error.log
CustomLog /path/to/logs/access.log combined
</VirtualHost>
We're almost there. When you reload Apache and run your application, you will get a KeyError when your settings.py tries to access os.environ[DB_NAME]. This is because mod_wsgi does not pass OS environment variables to the underlying application by default. You will need to have it set them manually. Here is a sample wsgi.py file for Django:
import os, site, sys
site.addsitedir('/usr/local/virtualenvs/MYAPP-VIRTUALENV/lib/python2.7/site-packages')
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.join(BASE_DIR, '..'))
os.environ["DJANGO_SETTINGS_MODULE"] = "myapp.settings" # see footnote [2]
from django.core.wsgi import get_wsgi_application
_application = get_wsgi_application()
env_variables_to_pass = ['DB_NAME', 'DB_USER', 'DB_PASSWD', 'DB_HOST', ]
def application(environ, start_response):
# pass the WSGI environment variables on through to os.environ
for var in env_variables_to_pass:
os.environ[var] = environ.get(var, '')
return _application(environ, start_response)
The variables we declare in Apache are passed to the WSGI application via its environ parameter. We simply explicitly set these in os.environ so that the underlying Django application can access them. (Also see footnote [2] regarding the annotated line in the above code.)
If you don't want to explicitly declare the variables to pass, you can preface each with MYAPP_ in the VirtualHost configuration, then do:
for key in environ:
if key.startswith('MYAPP_'):
os.environ[key] = environ[key]
For Flask users, the same wsgi.py would look something like:
import os, site, sys
site.addsitedir('/usr/local/virtualenvs/MY-VIRTUALENV/lib/python2.7/site-packages')
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.join(BASE_DIR, '..'))
def application(environ, start_response):
for key in ['DB_NAME', 'DB_USER', 'DB_PASSWD', 'DB_HOST', 'SECRET_KEY', ]:
os.environ[key] = environ.get(key, '')
from myapp.application import app as _application
return _application(environ, start_response)
Note that the Flask app is imported inside the def application block — if you import it outside of this block, you won't be able to use the environment variables at the Flask app level or any file which is imported on application load. This is because the WSGI application hasn't loaded at the time you import the Flask application, so it can't pass the environment variables yet.
Wrapping Up
Hope this helps! This is a very common use pattern for me, and one for which I didn't find very useful documentation when I first started looking for a solution. As always, comments and questions are welcome.
Footnotes
[1] | If your app is the only one running on this server which will need these environment variables, you can of course set them system- or interpreter-wide. In this case we're assuming that we need to set them on a per-VirtualHost basis. |
[2] | Django's wsgi.py template has this line as os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapp.settings"). To see why this can bite you, see this StackOverflow answer by mod_wsgi's creator Graham Dumpleton. |