Cloud Native 11 min read

How to Hide Multiple Docker Apps Behind a Single Nginx Gateway

This guide shows how to containerize five services, expose only ports 80/443 via an Nginx reverse‑proxy container, route traffic by sub‑domain, secure everything with HTTPS, and simplify deployment and maintenance using Docker‑Compose.

Xiao Liu Lab
Xiao Liu Lab
Xiao Liu Lab
How to Hide Multiple Docker Apps Behind a Single Nginx Gateway

Why You Need an “Nginx Unified Gateway”?

Running each application in its own container quickly leads to a proliferation of exposed ports, scattered IP addresses, and duplicated SSL certificates, which makes firewalls messy, user experience poor, and security weak. A single Nginx proxy that listens only on 80/443 and routes requests by domain solves all these problems.

Port reduction: Only 80 and 443 are open to the outside; internal services stay hidden.

Better UX: Users access services via easy‑to‑remember sub‑domains like www.xlsys.cn instead of IP + port.

Centralised SSL: Nginx handles certificates for all sub‑domains, while backend services communicate over internal HTTP.

Improved security: Backend containers are unreachable from the public network; only Nginx can reach them.

One‑Click Deployment: docker‑compose.yml

version: '3.8'
networks:
  secure-net:
    driver: bridge
services:
  nginx-proxy:
    image: nginx:alpine
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/certs:/etc/nginx/certs
      - ./nginx/logs:/var/log/nginx
    networks:
      - secure-net
    restart: unless-stopped

  wordpress:
    image: wordpress:php8.2-apache
    container_name: wordpress
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: wppass123
      WORDPRESS_DB_NAME: wpdb
    volumes:
      - wordpress_data:/var/www/html
    networks:
      - secure-net
    restart: unless-stopped
    depends_on:
      - mysql

  mysql:
    image: mariadb:10.6
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: rootpass123
      MYSQL_DATABASE: wpdb
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: wppass123
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - secure-net
    restart: unless-stopped

  ddns-go:
    image: jeessy/ddns-go
    container_name: ddns-go
    ports:
      - "9876:9876"
    networks:
      - secure-net
    volumes:
      - ./ddns-go:/root
    environment:
      - PUID=1000
      - PGID=1000
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000"
    networks:
      - secure-net
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=yourpassword
      - GF_USERS_ALLOW_SIGN_UP=false
    restart: unless-stopped

  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    ports:
      - "3001"
    networks:
      - secure-net
    volumes:
      - uptime_kuma_data:/app/data
    restart: unless-stopped

  trilium:
    image: zadam/trilium:latest
    container_name: trilium
    ports:
      - "8080"
    networks:
      - secure-net
    volumes:
      - trilium_data:/home/node/trilium-data
    environment:
      - TRILIUM_PORT=8080
    restart: unless-stopped

volumes:
  wordpress_data:
  mysql_data:
  grafana_data:
  uptime_kuma_data:
  trilium_data:

Nginx Configuration Files (Sub‑domain Routing)

blog.conf (WordPress)

server {
    listen 80;
    server_name www.xlsys.cn;
    location / {
        proxy_pass http://wordpress;
        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;
    }
}

ddns.conf (DDNS‑GO)

server {
    listen 80;
    server_name ***.xlsys.cn;
    location / {
        proxy_pass http://localhost:9876;
        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;
    }
}

monitor.conf (Uptime Kuma)

server {
    listen 80;
    server_name ***.xlsys.cn;
    location / {
        proxy_pass http://uptime-kuma:3001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_xforwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

metrics.conf (Grafana)

server {
    listen 80;
    server_name ***.xlsys.cn;
    location / {
        proxy_pass http://grafana:3000;
        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;
    }
}

note.conf (Trilium Notes)

server {
    listen 80;
    server_name ***.xlsys.cn;
    location / {
        proxy_pass http://trilium:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_xforwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Security Recommendations

Enable HTTPS for each sub‑domain using acme.sh or Certbot.

Restrict access to sensitive services (e.g., lab.xlsys.cn) with IP whitelists.

Regularly audit Nginx access logs (e.g., /opt/nginx-proxy/logs/access.log).

Back up persistent volumes such as wordpress_data and trilium_data.

My Actual Results

All services are reachable via *.xlsys.cn, giving a professional look.

Only ports 80/443 are exposed to the internet; internal ports are closed.

Adding a new service only requires dropping a new .conf file, reducing operational overhead.

The setup is ready for WAF, CDN, and full‑site HTTPS.

In summary, an Nginx container can act as a central control panel for all internal services, keeping them hidden, secure, and easy to manage.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

devopscontainerreverse proxy
Xiao Liu Lab
Written by

Xiao Liu Lab

An operations lab passionate about server tinkering 🔬 Sharing automation scripts, high-availability architecture, alert optimization, and incident reviews. Using technology to reduce overtime and experience to avoid major pitfalls. Follow me for easier, more reliable operations!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.