You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Nginx实现无端口访问多Docker部署的网站?

实现多域名无端口访问Docker部署网站的方案

问题背景

在DigitalOcean Droplet上用Docker部署了2个网站,目前只能通过Docker Compose指定的端口(如3001:443)访问。希望用户仅通过域名就能用HTTP/HTTPS直接访问,但Droplet上已运行的Nginx占用80端口,且443端口只能被一个站点占用,启动第二个站点会触发端口冲突。当前HTTP请求因宿主机Nginx占用80无法正常跳转,只能通过带端口的HTTPS访问网站。

当前配置信息

宿主机Nginx配置

server {
    listen 443;
    server_name example.com;

    location ~ ^/(api) {
            proxy_pass http://example-backend:8080;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_cache_bypass $http_upgrade;
    }
}

server {
    listen 443;
    server_name demo.com;

    location ~ ^/(api) {
            proxy_pass http://demo-backend:9090;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_cache_bypass $http_upgrade;
    }
}

其中一个网站的Docker Compose配置

version: '3.7'
services:
  example-backend:
    build:
      context: ./backend
    ports:
      - "8080:8080"
    restart: always
    environment:
      SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
      SPRING_DATASOURCE_USERNAME: ${MYSQL_DB_USER}
      SPRING_DATASOURCE_PASSWORD: ${MYSQL_DB_PASSWORD}
      SPRING_JPA_HIBERNATE_DDL-AUTO: update
      SPRING_MAIL_USERNAME: ${SPRING_MAIL_USERNAME}
      SPRING_MAIL_PASSWORD: ${SPRING_MAIL_PASSWORD}

  # 存放静态资源的NGINX容器(已完成构建)
  example-frontend:
    build:
      context: ./frontend
    ports:
      - "3001:443"
    restart: always
    depends_on:
      - example-backend
    volumes:
      - ./web/conf.d:/etc/nginx/conf.d
      - ./web/dhparam:/etc/nginx/dhparam
      - ./web/certbot/data:/usr/share/nginx/html/letsencrypt
      - ./web/certbot/conf/:/etc/nginx/ssl

该网站容器内的Nginx配置

server {
    listen 80;
    server_name example.com;
    location ~ /.well-known/acme-challenge/ {
        allow all;
        root /usr/share/nginx/html/letsencrypt;
    }

    location / {
        return 301 https://example.com:3001$request_uri;
    }
}

server {
     listen 443 ssl;
     server_name example.com;

     server_tokens off;
     ssl_certificate /etc/nginx/ssl/live/example.com/fullchain.pem;
     ssl_certificate_key /etc/nginx/ssl/live/example.com/privkey.pem;
     ssl_dhparam /etc/nginx/dhparam/dhparam-2048.pem;

    location / {
        root /usr/share/nginx/html/;
        try_files $uri /index.html;
    }

    location /api/ {
        proxy_pass http://example-backend:8080;
    }

}

解决方案

核心思路:将宿主机Nginx作为统一入口,反向代理到各个Docker容器的内部端口;容器不再暴露80/443到宿主机,仅在内部网络通信;统一由宿主机处理SSL证书和HTTP转HTTPS跳转。

步骤1:修改Docker Compose配置,取消宿主机端口映射

把容器的端口映射改为仅内部暴露,避免宿主机端口冲突,同时添加自定义网络提升通信隔离性:

version: '3.7'
# 添加自定义网络
networks:
  example-network:
    driver: bridge

services:
  example-backend:
    build:
      context: ./backend
    # 取消宿主机端口映射,仅内部暴露
    expose:
      - "8080"
    restart: always
    environment:
      SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
      SPRING_DATASOURCE_USERNAME: ${MYSQL_DB_USER}
      SPRING_DATASOURCE_PASSWORD: ${MYSQL_DB_PASSWORD}
      SPRING_JPA_HIBERNATE_DDL-AUTO: update
      SPRING_MAIL_USERNAME: ${SPRING_MAIL_USERNAME}
      SPRING_MAIL_PASSWORD: ${SPRING_MAIL_PASSWORD}
    networks:
      - example-network

  example-frontend:
    build:
      context: ./frontend
    # 取消宿主机端口映射,仅内部暴露
    expose:
      - "443"
      - "80"
    restart: always
    depends_on:
      - example-backend
    volumes:
      - ./web/conf.d:/etc/nginx/conf.d
      - ./web/dhparam:/etc/nginx/dhparam
      - ./web/certbot/data:/usr/share/nginx/html/letsencrypt
      - ./web/certbot/conf/:/etc/nginx/ssl
    networks:
      - example-network

另一个网站的Compose配置做同样修改,使用独立的自定义网络或共享网络均可。

步骤2:修改宿主机Nginx配置,添加完整反向代理规则

处理HTTP(80端口)跳转与证书验证

添加server块统一处理所有域名的HTTP请求:

server {
    listen 80;
    server_name example.com demo.com;

    # 处理Let's Encrypt证书验证请求,需将容器的certbot目录同步到宿主机(如/var/www/letsencrypt)
    location ~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    # 所有HTTP请求跳转到HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

处理HTTPS(443端口)反向代理

修改原有443 server块,同时代理前端静态资源和后端API:

server {
    listen 443 ssl;
    server_name example.com;

    # 宿主机上的证书路径,建议统一在宿主机管理证书或同步容器证书目录
    ssl_certificate /path/to/certs/example.com/fullchain.pem;
    ssl_certificate_key /path/to/certs/example.com/privkey.pem;
    ssl_dhparam /path/to/dhparam-2048.pem;

    # 代理前端静态资源到容器内部端口
    location / {
        proxy_pass https://example-frontend:443;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # 代理后端API请求
    location ~ ^/(api) {
            proxy_pass http://example-backend:8080;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_cache_bypass $http_upgrade;
    }
}

server {
    listen 443 ssl;
    server_name demo.com;

    ssl_certificate /path/to/certs/demo.com/fullchain.pem;
    ssl_certificate_key /path/to/certs/demo.com/privkey.pem;
    ssl_dhparam /path/to/dhparam-2048.pem;

    # 代理前端静态资源到demo容器
    location / {
        proxy_pass https://demo-frontend:443;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # 代理demo后端API请求
    location ~ ^/(api) {
            proxy_pass http://demo-backend:9090;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_cache_bypass $http_upgrade;
    }
}

步骤3:简化容器内的Nginx配置

去掉端口跳转逻辑,由宿主机统一处理HTTP转HTTPS:

server {
    listen 80;
    server_name example.com;
    location ~ /.well-known/acme-challenge/ {
        allow all;
        root /usr/share/nginx/html/letsencrypt;
    }

    # 跳转至HTTPS,无需指定端口
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
     listen 443 ssl;
     server_name example.com;

     server_tokens off;
     ssl_certificate /etc/nginx/ssl/live/example.com/fullchain.pem;
     ssl_certificate_key /etc/nginx/ssl/live/example.com/privkey.pem;
     ssl_dhparam /etc/nginx/dhparam/dhparam-2048.pem;

    location / {
        root /usr/share/nginx/html/;
        try_files $uri /index.html;
    }

    location /api/ {
        proxy_pass http://example-backend:8080;
    }

}

更简化的方式是让容器内Nginx仅监听80端口,宿主机代理时使用http://example-frontend:80,将SSL处理完全交给宿主机,减少容器内配置复杂度。

步骤4:重启服务生效

  1. 重启Docker容器:
docker-compose down && docker-compose up -d
  1. 重启宿主机Nginx:
sudo systemctl restart nginx

注意事项

  • 确保宿主机能通过Docker服务名(如example-frontend)访问容器内部端口,自定义网络下默认支持服务名解析。
  • 证书管理建议统一在宿主机用Certbot申请,再挂载到各个容器,避免重复申请。
  • 检查Droplet防火墙规则,确保80、443端口对外开放。

内容的提问来源于stack exchange,提问作者ServletException

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 10:18:10