'Host docker nginx multiple apps on same host Ubuntu

I want to manage reverse proxy (Nginx) with multiple Independent webapps on the same host. Means I have 4 webapps that are hosted on containers separately via docker-compose file and behind with Nginx. How I can handle this scenario, like I have 4 different environments (dev/qa/uat/prod) all are have septate apps, app1/app2/app3/app4 which have their own Nginx instances, all should be deployed on same Host Ubuntu(20.4)

When I run docker-compose-app1.yml its up and run as expected and I can browse (localhost --> https://localhost/login) same as host ip as well BUT

Problem statement: When I run docker-compose-app2.yml it gives following error

Container app2_nginx Starting
2.0s Error response from daemon: driver failed programming external connectivity on endpoint app2_nginx (18b62acf9312fd57cad9979bd0ebde963ff57f32a7489b11faacee4cf4b02b97): Bind for 0.0.0.0:443 failed: port is already allocated The terminal process "C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -Command docker compose -f "docker-compose-app2.yml" up -d --build" terminated with exit code: 1.

app1 Docker file (same for all web app)

FROM node:lts-alpine
ENV NODE_ENV=production

WORKDIR /usr/src/app
COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
RUN npm install --production --silent && mv node_modules ../
COPY . .
EXPOSE 3000
RUN chown -R node /usr/src/app

USER node
CMD ["npm", "start"]

Docker-compose-app1.yml (same for all except image/container/ssl certificates and app host port)

version: '3.4'

services:
  nginx:   
    image: nginx:latest
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes: 
      - ./site:/usr/share/nginx/html
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./nginx/app.com.crt:/etc/nginx/app1.com.crt
      - ./nginx/app.com.key:/etc/nginx/app1.com.key
    # environment:
      # SITES: 'app.com=app_ssl:80'
      # FORCE_HTTPS: "true"      
    depends_on:
      - app1
    container_name: app1_nginx

  app1:    
    image: app1
    build:
      context: .
      dockerfile: ./Dockerfile
    environment:
      NODE_ENV: production
    restart: always
    ports:
      - 9200:3000
      # - 801:80
    container_name: app1

Default.conf for app1

server {
        listen 80;
        server_name www.app1.com;
        index index.html;
            # root  /usr/share/nginx/html;
        location / {
        root         /usr/share/nginx/html;
        rewrite ^ https://$host$request_uri? permanent;
        error_log /etc/nginx/app1_errors.log;
        return 301 https://$server_name$request_uri;
      }
    }
    server { # This new server will watch for traffic on 443        
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name app1.com;            
        ssl_certificate /etc/nginx/app1.com.crt;
        ssl_certificate_key /etc/nginx/app1.com.key;        
        ssl_protocols TLSv1.2 TLSv1.1 TLSv1; 
        add_header Strict-Transport-Security max-age=500;       
        root /usr/share/nginx/html;    
        location / {
          proxy_pass http://hosyip:9200;
          error_log /etc/nginx/app1_errors.log;
          proxy_set_header X-Forwarded-Proto https;
        }
    }

Folder structure for webapp1, same as reset of other 3 Webapps

- app1
- nginx
-- nginx.conf
- docker-compose.yml


Solution 1:[1]

The issue is that you publish all container ports to the same host port, thats what the error is saying

Bind for 0.0.0.0:443 failed: port is already allocated

When you run the for first time docker-compose up there is no issue, but its subsequent one requests the same host port

In these cases, you could tackle this by not explicitily binding the ports to a specific host port, but instead let the system find the free ones.

docker-compose.yml

version: '3.4'

services:
  nginx:   
    image: nginx:latest
    restart: unless-stopped
    ports:
# NOT explicit bind
      - 80
      - 443
    volumes: 
      - ./site:/usr/share/nginx/html
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./nginx/app.com.crt:/etc/nginx/app1.com.crt
      - ./nginx/app.com.key:/etc/nginx/app1.com.key
    # environment:
      # SITES: 'app.com=app_ssl:80'
      # FORCE_HTTPS: "true"      
    depends_on:
      - app1
    container_name: app1_nginx

  app1:    
    image: app1
    build:
      context: .
      dockerfile: ./Dockerfile
    environment:
      NODE_ENV: production
    restart: always
    # There is no need to expose these ports since you use nginx for load balancing
    # ports:
      # - 9200:3000
      # - 801:80
    container_name: app1

Since the containers are able to perform dns discovery you could alter your nginx default.conf to find the container

default.conf

server {
        listen 80;
        server_name www.app1.com;
        index index.html;
            # root  /usr/share/nginx/html;
        location / {
        root         /usr/share/nginx/html;
        rewrite ^ https://$host$request_uri? permanent;
        error_log /etc/nginx/app1_errors.log;
        return 301 https://$server_name$request_uri;
      }
    }
    server { # This new server will watch for traffic on 443        
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name app1.com;            
        ssl_certificate /etc/nginx/app1.com.crt;
        ssl_certificate_key /etc/nginx/app1.com.key;        
        ssl_protocols TLSv1.2 TLSv1.1 TLSv1; 
        add_header Strict-Transport-Security max-age=500;       
        root /usr/share/nginx/html;    
        location / {
          # proxy_pass http://hosyip:9200;
          # Docker can resolve the hostname, you can use the container name
          proxy_pass http://app1:3000;
          error_log /etc/nginx/app1_errors.log;
          proxy_set_header X-Forwarded-Proto https;
        }
    }

After docker-compose up your OS will assign random ports for the nginx container, so no conflict will arise. An example

enter image description here

To access app1 curl localhost:49155

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tolis Gerodimos