374

I have a docker container running Nginx, that links to another docker container. The host name and IP address of the second container is loaded into the Nginx container as environment variables on startup, but is not know before then (it's dynamic). I want my nginx.conf to use these values - e.g.

upstream gunicorn {
    server $APP_HOST_NAME:$APP_HOST_PORT;
}

How can I get environment variables into the Nginx configuration on startup?

EDIT 1

This is the entire file, after the suggested answer below:

env APP_WEB_1_PORT_5000_TCP_ADDR;
# Nginx host configuration for django_app

# Django app is served by Gunicorn, running under port 5000 (via Foreman)
upstream gunicorn {
    server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000;
}

server {
    listen 80;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location /static/ {
        alias /app/static/;
    }
    location /media/ {
        alias /app/media/;
    }
    location / {
        proxy_pass http://gunicorn;
    }
}

Reloading nginx then errors:

$ nginx -s reload
nginx: [emerg] unknown directive "env" in /etc/nginx/sites-enabled/default:1

EDIT 2: more details

Current environment variables

root@87ede56e0b11:/# env | grep APP_WEB_1
APP_WEB_1_NAME=/furious_turing/app_web_1
APP_WEB_1_PORT=tcp://172.17.0.63:5000
APP_WEB_1_PORT_5000_TCP=tcp://172.17.0.63:5000
APP_WEB_1_PORT_5000_TCP_PROTO=tcp
APP_WEB_1_PORT_5000_TCP_PORT=5000
APP_WEB_1_PORT_5000_TCP_ADDR=172.17.0.63

Root nginx.conf:

root@87ede56e0b11:/# head /etc/nginx/nginx.conf
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;
env APP_WEB_1_PORT_5000_TCP_ADDR;

Site nginx configuration:

root@87ede56e0b11:/# head /etc/nginx/sites-available/default
# Django app is served by Gunicorn, running under port 5000 (via Foreman)
upstream gunicorn {
    server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000;
}

server {
    listen 80;

Reload nginx configuration:

root@87ede56e0b11:/# nginx -s reload
nginx: [emerg] directive "server" is not terminated by ";" in /etc/nginx/sites-enabled/default:3
4
  • 11
    This isn't a generic solution for environment variables, but if you're wanting to use environment variables for the hostnames/IP addresses of upstream servers, note that Docker (at least in recent versions) modifies /etc/hosts for you. See docs.docker.com/userguide/dockerlinks This means, if your linked container is called 'app_web_1', docker will create a line in /etc/hosts in your Nginx container. So you can just replace server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000; with server app_web_1:5000;
    – mozz100
    Jul 10, 2014 at 17:18
  • 1
    Thanks @mozz100 - that's incredibly useful - /etc/hosts entries are much more effective than env vars in this case. The only bit missing is what happens if the upstream container is restarted, and acquires a new IP. I am presuming that the child containers will still point to the original IP, not the new one? Jul 11, 2014 at 13:04
  • 1
    Yes, if you restarted app_web_1 it would get a new IP address, so you'd need to restart your nginx container too. Docker would restart it with an updated /etc/hosts so you wouldn't need to alter the nginx config file(s).
    – mozz100
    Jul 14, 2014 at 14:36
  • Reading through the answers here, I am flabbergasted that something so basic hasn't been elegantly implemented by probably the most popular web server on the planet.
    – csvan
    Nov 29, 2020 at 14:46

30 Answers 30

24

Here another one with envsubst.

nginx docker image already runs envsubst through /docker-entrypoint.d/20-envsubst-on-templates.sh. You only need to place a template file in the right place: /etc/nginx/templates/my-file.conf.template.

Then, you can create a dedicated template file only for variables with one or more map clauses that we will use as our custom variables. You must choose a name for the conf.d file so that it is the first one that nginx takes.

This is the basic concept explained with docker-compose and three files:

docker-compose.yml:

version: "3.9"
services:
  nginx:
    image: nginx:1.23
    volumes:
        - ./template-variables:/etc/nginx/templates/10-variables.conf.template:ro

    environment:
        EXTERNAL_IP: "1.2.3.4"

template-variables:

map $host $external_ip {
  default "$EXTERNAL_IP";
}

nginx.conf:

server {
     location / {

          proxy_set_header X-Real-IP $external_ip;
      }
}
4
  • 3
    Marking this as the solution as although it's not the first to highlight the solution, it's the most useful as it contains all the various elements rather that pointing to another article. Jan 2, 2023 at 11:02
  • why did you map $host to $external_ip I can see you are using $external_ip ion the other file but what is the purpose of $host?
    – PirateApp
    Feb 27, 2023 at 14:54
  • PirateApp, It's not relevant to the subject. The point is to be able to use something similar to environment variables in nginx conf. But, to answer your question, this header allows a service behind a reverse proxy to know the real IP from the host instead of the IP in the docker internal network.
    – David
    Mar 7, 2023 at 14:38
  • For this to work, make sure to keep include /etc/nginx/conf.d/*.conf; in nginx.conf - this is where the template gets written to. Jan 5 at 18:15
190

From the official Nginx docker file:

Using environment variables in nginx configuration:

Out-of-the-box, Nginx doesn't support using environment variables inside most configuration blocks.

But envsubst may be used as a workaround if you need to generate your nginx configuration dynamically before nginx starts.

Here is an example using docker-compose.yml:

image: nginx
volumes:
 - ./mysite.template:/etc/nginx/conf.d/mysite.template
ports:
 - "8080:80"
environment:
 - NGINX_HOST=foobar.com
 - NGINX_PORT=80
command: /bin/bash -c "envsubst < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" 

The mysite.template file may then contain variable references like this :

listen ${NGINX_PORT};

Update:

But you know this caused to its Nginx variables like this:

proxy_set_header        X-Forwarded-Host $host;

damaged to:

proxy_set_header        X-Forwarded-Host ;

So, to prevent that, i use this trick:

I have a script to run Nginx, that used on the docker-compose file as command option for Nginx server, i named it run_nginx.sh:

#!/usr/bin/env bash
export DOLLAR='$'
envsubst < nginx.conf.template > /etc/nginx/nginx.conf
nginx -g "daemon off;"

And because of defined new DOLLAR variable on run_nginx.sh script, now content of my nginx.conf.template file for Nginx itself variable is like this:

proxy_set_header        X-Forwarded-Host ${DOLLAR}host;

And for my defined variable is like this:

server_name  ${WEB_DOMAIN} www.${WEB_DOMAIN};

Also here, there is my real use case for that.

13
  • 4
    That kills nginx confs like proxy_set_header Host $http_host;
    – shredding
    Feb 17, 2016 at 17:00
  • 42
    @shredding you can pass in variables names to be replaced - others are not touched: command: /bin/bash -c "envsubst '$VAR1 $VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" works for me b/c I know what they are called ... Feb 19, 2016 at 10:13
  • 9
    ok, forgot to escape the $, should be command: /bin/bash -c "envsubst '\$VAR1 \$VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" Feb 19, 2016 at 10:33
  • 2
    With consideration for escaping this works in your own Dockerfile: CMD ["/bin/sh","-c", "if [ -n \"${SOME_ENV}\" ]; then echo envsubst '${SOME_ENV}' with ${SOME_ENV} && envsubst '${SOME_ENV}' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf; fi ; nginx -g 'daemon off;'"]
    – KCD
    Nov 28, 2016 at 0:12
  • 6
    Escaping $ in docker-compose.yml should be done with another $. So, the command should look like this: command: /bin/bash -c "envsubst '$${VAR1} $${VAR2}' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
    – sergei
    Jul 5, 2019 at 10:49
107

The official nginx image recommends using envsubst, but as pointed out by others it will replace also $host and other variables, which is not desirable. But fortunately envsubst can take as a parameter the names of variables to replace.

To avoid a very complex command parameter to the container (as in the linked example), you can write a Docker entrypoint script which will fill in the environment variables before executing the command. The entrypoint script is also a good place for validating the parameters and setting default values.

Here is an example of an nginx container which takes in API_HOST and API_PORT parameters as environment variables.

nginx-default.conf.template

resolver  127.0.0.11 valid=10s;  # recover from the backend's IP changing

server {
  listen  80;

  location / {
    root  /usr/share/nginx/html;
  }

  location /api {
    proxy_pass  http://${API_HOST}:${API_PORT};
    proxy_set_header  Host $http_host;
  }
}

docker-entrypoint.sh

#!/usr/bin/env sh
set -eu

envsubst '${API_HOST} ${API_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf

exec "$@"

Dockerfile

FROM nginx:1.15-alpine

COPY nginx-default.conf.template /etc/nginx/conf.d/default.conf.template

COPY docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
3
  • 3
    I changed the script slightly to clean up the template from conf.d. Final version: ``` #!/usr/bin/env sh set -eu envsubst '${API_HOST} ${API_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf rm /etc/nginx/conf.d/default.conf/template exec "$@" ``` great script though thanks! Nov 20, 2019 at 18:29
  • im curious why nginx made this so complicated.
    – dtc
    Jan 26, 2023 at 19:47
  • 1
    The official image now has one more environment variable to avoid replacing unwanted variables: NGINX_ENVSUBST_FILTER. It is a regex, and variables not matching it won't get replaced. You can set it to e.g. API_ via docker-compose.yml or Dockerfile to leave $host and other variables intact. It eliminates the need for custom entrypoint script. See github.com/nginxinc/docker-nginx/pull/706 for more details.
    – vvzh
    Jan 31, 2023 at 7:00
69

envsubstr is handled automatically by the nginx image now.

From the docs:

Out-of-the-box, nginx doesn't support environment variables inside most configuration blocks. But this image has a function, which will extract environment variables before nginx starts.

By default, this function reads template files in /etc/nginx/templates/*.template and outputs the result of executing envsubst to /etc/nginx/conf.d.

6
  • This is great! But, I don't see it happening. I put a .template file in /etc/nginx/templates/, but nothing is being written to /etc/nginx/conf.d. I am using the docker image nginx:1.19.2. I have a custom entrypoint script, but it ends up calling nginx -g 'daemon off;'. Am I missing something?
    – John L
    Aug 31, 2020 at 20:31
  • 2
    @JohnL I didn't look thoroghly, but it seems as though this action is done via a script included via the Dockerfile. You'll need to run that script to use that feature
    – xyhhx
    Sep 1, 2020 at 4:36
  • 7
    Docs are a bit sparse. The missing steps that got this working for me were: 1) creating a template, default.conf.template in a templates/ directory 2) in my docker-compose.yml adding volumes: - ./templates:/etc/nginx/templates to copy the templates. Then just do docker-compose up and et voila. Feb 17, 2021 at 19:18
  • Does this method require the official nginx docker image and all the files? I am trying this but its not working, My dockerfile is essentially "FROM nginx:1.19, EXPOSE 80, CMD ["nginx", "-g", "daemon off;"]" and my dir is just the dockerfile and a templates/default.conf.template and of course the docker-compose.yml filled from the official docs
    – Kent Wong
    Mar 16, 2021 at 5:05
  • 3
    As far as I can tell this only works if you have no command's in your docker-compose. It seems those will prevent the docker-entrypoint.sh script from running. You can see that script if you exec into the running container. Posted about this here.
    – Doug Fir
    Mar 21, 2021 at 13:02
56

Doing this with Lua is substantially easier than it sounds:

server {
    set_by_lua $server_name 'return os.getenv("NGINX_SERVERNAME")';
}

I found that here:

https://docs.apitools.com/blog/2014/07/02/using-environment-variables-in-nginx-conf.html

Edit:

Apparently this requires installing the lua module: https://github.com/openresty/lua-nginx-module

Edit 2:

Note that with this approach you have to define env variable in Nginx:

env ENVIRONMENT_VARIABLE_NAME

You have to do this in toplevel context in nginx.conf or it won't work! Not in server block or in config of some site in /etc/nginx/sites-available, because it is included by nginx.conf in http context (which is not top-level context).

Also note that with this approach if you try to make a redirect e.g.:

server {
    listen 80;
    server_name $server_name;
    return 301 https://$server_name$request_uri;
}

it won't work as well:

2016/08/30 14:49:35 [emerg] 1#0: the duplicate "server_name" variable in /etc/nginx/sites-enabled/default:8

And if you give a separate variable name to it:

set_by_lua $server_name_from_env 'return os.getenv("NGINX_SERVERNAME")';

server {
    listen 80;
    server_name $server_name_from_env;
    return 301 https://$server_name$request_uri;
}

nginx won't interpret it and will redirect you to https://%24server_name_from_env/.

5
  • 3
    you may need env NGINX_SERVERNAME somewhere in your nginx.conf.
    – hiroshi
    Feb 15, 2016 at 14:28
  • This did not work for me, although I have the lua module in my nginx docker image. Could this be related to the fact that I include a config file within my nginx.conf? I was trying to set_by_lua the variable in the included config file, while the env MY_VAR declaration was in the main nginx.conf, as suggested. What a shame, this would have been the cleanest solution!
    – jurgispods
    May 16, 2017 at 10:16
  • Does anyone know the pros/cons to using this method of envsubst? I guess the pro is you don't need to run the envsubstr command before starting the server and the con is that you need to install the lua module? I wonder if there are any security implications on either approach? Mar 4, 2018 at 16:19
  • 1
    @MarkWinterbottom didn't test this yet but it looks like you would not have to grant write access to the nginx config files, which you have to using envsubst and which is a no-go in my case
    – Gerrit-K
    Jun 5, 2018 at 14:04
  • When you say the env directive has to be done "in toplevel context in nginx.conf", what exactly is "nginx.conf"? Where should that file be?
    – Jack M
    Oct 12, 2019 at 20:23
15

I wrote something that may or may not be helpful: https://github.com/yawn/envplate

It inline edits configuration files with ${key} references to environment variables, optionally creating backups / logging what it does. It's written in Go and the resulting static binary can be simply downloaded from the release tab for Linux and MacOS.

It can also exec() processes, substitute defaults, logs and has sensible failure semantics.

0
6

Referring to the answer on using erb, it can be done as below.

Write the NGINX config file as an erb file containing the environment variable, and evaluate it using the erb command to a normal config file.

erb nginx.conf.erb > nginx.conf

Inside the server block of the nginx.conf.erb file there could be

listen <%= ENV["PORT"] %>;
2
  • 1
    Do I need to install Ruby then, inside the container? (If not, how is erb able to do variable substitution ... after the container was started, right?) — erb is this Ruby stuff right: stuartellis.name/articles/erb
    – KajMagnus
    Apr 8, 2017 at 7:57
  • 1
    It's a command line tool that comes with standard Ruby installation. Try other options if you don't have it in the container.
    – Ruifeng Ma
    Apr 12, 2017 at 11:45
6

I accomplish this using a shell script.

Here is the nginx template:

server {

    listen 80;
    server_name ___MY_DOMAIN_NAME___;
    charset utf-8;

    location /proxy {
        proxy_pass http://___PROXY_IP___:___PROXY_PORT___;
        proxy_set_header Host            $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
    }

    location / {
        return         200;
    }

}

And the script to replace the environment variables is here:

echo sleep 3
sleep 3

echo build starting nginx config


echo replacing ___MY_DOMAIN_NAME___/$MY_DOMAIN_NAME
echo replacing ___PROXY_IP___/$LETSENCRYPT_IP
echo replacing ___PROXY_PORT___/$PROXY_PORT

sed -i "s/___MY_DOMAIN_NAME___/$MY_DOMAIN_NAME/g" /etc/nginx/nginx.conf
sed -i "s/___PROXY_IP___/$PROXY_IP/g" /etc/nginx/nginx.conf
sed -i "s/___PROXY_PORT___/$PROXY_PORT/g" /etc/nginx/nginx.conf

cat /etc/nginx/nginx.conf

if [ -z "$MY_DOMAIN_NAME" ]; then
    echo "Need to set MY_DOMAIN_NAME"
    exit 1
fi  
if [ -z "$LETSENCRYPT_IP" ]; then
    echo "Need to set LETSENCRYPT_IP"
    exit 1
fi  
if [ -z "$LETSENCRYPT_PORT" ]; then
    echo "Need to set LETSENCRYPT_PORT"
    exit 1
fi
if [ -z "$LETSENCRYPT_HTTPS_IP" ]; then
    echo "Need to set LETSENCRYPT_HTTPS_IP"
    exit 1
fi 
if [ -z "$LETSENCRYPT_HTTPS_PORT" ]; then
    echo "Need to set LETSENCRYPT_HTTPS_PORT"
    exit 1
fi

nginx -g 'daemon off;'
1
  • Works perfect with docker-compose. Jul 2, 2020 at 8:21
6

What I did was to use the erb!

cat nginx.conf  | grep -i error_log

error_log <%= ENV["APP_ROOT"] %>/nginx/logs/error.log;

--After using erb

export APP_ROOT=/tmp

erb nginx.conf  | grep -i error_log

error_log /tmp/nginx/logs/error.log;

This is used in the Cloudfoundry staticfile-buildpack

Sample nginx configuration: https://github.com/cloudfoundry/staticfile-buildpack/blob/master/conf/nginx.conf

In your case

head /etc/nginx/nginx.conf
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;
env APP_WEB_1_PORT_5000_TCP_ADDR;
upstream gunicorn {
    server $APP_HOST_NAME:$APP_HOST_PORT;
}

become

head /etc/nginx/nginx.conf
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;
env <%= ENV["APP_WEB_1_PORT_5000_TCP_ADDR"] %>
upstream gunicorn {
    server <%= ENV["APP_HOST_NAME"] %>:<%= ENV["APP_HOST_PORT"] %>
}

#After applying erb

export APP_WEB_1_PORT_5000_TCP_ADDR=12.12.12.12
export APP_HOST_NAME=test
export APP_HOST_PORT=7089 

erb /etc/nginx/nginx.conf

head /etc/nginx/nginx.conf
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;
env 12.12.12.12
upstream gunicorn {
    server test: 7089
}
0
5

There are many ways. Some have been outlined in some answers.

If you use the ngx_http_js_module, there is also a way to do that with JS:

## /etc/nginx/fetch_env.js
function fetch_upstream_host(r) {
  return process.env.UPSTREAM_HOST;
}

function fetch_upstream_port(r) {
  return process.env.UPSTREAM_PORT;
}

and

## /etc/nginx/nginx.conf
load_module modules/ngx_http_js_module.so;

env UPSTREAM_HOST;
env UPSTREAM_PORT;

http {
  js_include fetch_env.js;
  js_set $upstream_host fetch_upstream_host;
  js_set $upstream_port fetch_upstream_port;

  server {
    ...

    location / {
      ...

      proxy_pass http://$upstream_host:$upstream_port;
    }
  }

Please make sure you use njs module 0.33 or newer

1
  • 1
    A neat approach, however limited in that nginx variables can only be used in run-time directives, during the processing of each request; nginx.org/en/docs/faq/variables_in_config.html. For example, the njs approach won't work for most config directives such as listen, server_name, etc.
    – insertcoin
    Jun 10, 2021 at 7:46
4

Here's an example of using the sed approach, not necessarily better but it may be useful to some. First, add a custom keyword to be replaced within the conf file. Second, create a dockerfile that declares an ENV variable and then a CMD that uses sed to edit the config before running nginx explicitly.

So suppose your default.conf contains this with the keyword docker_host:

location /api { proxy_pass http://docker_host:9000/api; }

And write your Dockerfile similar to:

ENV docker_host localhost
ADD default.conf /etc/nginx/conf.d/default.conf
CMD sed -i.bak s/docker_host/$docker_host/g /etc/nginx/conf.d/default.conf &&   nginx -g "daemon off;"

Then build the image and run the container using

docker run -d -p 80:80 -e "docker_host=${env:COMPUTERNAME}" imagename
1
  • It could be stated slightly clearer proxy_pass http://__DOCKER_HOST__:9000/api;
    – vatosarmat
    Nov 25, 2021 at 12:01
3

I know this is an old question, but just in case someone stumbles upon this (as I now have), there's a much better way to do this. Because docker inserts the linked container's alias in /etc/hosts, you can just do

upstream upstream_name {
    server docker_link_alias;
}

assuming your docker command is something like docker run --link othercontainer:docker_link_alias nginx_container.

1
  • 1
    You can get the host using this method but not the port. You have to either hard code the port or assume it's using port 80.
    – Ben Whaley
    Mar 18, 2016 at 13:58
3

Like already explained in Martin's answer, the official Docker image supports parsing templates with envsubst nowadays. However, envsubst does not support default values like a regular shell does, as in ${MY_VAR:-My Default}.

To circumvent that, one can use a custom entrypoint, like docker-defaults.sh:

#!/usr/bin/env sh
set -eu

# As of version 1.19, the official Nginx Docker image supports templates with
# variable substitution. But that uses `envsubst`, which does not allow for
# defaults for missing variables. Here, first use the regular command shell
# to set the defaults:

export PROXY_API_DEST=${PROXY_API_DEST:-http://host.docker.internal:8000/api/}

echo "Will proxy requests for /api/* to ${PROXY_API_DEST}*"

# Next, let the original entry point do its work:

/docker-entrypoint.sh "$@"

Along with, say, some docker-nginx.conf:

# After variable substitution, this will replace /etc/nginx/conf.d/default.conf
server {
    listen 80;
    listen [::]:80;
    server_name localhost;

    location / {
        root /usr/share/nginx/html;
        # Forward all non-existing paths, such as /help and /file/..., to the app
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
       # Proxy API calls to another destination; the default for the variable is
       # set in docker-defaults.sh
       proxy_pass $PROXY_API_DEST;
    }

    # Redirect server error pages to the static page /50x.html
    error_page 500 502 503 504  /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}

In the Dockerfile copy it into /etc/nginx/templates/default.conf.template:

# Each time Nginx is started it will perform variable substition in the template
# file /etc/nginx/templates/default.conf.template and copy the result into
# /etc/nginx/conf.d/default.conf, so replacing the original configuration; see 
# also https://hub.docker.com/_/nginx
COPY docker-nginx.conf /etc/nginx/templates/default.conf.template
COPY docker-defaults.sh /

# This will delegate to the original docker-entrypoint.sh
ENTRYPOINT ["/docker-defaults.sh"]

CMD ["nginx", "-g", "daemon off;"]

Now using, e.g., docker run --env PROXY_API_DEST=https://example.com/api/ ... will set a value, or in this example will default to http://host.docker.internal:8000/api/ if not set (which is actually http://localhost:8000/api/ on the local machine).

3

nginx Docker image can extract environment variables before it starts, but it's a bit tricky. One solution is to:

  1. Add env variables to your nginx.conf file.
  2. Copy it to /etc/nginx/templates/nginx.conf.template in the container (as opposed to your normal /etc/nginx) in the build step or as a volume.
  3. Set the NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx environment variable in docker-compose.yml.

This will cause the nginx.conf.template file to be copied to /etc/nginx as nginx.conf and the environment variables will be replaced with their values.

There is one caveat to keep in mind: using command property in docker-compose.yml seems to be disabling the extraction functionality. If you need to run a custom command to start-up nginx, you can use the Dockerfile version.

I created a repo with the full setup, but in case it's not available:

# docker-compose.yml

version: "3"
services:
  nginx-no-dockerfile:
    container_name: nginx-no-dockerfile
    image: nginx:1.23.1-alpine
    ports:
      - 8081:80
    volumes:
      - ./site/index.html:/usr/share/nginx/html/index.html
      - ./site/nginx.conf:/etc/nginx/templates/nginx.conf.template
    working_dir: /usr/share/nginx/html
    environment:
      NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx
      API_URL: http://example.com


  nginx-with-dockerfile:
    container_name: nginx-with-dockerfile
    build:
      context: ./site
      dockerfile: ./Dockerfile
    ports:
      - 8082:80
    volumes:
      - ./site/index.html:/usr/share/nginx/html/index.html
    environment:
      NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx
      API_URL: http://example.com
# site/nginx.conf

worker_processes auto;

events {
}

http {
  include /etc/nginx/mime.types;

  server {
    listen 80;

    root /usr/share/nginx/html;
    index index.html index.htm;

    location / {
      try_files $uri $uri/ /index.html;
    }

    location /example {
      proxy_pass $API_URL;
    }
  }
}
# site/Dockerfile

FROM nginx:1.23.1-alpine

WORKDIR /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/templates/nginx.conf.template

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Run no Dockerfile version: docker compose up nginx-no-dockerfile

Run Dockerfile version:

docker compose build nginx-with-dockerfile
docker compose up nginx-with-dockerfile

Make sure to also have index.html file in the site folder.

2

Another option... I just found this tool today: https://github.com/kreuzwerker/envplate ... Written in Go, it can be installed very easily. Using it is quite simple. Though you will need to place template variables in your nginx.conf.

For example ${SOME_ENV_VAR} will be replaced when envplate's ep command is called on the file. So should your Dockerfile fail to get that binary or should it not run for some reason, it would lave your config invalid. Just a little note compared to other solutions like using perl or lua extensions.

I really like how you can set default values too for when the environment variable is not set. Ex. ${SOME_ENV_VAR:default-value} (and you can escape values). Again, envplate must still successfully run.

One benefit of using an approach like this is that you don't end up with a Docker image that's larger than necessary because you went off installing all sorts of extra modules you don't otherwise need. It may also be easier than using sed if things start to get complex and it contains that default value functionality.

1
2

Another possibility is to use the 'sed' command with regular expressions, then you won't have to mess with your config files at all! That way you can use your config files normally, but when you run docker, it will swap the values out with the env variables. None of this "add a string of text to your config files that you search and replace with."

You can create a run.sh file with replacement values using your environment variables.

To change the "7s" on this line:

client_body_timeout 7s;         #Default 60s

Sed Command using client_body_timeout as search line and $client_body_timeout as replacement env variable:

sed -i "s/\(client_body_timeout\).*\?\;/\1 $client_body_timeout;/" /usr/local/nginx/conf/nginx.conf

Copy/paste this line for every parameter you want to set and change the client_body_timeout with the config option and $client_body_timeout with the env variable it is associated with. Use with existing config file and it will just work.

1

Proper way to do this with lua, since the answer above is stale:

server {
    set_by_lua $curr_server_name 'return os.getenv("NGINX_SERVERNAME")';
    server_name = $curr_server_name;
}

1

In addition to the lua and to the js method, there is a way with perl, especially, if nginx already compiled with --with-http_perl_module. Important parts are commented inline:

# declare variable
env ENDPOINT;

events {
    worker_connections 1024;
}

http {
    include mime.types;

    # store variable from env as nginx variable
    perl_set $endpoint 'sub { return $ENV{"ENDPOINT"}; }';

    server {
        listen 8080;
        root /usr/local/nginx;
        location / {
            sub_filter_once off;
            sub_filter_last_modified on;
            sub_filter_types application/javascript;

            # use variable, for example:
            sub_filter 'ENDPOINT' $endpoint;

            try_files $uri /testfile.js;
        }
    }
}

Be aware, that perl is a dynamic module and probably you need libnginx-mod-http-perl or nginx-extras to be installed:

apt-get install libnginx-mod-http-perl
1

I think everything is simpler here. First of all, envsubst is better than lua block. envsubst creates configuration files before nginx starts and then all variables are static. When using a lua block, each time a request is made, the block will be processed and the environment variables will be initialized each time, and this is an additional burden. But this doesn't matter if you have a workstation. At high loads I think this will matter.

Further, if you go a little deeper into the details, you can simplify everything. Environment variables can simply be included in a separate file, which is generated from the template.

In container /docker-entrypoint.d/20-envsubst-on-templates.sh script handles the templates. All templates paths are saved in the created configuration files.
Example: /etc/nginx/templates/dir/my_vars.conf.template to /etc/nginx/dir/my_vars.conf

In *.conf.template templates you can include environment variables via the env directive - env VAR=${MY_VAR}.

Next, in any config file you include a variable file include /etc/nginx/dir/my_vars.conf

I'll explain with an example.

First, let's copy the nginx configs from the container

docker run -d --rm --name test nginx:latest && docker cp test:/etc/nginx/ $(pwd)/ngninx_config ; docker stop test
($(pwd)/ngninx_config directory must not exist)

Next, let's launch the container for debugging

docker run \
-e NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx \
-e MY_VAR=Hello \
--mount type=bind,src=$(pwd)/ngninx_config,dst=/etc/nginx \
--name test_nginx \
-it --entrypoint bash --rm nginx:latest

NGINX_ENVSUBST_OUTPUT_DIR variable see https://hub.docker.com/_/nginx => Using environment variables in nginx configuration (new in 1.19)

Сreate file /etc/nginx/templates/env/nginx.conf.template

env VAR=${MY_VAR}

Change file /etc/nginx/nginx.conf

user  www-data;
worker_processes  auto;
include /etc/nginx/env/nginx.conf; # <===

...

run command in container

/docker-entrypoint.d/20-envsubst-on-templates.sh && nginx -t

Debug your settings until you succeed.

The env directive is used in the main block. You can use the set directive. It is used in server, location, if blocks

(UPDATED)
Since setting environment variables in config files is a common difficulty (for example in mysql configs), I rewrote the /docker-entrypoint.d/20-envsubst-on-templates.sh file for general cases. The script in the templates replaces environment variables and creates config files.
https://gist.github.com/alexeyp0708/d244d0c6714d937bbcac55769c2bd392
See createFromTpl.sh -h command

0

If you need to keep it simple: bash eval sed
And follow two simple rules: ${ENV_IN_PARENTHESIS} and always use ' quotes in template
You can swap all environment variables in one generic call.

 eval "echo \"$(sed 's/\$/##/g; s/\##{/${/g;' some.template)\"" | sed 's/\##/$/g' > some.conf


Whats happening:

A. $(sed 's/\$/##/g; s/\##{/${/g;' some.template)

Run the template through sed and:
1. replace all $ characters with ##
2. replace all ##{ back to ${

B. eval "echo \"$(sed 's/\$/##/g; s/\##{/${/g;' some.template)\""

Runs the modified template through eval which replaces visible ${ENV_IN_PARENTHESIS}

C. ... | sed 's/\##/$/g' > some.conf

Pipes the result of the eval to another sed call that returns all original $ back from ## and output the final result to your shiny new conf file.

0

Taking up @omid's solution, here's a way of doing it without custom scripting and/or changing the default entry point script of the official Nginx docker image:

in Dockerfile:

FROM nginx

# override if necessary
ENV MY_VARIABLE='something'

# do NOT override this
ENV DOLLAR_SIGN='$'

COPY ./nginx.conf.template /etc/nginx/templates/default.conf.template

In the local nginx.conf.template file:

upstream upstream_server {
 server ${MY_VARIABLE}:8001 fail_timeout=0;
}

server {
 ...
 location / {
   proxy_set_header Host ${DOLLAR_SIGN}http_host;
   ....
   }
 }
}
0

Nginx 1.19 supports environment variables and templates in Docker

In this blog

1
  • 1
    Hello! Please post complete answers instead of links to blog posts. Links can rot or be not a direct answer to the posted question.
    – ablackhat
    Nov 5, 2021 at 5:26
0

ex:

set_by_lua $curr_domain_name 'return os.getenv("DOMAIN")';
add_header Content-Security-Policy 'script-src  ${curr_domain_name}';

This worked for me.

0

If you're not tied to bare installation of nginx, you could use docker for the job.
For example nginx4docker implements a bunch of basic env variables that can be set through docker and you don't have to fiddle around with nginx basic templating and all it's drawbacks.

nginx4docker could also be extended with your custom env variables. only mount a file that lists all your env variables to docker ... --mount $(pwd)/CUSTOM_ENV:/ENV ...

When the worst case happens and you can't switch/user docker, a workaround maybe to set all nginx variables with their names (e.g. host="$host") in this case envsubst replaces $host with $host.

0

The offical Nginx Docker image has a hidden hook Supporting this. See https://medium.com/@tal.potlog/nginx-docker-and-the-hidden-hooks-f5782261fae

1
  • 1
    Would be great to post the answer also here instead of only linking to an external resource.
    – ppuschmann
    Jul 4, 2022 at 16:19
0

For future reference, this is our local setup:

docker-compose.yml

# docker-compose.yml

version: "3.8"

services:
  # nginx is used to handle SSL termination. The upstream app location
  # is the Django app, and it can be set to run in docker or on the
  # host. It is set using the NGINX_UPSTREAM_APP env var which is
  # injected into the app.conf using the app.conf.template file.
  nginx:
    image: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx/ssl:/etc/nginx/certs:ro
      - ./docker/nginx/templates:/etc/nginx/templates:ro
    environment:
      - NGINX_UPSTREAM_APP=${NGINX_UPSTREAM_APP:-django:5001}

app.conf.template

# Nginx host configuration for local development.
#
# The upstream app path is set dynamically when the nginx container is
# rebuilt, using a template and env var substitution.
#
# NB paths in this file are resolved inside the docker container.
upstream app {
    # host.docker.internal is the host machine
    server ${NGINX_UPSTREAM_APP} fail_timeout=0;
}

server {
    listen 80;
    listen 443 ssl;
    ssl_protocols             TLSv1.2 TLSv1.3;
    ssl_ciphers               HIGH:!ADH:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_certificate           /etc/nginx/certs/localhost.dev.crt;
    ssl_certificate_key       /etc/nginx/certs/localhost.dev.key;
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_pass http://app;
    }
    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;
}
0
  1. Create a template directory that should contain the .conf files (make sure the files have .template extension e.g default.conf.template)

  2. mount the folder as a volume in the docker-compose file to /etc/nginx/templates (- ./templates:/etc/nginx/templates) ( see image below)

  3. Define the .env variables keys in your docker-compose file without the values ( see image below)

  4. run the docker compose up command and nginx will automatically create the conf files from the template directory before starting the engine.

Docker compose

Docker compose file

Default.conf.template

Default.conf.template

NB: No extra commands needed

0

According to @xyhhx and @ESV nginx can now handle ENV variables by creating a template, and placing it in directory defined in NGINX_ENVSUBST_TEMPLATE_DIR env variable.

TEMPLATE APPROACH - docker/nginx docs

However since approach with envsubst can still be a desired solution I've created easy approach that doesn't have drawback of having to modify variables in many places. So if someone wants to create their own custom solution with envsubst, here's a concise example.

docker-compose

  nginx:
    image: nginx:stable-alpine
    env_file:
      - ./config/nginx/.nginx-env
    entrypoint: /etc/nginx/setup_conf.sh

entrypoint

#!/bin/sh

# LOAD ALL NGINX_* ENV VARS TO SHELL-FORMAT
envs=$(printenv | grep NGINX_ | awk -F '=' '{printf("$${%s} ", $1)}')

# LOAD TEMPLATE, INJECT ENV VARIABLES, OUTPUT CONF WITH INJECTED VARIABLES
envsubst "$envs" < /etc/nginx/template.conf > /etc/nginx/nginx.conf

# START NGINX IN THE BACKGROUND
nginx -g "daemon off;"

env file

NGINX_API_PATH=example.com
0

I will further expand David reply with a more complex example:

NGINX image do already have variable substitution; you can do it yourself with an envsub in your entrypoint (like i did before) or CMD but i do prefer using what it's already available.

I usually have an image that creates the right user and group (i usually run all my containers directly on 1000:1000 for security, with NGINX it's better to run the image as root to do variable replacing in config and make it read only for the user running NGINX) and various configurations with predefined config (ex. wordpress, laravel ,etc.) that i switch by changing the TEMPLATE env variable (ex. i set TEMPLATE="laravel" for laravel).

With the default NGINX image all the files ending with .conf.template under /etc/nginx/templates will be processed replacing variables and copied under /etc/nginx/conf.d/.conf; this works also with folders so /etc/nginx/templates/my-template.conf.template will generate /etc/nginx/conf.d/my-template.conf and /etc/nginx/templates/my-folder/another-template.conf.template will generate /etc/nginx/conf.d/my-folder/another-template.conf.

Let's start making a sample docker-compose file; this is just a sample to show where to mount the files and folders, you could also create a Dockerfile if you prefer

docker-compose.yml

version: "3.9"
services:
  nginx:
    image: nginx
    volumes:
        - ./templates:/etc/nginx/templates
        - ./nginx.conf:/etc/nginx/nginx.conf

    environment:
        USER_NAME: "user"
        USER_GROUP: "user"
        TEMPLATE: "my-template"
        MAX_BODY_SIZE: "1G"
        PROXY_TIMEOUT: "60"
        MY_VAR: "my-value"

We then create a nginx.conf file that will import all the configuration files in conf.d

nginx.conf

include /etc/nginx/conf.d/*.conf;

We then create a default config file that in our case only import the correct template based on the TEMPLATE env; this could not be done on nginx.conf.

templates/default.conf.template

include /etc/nginx/conf.d/templates/$TEMPLATE.conf;

then we will create our templates under /templates/templates

templates/templates/my-template.conf.template

user $USER_NAME $USER_GROUP;
events {
}
http {
    client_max_body_size $MAX_BODY_SIZE;
    fastcgi_read_timeout $PROXY_TIMEOUT;
    proxy_read_timeout $PROXY_TIMEOUT;
    #....
}
-2

You should able to do what you want with Dockerize's templates.

1
  • 9
    Can you add some more details? May 20, 2016 at 11:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .