Passing environment variables into a Dockerised NGINX configuration

Introduction
I was recently working on a small project that required environment variables to be passed into an NGINX Docker container from the host system. The environment variables had to be available to the NGINX config at runtime, so that changes would show up whenever the container was rebuilt with a new value.
I quickly discovered that NGINX configs don't work quite that way straight out of the box, so after a bit of research and experimentation I decided to document my solution and hopefully save someone else the same trouble.
Goals
By the end of this article, you should be able to:
- build a Dockerised NGINX config that uses values passed in from environment variables
- test the functionality of the config using Docker
Prerequisites
- Docker version 17.12.0-ce or higher
Decisions: Lua or Perl
There are actually a couple of different ways to do the same thing here.
If you plan on using Lua, then NGINX seems to recommend that you use the lua-nginx-module available in one of the OpenResty releases which, as stated in the NGINX docs, "....integrate Nginx, ngx_lua, LuaJIT 2.1, as well as other powerful companion NGINX modules and Lua libraries".
I know nothing about Lua, and as my needs were very simple it didn't seem to make sense to stray too far from my tried and trusted NGINX setup, and invest the time in learning about the openresty implementation. On the other hand, if time isn't a factor for you, or you have experience with Lua, then by all means check out what openresty has to offer as it may well have advantages over the Perl module.
With this in mind, I decided that it might be easier to use my existing, though very limited, knowledge of Perl, and instead make use of the ngx_http_perl_module.so that's provided in the 1.13.8-alpine-perl container from NGINX.
Crack open the code
Take a look at my source code, and I'll explain the main points below:
Dockerfile
FROM nginx:1.13.8-alpine-perl
LABEL maintainer="Fizzymatt"
EXPOSE 80
ADD ./nginx/etc/nginx/nginx.conf /etc/nginx/nginx.conf
RUN rm /etc/nginx/conf.d/default.conf
The exact structure of your Dockerfile will vary depending upon the project that you're building, so I won't bother going into the details of this. Just notice that I'm using nginx:1.13.8-alpine-perl as a base container....
FROM nginx:1.13.8-alpine-perl
....and storing my config in /etc/nginx/nginx.conf
ADD ./nginx/etc/nginx/nginx.conf /etc/nginx/nginx.conf
NGINX config
This is the file that we're really interested in (/etc/nginx/nginx.conf).
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
load_module modules/ngx_http_perl_module.so;
env HAPPY_GREETING;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
sendfile off;
server_tokens off;
perl_set $happy_greeting 'sub { return $ENV{"HAPPY_GREETING"}; }';
server {
listen 80;
server_name _;
resolver 8.8.8.8;
location / {
return 200 'the environment variable contains: ${happy_greeting}';
}
}
}
First of all, I tell NGINX to load the "ngx_http_perl_module.so" module. This module is baked into the nginx:1.13.8-alpine-perl image that I used as the base for my Dockerfile, so it can be loaded up straight away without any hassle.
load_module modules/ngx_http_perl_module.so;
The next line of interest is where I create the environment variable that exists within the NGINX container. Out of the box, NGINX provides support for environment variables, but strips away anything that is passed in from outside. I'll deal with that problem later on, but I still need to create a variable to hold the value internally.
env HAPPY_GREETING;
I can now use Perl's perl_set command to grab the value of the environment variable that I'm passing in from outside (when the Docker image is built), and assign it to a variable in the NGINX config.
perl_set $happy_greeting 'sub { return $ENV{"HAPPY_GREETING"}; }';
Finally, I return the value that is stored within the $happy_greeting variable as part of the response from an http route:
return 200 'the environment variable contains: ${happy_greeting}';
Passing values with Docker
Now it's time to use Docker to build an image and spin up a container, so that we can see some proof that all of this actually works.
If you've cloned my repo from Github, then you can open a terminal in the project root and run the following command:
docker build -t nginx-config-env-vars .
Run the following command, and you should now see an image named nginx-config-env-vars listed:
docker images
There are a couple of different ways to pass environment values into a Docker container, but I like to create a file and pass them in from there.
In the project root, you'll notice that there's a file named env.dev. This contains the environment variable and the value that we'll be passing in.
HAPPY_GREETING=Hello, from hackerbox.io :)
Run the following command to spin up the container:
docker run --name nginx-config-env-vars --env-file ./env.dev -p 80:80 -d nginx-config-env-vars
Point your browser at localhost, and you should see a message displayed as shown below:
the environment variable contains: Hello, from hackerbox.io :)
If we change the value of the environment variable and then rebuild the container, we can see that our NGINX config responds to the changes.
First, stop our running container:
docker stop nginx-config-env-vars
Then remove the stopped container:
docker rm nginx-config-env-vars
Change the contents of the env.dev file, so that the HAPPY_GREETING variable is assigned a different value:
HAPPY_GREETING=Hi, from hackerbox.io!
If you bring up the container again....
docker run --name nginx-config-env-vars --env-file ./env.dev -p 80:80 -d nginx-config-env-vars
....then you should see that the message has changed when you point your browser at localhost:
the environment variable contains: Hi, from hackerbox.io!
Summary
With any luck, you now have a working example of a Dockerised NGINX config that will respond to values passed in from the outside.
As I've mentioned, this wasn't the only way that I could've acheived my goal, so have a look around and find the solution that works best for you. Hopefully this article has at least helped you toward that decision though.
Now that you're done.......
If you like indie / punk rock, have a listen to this onBandcamp(orYouTube):
If you liked that article, then why not try:
How to run the Nessus (v7) security scanner in a Docker container
I wanted to test out the latest version of the Nessus security scanner this week, but I couldn't find any ready made Docker images that quite fit the bill. In this article, I take a look at how you can build your own Ubuntu based image to run the "Home" licensed version of Nessus.