Substitute environment variables in NGINX config from docker-compose Substitute environment variables in NGINX config from docker-compose nginx nginx

Substitute environment variables in NGINX config from docker-compose


Since nginx 1.19 you can now use environment variables in your configuration with docker-compose. I used the following setup:

# file: docker/nginx/templates/default.conf.confupstream api-upstream {    server ${API_HOST};}# file: docker-compose.ymlservices:    nginx:        image: nginx:1.19-alpine        volumes:            - "./docker/nginx/templates:/etc/nginx/templates/"        environment:            NGINX_ENVSUBST_TEMPLATE_SUFFIX: ".conf"            API_HOST: api.example.com        

I'm going off script a little from the example in the documentation. Note the extra .conf extension on the template file - this is not a typo. In the docs for the nginx image it is suggested to name the file, for example, default.conf.template. Upon startup, a script will take that file, substitute the environment variables, and then output the file to /etc/nginx/conf.d/ with the original file name, dropping the .template suffix.

By default that suffix is .template, but this breaks syntax highlighting unless you configure your editor. Instead, I specified .conf as the template suffix. If you only name your file default.conf the result will be a file named /etc/nginx/conf.d/default and your site won't be served as expected.


You can avoid some of the hassles with Compose interpreting environment variables by defining your own entrypoint. See this simple example:

  • entrypoint.sh (make sure this file is executable)
#!/bin/shexport NGINXPROXYenvsubst '${NGINXPROXY}' < /config.template > /etc/nginx/nginx.confexec "$@"
  • docker-compose.yml
version: "3.7"services:    front-end:        image: nginx        environment:            - NGINXPROXY=172.31.67.100:9300        ports:            - 80:80        volumes:            - ./config:/config.template            - ./entrypoint.sh:/entrypoint.sh        entrypoint: ["/entrypoint.sh"]        command: ["nginx", "-g", "daemon off;"]

My config file has the same content as your nginx.conf, aside from the fact that I had to comment the lines using the Perl module.

Note that I had to mount my config file to another location before I could envsubst it. I encountered some strange behaviour in the form that the file ends up empty after the substitution, which can be avoided by this approach. It shouldn't be a problem in your specific case, because you already embed it in your image on build time.


EDIT

For completeness, to change your setup as little as possible, you just have to make sure that you export your environment variable. Adapt your command like this:

command: ["/bin/bash", "-c", "export NGINXPROXY && envsubst '$$NGINXPROXY' < /etc/nginx/nginx.conf > /etc/nginx/nginx.conf && nginx -g 'daemon off;'"]

...and you should be good to go. I would always recommend the "cleaner" way with defining your own entrypoint, though.


So after some wrestling with this issue, I managed to get it working similarly to the answer provided by bellackn. I am going to post my exact solution here, in case anybody else needs to reference a complete solution.

Step1: Write your nginx.conf or default.conf how you would normally write it. Save the file as "nginx.conf.template", or "default.conf.template" depending on which you are trying to substitute variables into.

user  nginx;worker_processes  1;error_log  /var/log/nginx/error.log warn;pid        /var/run/nginx.pid;events {    worker_connections  1024;}http {    upstream api-upstream {        server 192.168.25.254;    }    include       /etc/nginx/mime.types;    default_type  application/octet-stream;    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '                      '$status $body_bytes_sent "$http_referer" '                      '"$http_user_agent" "$http_x_forwarded_for"';    access_log  /var/log/nginx/access.log  main;    sendfile        off;    #tcp_nopush     on;    keepalive_timeout  65;    #gzip  on;    include /etc/nginx/conf.d/*.conf;}

Step2: Substitute a variable in the format ${VARNAME} for whatever value(s) you want to replace with an environment variable:

user  nginx;worker_processes  1;error_log  /var/log/nginx/error.log warn;pid        /var/run/nginx.pid;events {    worker_connections  1024;}http {    upstream api-upstream {        server ${SERVER_NAME};    }    include       /etc/nginx/mime.types;    default_type  application/octet-stream;    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '                      '$status $body_bytes_sent "$http_referer" '                      '"$http_user_agent" "$http_x_forwarded_for"';    access_log  /var/log/nginx/access.log  main;    sendfile        off;    #tcp_nopush     on;    keepalive_timeout  65;    #gzip  on;    include /etc/nginx/conf.d/*.conf;}

Step 3: In your docker-file, copy your nginx configuration files (your nginx.conf.template or default.conf.template) into your container at the appropriate location:

# build stageFROM node:latestWORKDIR /appCOPY ./ /appRUN npm installRUN npm run build# production stageFROM nginx:1.17.0-perlCOPY --from=0 /app/dist /usr/share/nginx/htmlRUN apt-get update && apt-get install -y gettext-baseRUN rm /etc/nginx/conf.d/default.confRUN rm /etc/nginx/nginx.conf#-----------------------------------#|COPY default.conf /etc/nginx/conf.d||COPY nginx.conf.template /etc/nginx|#-----------------------------------#RUN mkdir /certsEXPOSE 80 443CMD ["nginx", "-g", "daemon off;"]

Step 4: Set your environment variable in your docker-compos.yml file using the "environment" section label. Make sure your environment variable name matches whatever variable name you chose within your nginx config file. Use the "envsubt" command within your docker container to substitute your variable values in for your variables within your nginx.conf.template, and write the output to a file named nginx.conf in the correct location. This can be done within the docker-compose.yml file by using the "command" section label:

version: '2.0'services:    front-end:        environment:            - SERVER_NAME=172.31.67.100:9100        build: http://git-account:git-password@git-server.com/project-group/repository-name.git#branch-ame        container_name: qa_front_end        image: qa-front-end-vue        restart: always        networks:            qa_network:                ipv4_address: 172.28.0.215        ports:            - "9080:80"        command: >            /bin/sh -c            "envsubst '            $${SERVER_NAME}            '< /etc/nginx/nginx.conf.template            > /etc/nginx/nginx.conf            && nginx -g 'daemon off;'"

Step 5: Run your stack with docker-compose up (with whatever additional switches you need) and your nginx server should now start with whatever value you supplied in the "environment" section of your docker-compose.yml

As mentioned in the solution above, you can also define your own entry point, however this solution has also proven to work pretty well, and keeps everything contained into a single configuration file, giving me the ability to run a stack of services directly from git with nothing but a docker-compose.yml file.

A big thank you to everybody who took the time to ready through this, and bellackn for taking the time to help me solve the issue.