Sharing Environmental Variables With Docker & uwsgi
In this post I am going to describe how to share environmental variables between applications running in a uwsgi application server instance and the shell in the same Docker container.
Photo by Valeria Farina on Unsplash because I like lizards.
Background
Consider the following scenario:
A Django project running in a uwsgi application server instance within a Docker container.
The Django project requires access to certain external environmental variables, e.g. DEBUG or LOGS_DIR.
The user requires these same environmental variables to be available to the shell so that they can use Django’s command line tools, such as manage.py, to perform various operations on the project.
Possible solutions include:
Hard code these variables into the Django project’s settings.py file and specify them on the command line when running manage.py, etc.
Hard code these variables into the Django project’s settings.py file and separately hard code them into a startup file for the shell, e.g. ~/.profile.
Keep all environmental variables in a single file and have both Django and the shell import them.
The first solution is cumbersome (remembering and typing out the variables every time you need them). The second solution is less so, but both lead to the possibility of inconsistency if a value is changed in one place but not the other.
This leaves the third solution as the best. In addition, it can allow for quicker switching between development and production environments.
The Solution
Create a file called environment.txt in your Docker build directory. This file should contain your environmental variables as key-value pairs:
# Comment
VAR1=VALUE1
VAR2=VALUE2
...
In the Dockerfile for your container, add the following line to copy this file into a suitable place in the container (I use /project/conf) during the build:
COPY environment.txt /project/conf/
Now the environmental variables will be present in your Docker container. The next step is making them available to the shell.
Create a shell startup file called profile in your Docker build directory and add the following line to it:
export $(grep -v '^#' /project/conf/environment.txt | xargs -0)
When executed, this file will read the environment.txt file and export the key-value pairs contained within it into the shell environment. The -v '^#' argument to grep will skip lines beginning with # so you can use them for comments. Piping the output of grep to xargs -0 allows values to contain spaces (the argument to xargs may vary depending on the distribution you are using).
In the Dockerfile for your container, add the following line to copy this file into the primary user (usually root) directory in your Docker container:
COPY profile /root/.profile
There is, however, one gotcha. In Alpine Linux (and possibly others) the default shell is not a login shell, which means .profile will not be executed when the container is accessed using something like docker exec -it container /bin/sh. To fix this , also add the following to the same Dockerfile:
ENV ENV="/root/.profile"
Django gets its environmental variables from the application server it is running in, so we need uwsgi to also read environment.txt. Add the following to your uwsgi.ini file:
for-readline = /project/conf/environment.txt
env = %(_)
endfor
The final part of this solution is allowing the Django project to also access these variables. Add the following to the top of your Django project’s settings.py file:
def get_env_variable(var_name):
try:
except KeyError:
error_msg = 'Set the {} environmental variable'.format(var_name)
raise ImproperlyConfigured(error_msg)
Then anywhere in settings.py that you need to access an environmental variable, simply use:
VAR1 = get_env_variable('VAR1')
All done!
To-Do
One unsolved problem remains with this solution- if you execute manage.py directly using docker exec, it does not receive the environmental variables:
docker exec -it container command ...
Instead you have to use the more cumbersome:
docker exec -it container /bin/sh -lc 'command ...'
I don’t do this often but the grey matter is searching for a solution nonetheless…
Conclusion
This solution should also apply to other configurations, e.g. different Linux distributions, other uWSGI applications, Python 2 vs. Python 3, etc., with minimal modification.
FYI — don’t use this for passwords and other secrets!
Any comments, suggestions or corrections welcome!
Originally published at www.tunecrew.com on February 14, 2019.