Set nginx fallback when proxy fails because docker container is not running - docker

I am trying to get nginx to proxy a subdomain to another docker container or return an error if the container is not available.
My issue is that nginx seems to check if the server is available and refuses to start if it is not.
I tried using error_page like described here but it didn't change anything about the issue.
The following configuration works if both containers are up and running:
server {
server_name localhost;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
return 501;
}
}
server {
server_name *.ref.localhost ref.localhost;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_intercept_errors on;
proxy_pass http://reference:5000;
}
}
When I kill the reference container after nginx is running, I get a 502 error, but when I try to start the nginx container without the reference container running, nginx crashes with the following error message:
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Recreating nginx ... done
Attaching to nginx
nginx | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
nginx | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
nginx | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
nginx | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
nginx | 10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf differs from the packaged version
nginx | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
nginx | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
nginx | /docker-entrypoint.sh: Configuration complete; ready for start up
nginx | 2021/05/15 12:55:51 [emerg] 1#1: host not found in upstream "reference" in /etc/nginx/conf.d/default.conf:25
nginx | nginx: [emerg] host not found in upstream "reference" in /etc/nginx/conf.d/default.conf:25
nginx | 2021/05/15 12:55:53 [emerg] 1#1: host not found in upstream "reference" in /etc/nginx/conf.d/default.conf:25
nginx | nginx: [emerg] host not found in upstream "reference" in /etc/nginx/conf.d/default.conf:25
nginx exited with code 1
Both nginx and reference are running with docker-compose:
version: "3.8"
services:
nginx:
container_name: nginx
restart: always
build:
context: ./nginx
ports:
- "80:80"
reference:
container_name: reference
build:
context: ./app
nginx/Dockerfile
# pull the Node.js Docker image
FROM node:alpine
# create the directory inside the container
WORKDIR /usr/src/app
# copy the package.json files from local machine to the workdir in container
COPY package*.json ./
# run npm install in our local machine
RUN npm install
# copy the generated modules and all other files to the container
COPY . .
# our app is running on port 5000 within the container, so need to expose it
EXPOSE 5000
# the command that starts our app
CMD ["node", "index.js"]
reference/Dockerfile:
# pull the Node.js Docker image
FROM node:alpine
# create the directory inside the container
WORKDIR /usr/src/app
# copy the package.json files from local machine to the workdir in container
COPY package*.json ./
# run npm install in our local machine
RUN npm install
# copy the generated modules and all other files to the container
COPY . .
# our app is running on port 5000 within the container, so need to expose it
EXPOSE 5000
# the command that starts our app
CMD ["node", "index.js"]

Related

nginx conditional proxy pass based on environment variable

I would like to proxy_pass to the related service conditionally based on environment variable.. What I mean, prox_pass adress should be change based on NODE_ENV variable..
What is the best approach of doing this ? Can I use if statement like as below for proxy_pass? If yes how should I do this ? Apart from this, I tried to create a bash as below as below to pass environment variable to nginx but could not able to set and pass $NGINX_BACKEND_ADDRESS to nginx conf somehow. Any help will be appreciated
if ($NODE_ENV == "development) {
proxy_pass http://myservice-dev;
}
nginx.conf
server {
listen 3000;
location / {
root /usr/src/app;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /csrf/token {
proxy_pass ${NGINX_BACKEND_ADDRESS}/csrf/token;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /export/apis {
proxy_pass ${NGINX_BACKEND_ADDRESS}/export/apis;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
entrypoint.sh
#!/usr/bin/env sh
set -eu
export NODE_ENV=development
if ["$NODE_ENV" == "development"]
then
export NGINX_BACKEND_ADDRESS=http://backend-dev
elif ["$NODE_ENV" == "stage"]
then
export NGINX_BACKEND_ADDRESS=http://backend-stage
elif ["$NODE_ENV" == "development"
then
export NGINX_BACKEND_ADDRESS=http://backend-preprod
elif ["$NODE_ENV" == "development"]
then
export NGINX_BACKEND_ADDRESS=http://backend
else
echo "Error in reading environment variable in nginx-conf.sh."
fi
echo "Will proxy requests for to ${NGINX_BACKEND_ADDRESS}*"
exec /nginx-conf.sh "$#"
Dockerfile
FROM nginx:alpine AS production-build
WORKDIR /usr/src/app
ARG NODE_ENVIRONMENT=development
ENV NODE_ENV=$NODE_ENVIRONMENT
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/nginx.conf.template /etc/nginx/conf.d/default.conf.template
COPY nginx-conf.sh /
RUN chgrp -R root /var/cache/nginx /var/run /var/log/nginx /var/run/nginx.pid && \
chmod -R 775 /var/cache/nginx /var/run /var/log/nginx /var/run/nginx.pid
USER nginx
COPY --from=builder /usr/src/app/dist .
ENTRYPOINT ["/nginx-conf.sh", $NODE_ENVIRONMENT]
EXPOSE 3000
CMD ["nginx", "-g", "daemon off;"]
The Docker Hub nginx image (as of nginx:1.19) has a facility to do environment-variable replacement in configuration files:
[...] this image has a function, which will extract environment variables before nginx starts. [...] this function reads template files in /etc/nginx/templates/*.template and outputs the result of executing envsubst to /etc/nginx/conf.d.
So your first step is to rename your configuration file as is (including proxy_pass ${NGINX_BACKEND_ADDRESS}/...) to something like default.conf.template and put it in the required directory.
I would directly pass that address in your deploy-time configuration. I would not include it in the image in any way. (Imagine setups like "a developer is trying to run this stack on their local desktop system" where none of the URLs in the entrypoint script are right.) That also lets you get rid of pretty much all the code here; you would just have
# Dockerfile
FROM ... AS builder
...
FROM nginx:1.21-alpine
COPY nginx/nginx.conf.template /etc/nginx/conf.d/default.conf.template
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
# Permissions, filesystem layout, _etc._ are fine in the base image
# Use the base image's ENTRYPOINT/CMD
# docker-compose.yml
version: '3.8'
services:
proxy:
build: .
ports: ['8000:80']
environment:
- NGINX_BACKEND_ADDRESS=https://backend-prod.example.com
If you are in fact using Compose, you can use multiple docker-compose.yml files to provide settings for specific environments.
# docker-compose.local.yml
# Run the backend service locally too in development mode
version: '3.8'
services:
backend: # not in docker-compose.yml
build: backend
# and other settings as required
nginx: # overrides docker-compose.yml settings
environment:
- NGINX_BACKEND_ADDRESS=http://backend
# no other settings
docker-compose -f docker-compose.yml -f docker-compose.local.yml up
if you want to run an if statement in your dockerfile, then you can use the RUN command in the dockerfile, for example using bash,
RUN if [[ -z "$arg" ]] ; then echo Argument not provided ; else echo Argument is $arg ; fi
The way i normally do this is I have a generic nginx proxy and i then just pass in the url and protocol as env vars
ubuntu#vps-f116ed9f:/opt/docker_projects/docker_examples/load_balancer$ cat proxy.conf
server {
listen 80 default_server;
resolver 127.0.0.11 valid=1s;
set $protocol $PROXY_PROTOCOL;
set $upstream $PROXY_UPSTREAM;
location / {
proxy_pass $protocol://$upstream$request_uri;
proxy_pass_header Authorization;
proxy_http_version 1.1;
proxy_ssl_server_name on;
proxy_set_header Host $upstream;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";
proxy_buffering off;
proxy_read_timeout 5s;
proxy_redirect off;
proxy_ssl_verify off;
client_max_body_size 0;
}
}
ubuntu#vps-f116ed9f:/opt/docker_projects/docker_examples/load_balancer$ cat Dockerfile
FROM nginx:1.13.8
ENV PROXY_PROTOCOL=http PROXY_UPSTREAM=example.com
COPY proxy.conf /etc/nginx/conf.d/default.template
COPY start.sh /
CMD ["/start.sh"]
I then have a start script that will substitue the env vars into my proxy_config.
ubuntu#vps-f116ed9f:/opt/docker_projects/docker_examples/load_balancer$ cat start.sh
#!/usr/bin/env bash
envsubst '$PROXY_PROTOCOL,$PROXY_UPSTREAM' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf
exec nginx -g 'daemon off;'

Worker Tiemout Upstream Gunicorn on Docker (Bad Gateway 502)

I'm trying to run a platform building on Django, Docker, Nginx and Gunicorn from my Ubuntu server.
Before to ask you, i'm reading about my problem and i did on my nginx.conf:
location / {
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
...
}
Then, on my Gunicorn settings:
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "-t 90", "config.wsgi:application"]
The problem persists and server always returns: 502 Bad Gateway. When i try to access to:
http://34.69.240.210:8000/admin/
From browser, the server redirect to
http://34.69.240.210:8000/admin/login/?next=/admin/
But show the error:
My Dockerfile:
FROM python:3.8
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY . /code/
RUN pip install -r requirements.txt
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "-t 90", "config.wsgi:application"]
My docker-compose.yml:
version: "3.8"
services:
django_app:
build: .
volumes:
- static:/code/static
- .:/code
nginx:
image: nginx:1.15
ports:
- 8000:8000
volumes:
- ./config/nginx/conf.d:/etc/nginx/conf.d
- static:/code/static
depends_on:
- django_app
volumes:
.:
static:
My Nginx file:
upstream django_server {
server django_app:8000 fail_timeout=0;
}
server {
listen 8000;
server_name 34.69.240.210;
keepalive_timeout 5;
client_max_body_size 4G;
location / {
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
proxy_pass http://django_server;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
}
Any idea what can i do to fix it?
Thank you.
Well, for the record.
My problem was the database connection. My Docker container couldn't connect to potsgres local database.
So, i added this line to postgresql.conf:
listen_addresses = '*'
Then, i added this line to pg_hba.conf
host all all 0.0.0.0/0 md5
Restart Postgres:
sudo service postgresql restart
My host postgres ip:
172.17.0.1
Test from inside docker:
psql -U myuser -d databasename -h 172.17.0.1 -W
Done! :)

Docker image on Google Compute Engine wont run on port 80

I am deploying my Next.js / Nginx docker image from Container Registry to Compute Engine
Once deployed the application is running as expected, but its running on port 3000 instead of 80 - i.e. I want to access it at <ip_address> but I can only access it on <ip_address>:3000. I have setup a reverse proxy in Nginx to forward port 3000 to 80 but it does not seem to be working.
When I run docker-compose up the app is accessible on localhost (rather than localhost:3000)
Dockerfile
FROM node:alpine as react-build
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json /usr/src/app
RUN npm install
RUN npm install --global pm2
COPY . /usr/src/app
RUN npm run build
# EXPOSE 3000
EXPOSE 80
CMD [ "pm2-runtime", "start", "npm", "--", "start" ]
docker-compose.yml
version: '3'
services:
nextjs:
build: ./
nginx:
build: ./nginx
ports:
- 80:80
./nginx/Dockerfile
FROM nginx:alpine
# Remove any existing config files
RUN rm /etc/nginx/conf.d/*
# Copy config files
# *.conf files in "conf.d/" dir get included in main config
COPY ./default.conf /etc/nginx/conf.d/
./nginx/default.conf
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:10m inactive=7d use_temp_path=off;
server {
listen 80;
gzip on;
gzip_proxied any;
gzip_comp_level 4;
gzip_types text/css application/javascript image/svg+xml;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# BUILT ASSETS (E.G. JS BUNDLES)
# Browser cache - max cache headers from Next.js as build id in url
# Server cache - valid forever (cleared after cache "inactive" period)
location /_next/static {
#proxy_cache STATIC;
proxy_pass http://localhost:3000;
}
# STATIC ASSETS (E.G. IMAGES)
# Browser cache - "no-cache" headers from Next.js as no build id in url
# Server cache - refresh regularly in case of changes
location /static {
#proxy_cache STATIC;
proxy_ignore_headers Cache-Control;
proxy_cache_valid 60m;
proxy_pass http://locahost:3000;
}
# DYNAMIC ASSETS - NO CACHE
location / {
proxy_pass http://locahost:3000;
}
}

Couldn't start nginx-proxy docker image with error "Contents of /etc/nginx/conf.d/default.conf did not change"

I am playing with "nginx-proxy", pulled the image "jwilder/nginx-proxy:latest" to my local host, tried to start it but got this error "Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload'", and when I tried to go to the server at port 80: it returned 503 Bad Gateway:
Here is my docker-compose file:
version: '3.0'
services:
proxy:
image: jwilder/nginx-proxy:latest
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- /etc/nginx/vhost.d
- /usr/share/nginx/html
- /docker/certs:/etc/nginx/certs:ro
network_mode: "bridge"
and the error I've got
WARNING: /etc/nginx/dhparam/dhparam.pem was not found. A pre-generated dhparam.pem will be used for now while a new one is being generated in the background.Once the new dhparam.pem is in place, nginx will be reloaded.
forego | starting dockergen.1 on port 5000
forego | starting nginx.1 on port 5100
dockergen.1 | 2018/07/18 04:14:14 Generated '/etc/nginx/conf.d/default.conf' from 1 containers
dockergen.1 | 2018/07/18 04:14:14 Running 'nginx -s reload'
dockergen.1 | 2018/07/18 04:14:14 Watching docker events
dockergen.1 | 2018/07/18 04:14:14 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload'
Any idea is much appreciated. Cheers
It's default behavior for this image, you can see /etc/nginx/conf.d/default.conf:
server {
server_name _;
listen 80;
access_log /var/log/nginx/access.log vhost;
return 503;
}
So when your visit, it will give 503 error.
This is a service discovery service, so you need to use it.
See the official example:
docker-compose.yaml
version: '2'
services:
nginx-proxy:
image: jwilder/nginx-proxy
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
whoami:
image: jwilder/whoami
environment:
- VIRTUAL_HOST=whoami.local
If you use docker-compose up to start it, then have a look at /etc/nginx/conf.d/default.conf again, you will see:
server {
server_name _;
listen 80;
access_log /var/log/nginx/access.log vhost;
return 503;
}
# whoami.local
upstream whoami.local {
## Can be connected with "a_default" network
# a_whoami_1
server 172.20.0.2:8000;
}
server {
server_name whoami.local;
listen 80 ;
access_log /var/log/nginx/access.log vhost;
location / {
proxy_pass http://whoami.local;
}
}
Here, jwilder/nginx-proxy watch the events of docker, and add a proxy to nginx reverse settings.
So if execute curl -H "Host: whoami.local" localhost on your host machine, it will print I'm 5b129ab83266.
server 172.20.0.2 in nginx settings is your application container's ip, it will changes everytime you start a new container, so with this method, you can free to know the ip of your application container, just use inverse proxy from nginx.
Many service such as marathon-lb which is a component of marathon who known as a mesos framework also could afford such function, maybe k8s also? Anyway, you need to know principle of this image, a useful doc for your reference: http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/
Hung Vu, was that the entirety of your docker-compose file, or just the section for jwilder/nginx-proxy? I ask because I experienced this error when I had simply forgotten to add the "virtual_host" environment variable for my other services, after dropping in that service definition for the nginx-proxy. Doh! :-) See its docs, or atline's example here.
I ran into this issue in 2021, but I was able to resolve it. Documenting as this thread is at the top of Google searches.
tl;dr: Run the containers without a custom NGINX config at least once
This happened to me as I was migrating from one host to another. I migrated all of my files over and then started running my containers one by one.
For me, I had a custom NGINX config file that proxied a path to a separate docker container that was not created yet.
- "~/gotti/volumes/nginx-configs:/etc/nginx/vhost.d"
in this mount, I had a mydomain.conm config file with the following contents
# reverse proxy
location /help {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://ghost:2368; <----- Problem: This container didn't exist yet.
proxy_redirect off;
}
This invalid reference prevented NGINX from proxying my app and thus prevented a SSL cert from being issued.

Docker nginx cannot reverse proxy within default network

I'm having problems with nginx reverse proxying as a docker container. My question is about how to correctly proxy pass nginx in a default docker network?
Here's my docker-compose.yml (unnecessary details omitted for brevity)
version: '3'
networks:
nginx_default:
external: true
services:
db:
image: postgres:10.2
ports:
- "5432:5432"
environment: ...
postgrest:
image: postgrest/postgrest
ports:
- "3000:3000"
environment: ...
nginx:
restart: always
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/sites-enabled/ruler
command: [nginx-debug, '-g', 'daemon off;']
webapp:
build:
context: "./"
dockerfile: Dockerfile.dev
volumes: ...
ports:
- "3001:3001"
environment: ...
Here's my nginx.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name _;
gzip on;
gzip_proxied any;
gzip_types text/plain text/xml text/css application/x-javascript;
gzip_vary on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
location / {
try_files $uri #node;
}
location /api/ {
try_files $uri #postgrest;
}
location #node {
proxy_pass http://webapp:3001;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_cache_control;
add_header X-Proxy-Cache $upstream_cache_status;
}
location #postgrest {
proxy_pass http://postgrest:3000;
proxy_http_version 1.1;
default_type application/json;
proxy_set_header Connection "";
proxy_hide_header Content-Location;
add_header Content-Location /api/$upstream_http_content_location;
}
}
And my Dockerfile.dev
FROM node:8.9
WORKDIR /client
CMD npm run dev -- -p 3001
When I do $ docker-compose up -d everything starts without an error. After that I can successfully do $ curl http://127.0.0.1:3001/ (webapp) and $ curl http://127.0.0.1:3000 (postgrest).
But when I try $ curl http://127.0.0.1:8080/ (nginx should handle here the proxying) I get default nginx welcome page. Also $ curl http://127.0.0.1:8080/api/ is not hitting the API :/
What may be the cause? Using $ docker inspect I see that every container is in the same default network.
Edit: Using $ docker-compose logs seems like the default network is not used at all O_o
docker-compose logs
WARNING: Some networks were defined but are not used by any service: nginx_default
Attaching to ruler_webapp_1, ruler_nginx_1
webapp_1 |
webapp_1 | > ruler# dev /client
webapp_1 | > next "-p" "3001"
webapp_1 |
webapp_1 | > Using external babel configuration
webapp_1 | > Location: "/client/.babelrc"
webapp_1 | DONE Compiled successfully in 1741ms09:04:49
webapp_1 |
My guess is you mapped your local nginx configuration file to the wrong file on the container side. The default configuration file for the nginx image is located at /etc/nginx/conf.d/default.conf so the volume of the nginx container should be:
./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
You can check your configuration file is used correctly by executing:
docker-compose exec nginx nginx -T
Side notes:
Never use the latest tag, because in some time you may face broken compatibility issues. Use fixed version tag 1, 1.13 etc. instead
You don't need to publish ports everywhere, eg. 3000:3000, 3001:3001. Those ports will be accessible internally by containers
Your config is a partial config and not a complete nginx config. So it needs to go inside conf.d inside the container and not on nginx.conf or sites-enabled. So change
volumes:
- ./nginx/nginx.conf:/etc/nginx/sites-enabled/ruler
to
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
And now it should start working

Resources