nginxinc / docker-nginx

Official NGINX Dockerfiles
BSD 2-Clause "Simplified" License
3.26k stars 1.73k forks source link

Using environment variables in nginx configuration not working #422

Closed mjswensen closed 4 years ago

mjswensen commented 4 years ago

According to the documentation, I should be able to provide a template directory and the container will automatically extract and replace environment variables, dropping the resulting files in /etc/nginx/conf.d/. I don't see that behavior.

Base reproducible case

Given the following files (copied and pasted from the example in the documentation):

docker-compose.yml:

version: '3.7'

services:
  web:
    image: nginx
    volumes:
      - ./templates:/etc/nginx/templates
    ports:
      - "8080:80"
    environment:
      - NGINX_HOST=foobar.com
      - NGINX_PORT=80

templates/foobar.conf.template:

server {
  listen       ${NGINX_PORT};
}

I then run docker-compose up and attach a separate shell to my running web container.

From within the container I observe the following:

$ ls /etc/nginx/templates/
foobar.conf.template
# as expected

$ ls /etc/nginx/conf.d/
default.conf
# where is foobar.conf?

Thank you for your help and forgive me if I'm missing an obvious step!

TamerShlash commented 4 years ago

Same problem here. I also tried manually running nginx -g 'daemon off;' -T and it's using the original default.confonly.

Using nginx:1.19.0-alpine

thresheek commented 4 years ago

I've just tried it and it works.

Can you "docker pull nginx" or "nginx:1.19.0-alpine" and then docker-compouse up -d and then show the output of docker logs for the launched container?

TamerShlash commented 4 years ago

For my case, I know what the problem was.

I was overriding CMD in my Dockefile. By taking a look at the content of the entrypoint script of the Nginx docker image:

if [ "$1" = "nginx" -o "$1" = "nginx-debug" ]; then

This means that, if your custom CMD does not start with nginx or nginx-debug (which was the case for me), none of the /docker-entrypoint.d scripts will be run because of how docker-entrypoint.sh works. The Nginx template compilation step is one of those scripts.

This is very unclear in the README of the image on dockerhub and I was only able to figure it out by looking at the code of the entrypoint script.

My solution was to simply add whatever I want to execute on entrypoint to a custom script and copy it to /docker-entrypoint.d/ so that it gets executed with the existing scripts, and just not overriding CMD or ENTRYPOINT in my custom image Dockerfile.

I think the README should be updated to reflect how this custom templating function actually works and how the entrypoint works too.

I also recommend taking a look at this how CMD and ENTRYPOINT interact.

yosifkit commented 4 years ago

I also recommend taking a look at this how CMD and ENTRYPOINT interact.

Is there something there that should be considered?

Unfortunately that table isn't correct/complete. Everything under the ENTRYPOINT exec_entry p1_entry should start with /bin/sh -c 'exec_entry p1_entry' (the quotes are important since it is a single argument for the -c of /bin/sh) and CMD needs to be shown as extra arguments to the sh -c. While these extra args wouldn't usually have an effect, they do become arguments to the "script":

entrypoint/cmd examples: ```console $ docker build . Sending build context to Docker daemon 2.048kB Step 1/3 : FROM bash ---> 2437bbe54138 Step 2/3 : ENTRYPOINT echo "'self': $0"; i=0; for arg in "$@"; do echo "$i: $arg"; let "i=i+1"; done ---> Running in 3f01c9594b33 Removing intermediate container 3f01c9594b33 ---> 0bd7f79cb7f3 Step 3/3 : CMD arg1 arg2 ---> Running in f0b15fc9586e Removing intermediate container f0b15fc9586e ---> 583dd978ce72 Successfully built 583dd978ce72 19:47:44 [sauron@isengard ~/tmp/entrypoint]$ docker run -it --rm 583dd978ce72 'self': /bin/sh 0: -c 1: arg1 arg2 $ docker build . Sending build context to Docker daemon 2.048kB Step 1/3 : FROM bash ---> 2437bbe54138 Step 2/3 : ENTRYPOINT echo "'self': $0"; i=0; for arg in "$@"; do echo "$i: $arg"; let "i=i+1"; done ---> Using cache ---> 0bd7f79cb7f3 Step 3/3 : CMD ["arg1", "arg2"] ---> Running in 44b2751d10b8 Removing intermediate container 44b2751d10b8 ---> 67ce95e48e75 Successfully built 67ce95e48e75 $ docker run -it --rm 67ce95e48e75 'self': arg1 0: arg2 ```

Regardless, the entrypoint and command here are working as designed (and very similar to most other images in the official-images program, e.g. redis).

timakamystery commented 4 years ago

@TamerShlash :

I was overriding CMD in my Dockefile.

Same for me (but ENTRYPOINT). Removing ENTRYPOINT from child image resolved the issue..

abdounikarim commented 4 years ago

I just tried and it works for me.

My docker-compose file :

version: '3.7'

services:

    nginx:
        container_name: nginx
        environment:
            NGINX_PORT: 80
        image: nginx
        restart: always
        volumes:
            - ./docker/nginx/templates:/etc/nginx/templates:rw,cached

And my default.conf.template file :

server {

    root /var/www/project/public;
    listen ${NGINX_PORT};

    location / {
        # try to serve file directly, fallback to index.php
        try_files $uri /index.php$is_args$args;
    }

    # optionally disable falling back to PHP script for the asset directories;
    # nginx will return a 404 error when files are not found instead of passing the
    # request to Symfony (improves performance but Symfony's 404 page is not displayed)
    # location /bundles {
    #     try_files $uri =404;
    # }

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;

        # optionally set the value of the environment variables used in the application
        # fastcgi_param APP_ENV prod;
        # fastcgi_param APP_SECRET <app-secret-id>;
        # fastcgi_param DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name";

        # When you are using symlinks to link the document root to the
        # current version of your application, you should pass the real
        # application path instead of the path to the symlink to PHP
        # FPM.
        # Otherwise, PHP's OPcache may not properly detect changes to
        # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
        # for more information).
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        # Prevents URIs that include the front controller. This will 404:
        # http://domain.tld/index.php/some-path
        # Remove the internal directive to allow URIs like this
        internal;
    }

    # return 404 for all other php files not matching the front controller
    # this prevents access to other php files you don't want to be accessible.
    location ~ \.php$ {
        return 404;
    }

    error_log /var/log/nginx/project_error.log;
    access_log /var/log/nginx/project_access.log;
}

You need to create your file and to copy it in /etc/nginx/templates. This file will not be changed by nginx, but the content will be replaced and you should see it in /etc/nginx/conf.d/default.conf.

You will have in your default.conf file :

server {

    root /var/www/project/public;
    listen 80;

    location / {
        # try to serve file directly, fallback to index.php
        try_files $uri /index.php$is_args$args;
    }

    # optionally disable falling back to PHP script for the asset directories;
    # nginx will return a 404 error when files are not found instead of passing the
    # request to Symfony (improves performance but Symfony's 404 page is not displayed)
    # location /bundles {
    #     try_files $uri =404;
    # }

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;

        # optionally set the value of the environment variables used in the application
        # fastcgi_param APP_ENV prod;
        # fastcgi_param APP_SECRET <app-secret-id>;
        # fastcgi_param DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name";

        # When you are using symlinks to link the document root to the
        # current version of your application, you should pass the real
        # application path instead of the path to the symlink to PHP
        # FPM.
        # Otherwise, PHP's OPcache may not properly detect changes to
        # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
        # for more information).
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        # Prevents URIs that include the front controller. This will 404:
        # http://domain.tld/index.php/some-path
        # Remove the internal directive to allow URIs like this
        internal;
    }

    # return 404 for all other php files not matching the front controller
    # this prevents access to other php files you don't want to be accessible.
    location ~ \.php$ {
        return 404;
    }

    error_log /var/log/nginx/project_error.log;
    access_log /var/log/nginx/project_access.log;
}

I hope this helps 😉

mishka81 commented 4 years ago

~~same problem with 1.9.15-alpine. @abdounikarim may be, that works for the default.conf template, but the issue here is that we want it to work for a custom template.~~

OK, finally that works for me with 1.9.15-alpine :

cseunion-nginx | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh cseunion-nginx | 20-envsubst-on-templates.sh: Running envsubst on /etc/nginx/templates/default.conf.template to /etc/nginx/conf.d/default.conf cseunion-nginx | 20-envsubst-on-templates.sh: Running envsubst on /etc/nginx/templates/site.conf.template to /etc/nginx/conf.d/site.conf cseunion-nginx | /docker-entrypoint.sh: Configuration complete; ready for start up

Both files gets created. This issue should be considered resolved.

phillipuniverse commented 3 years ago

Kind of an old thread but I would like to mention a workaround for parsing environment variables.

None of the entrypoint commands were executing for me (including envsubst) because I wanted to control my config file with an environment variable

COPY dev-config.nginx.conf /etc/nginx/dev-config.nginx.conf
ENV CONFIG_FILE='/etc/nginx/dev-config.nginx.conf'
CMD nginx -c "${CONFIG_FILE}" -g 'daemon off;'

Based on the very helpful comment from @TamerShlash and the linked table in the Docker docs, this turns the final command into what's below:

/docker-entrypoint.sh /bin/sh -c nginx -c "${CONFIG_FILE}" -g 'daemon off;'

And thus skips the entrypoint since the command is /bin/sh not nginx.

The key is that you need to use the array form of CMD. But if you use the array form of CMD, environment variable substitution doesn't work. So the fix is:

  1. Create an executable file called nginx with these contents:

    #!/bin/sh
    
    /usr/sbin/nginx -c "${CONFIG_FILE}" -g 'daemon off;'
  2. Copy that file to /usr/local/bin/nginx so that it's invoked prior to the other commands
  3. Change the CMD to just CMD ["nginx"]

The relevant Dockerfile change looks like:

ADD nginx /usr/local/bin/nginx
CMD ["nginx"]

This turns the final command for the Docker image into:

/docker-entrypoint.sh nginx

But nginx in this context uses /usr/local/bin/nginx not /usr/sbin/nginx. Since /usr/local/bin/nginx is an sh file, it will do environment substitution in the command. Because the command is nginx the entrypoint scripts are executed, including the ones for envsubst.

pravinfullstack commented 2 years ago

I am literally stuck with below setup, and I am facing the same issue.

# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# production stage
FROM nginx:latest as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html

COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]

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

My entrypoint file

#!/bin/sh
JSON_STRING='window.configs = { \
  "VUE_APP_VARIABLE_1":"'"${VUE_APP_VARIABLE_1}"'", \
  "VUE_APP_VARIABLE_2":"'"${VUE_APP_VARIABLE_2}"'" \
}'
sed -i "s@// CONFIGURATIONS_PLACEHOLDER@${JSON_STRING}@" /usr/share/nginx/html/index.html
exec "$@"
vue-app:
    container_name: vue-app
    image: vue-app
    volumes:
      - "./nginx/templates/:/etc/nginx/templates/"
    environment:
      MY_VARIABLE: 'I AM A VARIABLE'
    ports:
      - 8080:80
    restart: on-failure
thresheek commented 2 years ago

Hi @power-cut.

You're redefining an entrypoint script, which does the templating and variable substitution for nginx configuration.

denmely commented 11 months ago

"Simply place the entrypoint file inside the folder, and NGINX will execute all scripts within that folder."

COPY docker/entrypoint.sh /docker-entrypoint.d/
RUN chmod +x /docker-entrypoint.d/entrypoint.sh

image

mhabsaoui commented 6 months ago

Hi,

Just trying to achieve the Out-of-the-box environment variables in Docker nginx configuration (envsubst), in deploying a ReactJS app in Docker (on Ubuntu 24.04 LTS)

I honnestly don't see what I am missing, only get the original nginx default config and no substitution occurs :thinking:

Nginx template file :

server {
    listen       ${APP_SERVER_PORT};
    server_name  ${APP_SERVER_HOST};

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # 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;
    }
}

Dockerfile :

FROM nginx:1.26.0-alpine as integ
# Copy nginx config template
COPY --from=builder /app/nginx.conf.template /etc/nginx/templates
WORKDIR /usr/share/nginx/html
# Remove default nginx static assets
RUN rm -rf ./*
# Copy app static assets from builder stage
COPY --from=builder /app/dist .
# Containers run nginx with global directives and daemon off
ENTRYPOINT ["nginx", "-g", "daemon off;"]
HEALTHCHECK --interval=30s --timeout=10s \
    CMD wget -nv -t1 --spider http://${APP_SERVER_HOST}:${APP_SERVER_PORT}/#/healthz || exit 1

Compose file :

version: "3.8"
services:
  react:
    platform: "linux/amd64"
    restart: always
    init: true # Run an init inside the container that forwards signals and reaps processes
    build:
      context: .
      target: integ
    env_file: ./.env
    ports:
      - ${APP_SERVER_PORT}:${APP_SERVER_PORT}
    networks:
      - frontend
networks:
  frontend:
    driver: bridge

.env file :

APP_SERVER_HOST=0.0.0.0
APP_SERVER_PORT=3001
gspgsp commented 3 months ago

i have another problem, i defined the operation: COPY ./assets/templates/* /etc/nginx/templates/ in the Dockerfile, after run the container, there exist the tempalte config, and also in the conf.d, but the problem is, when i modified the configuration in /assets/templates, and run the contaienr again, the tempaltes configuration not modified. and also the configuration in the conf.d, i tried many times, but the problem still exist, is there any cache for the template?