This site looks a lot better with CSS turned on. Note: IE<10 is not supported.

an article is displayed.

Passing environment variables into a Dockerised NGINX configuration

The NGINX and Docker logos

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.

Spoiler alert: I end up using NGINX's Perl module

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.

I used the Alpine container so that I could keep things nice and slim, but you'll probably find that any of the *-perl containers will do the job.

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.

The name of the internal environment variable needs to be the same as the environment variable that is being passed in from outside

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

Notice that we give the container a name (nginx-config-env-vars), tell Docker to pass in variables from our env.dev file, using the --env-file parameter, and also open port 80

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.

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.