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

如何解决同一主机多套带SSL的Docker+Nginx环境访问异常问题?

问题:同一主机多Docker环境配置虚拟主机+SSL后,访问Staging域名跳转到生产应用

我在同一主机上运行两套Docker环境,每套包含1个postgres容器、1个nginx容器和1个Web应用容器,原本靠内部IP+端口访问没问题。给两套环境加了自签名证书、在nginx里配置虚拟主机后,生产环境正常运行,但访问Staging域名时打开的却是生产应用,怀疑和端口映射有关但没找到根因。

现有配置

1. 生产环境

docker-compose.yml

version: '3'
services:
  postgres:
    image: postgres
    restart: unless-stopped
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    ports:
      - 5432
    volumes:
      - postgres:/var/lib/postgresql/data

  web:
    build: .
    restart: unless-stopped
    command: >
      bash -c "bundle exec hanami db migrate
      && bundle exec rake initial_settings:add_default_language
      && bundle exec rake initial_settings:add_session_validity
      && bundle exec rake import_user:create
      && bundle exec rake super_admin:create
      && bundle exec rake create_parser_rules:start
      && bundle exec hanami server"
    links:
      - postgres
    depends_on:
      - postgres

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    tty: true
    ports:
      - "${NGINX_PORT}:80"
      - 443:443
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - web

volumes:
  postgres:
  nginx:

nginx.conf

user nginx;

events {}

http {
    server {
         listen 80;
         server_name my_app.my_domain.com;
         return 301 https://$server_name$request_uri;
         }

    upstream appname {
        server web:2300;
    }

    server {
        listen 443;
        ssl on;
        server_name my_app.my_domain.com;
        ssl_certificate /etc/nginx/app-nginx-selfsigned.crt;
        ssl_certificate_key /etc/nginx/app-nginx-selfsigned.key;

        location / {
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header X-NginX-Proxy true;

            proxy_pass http://appname;
            proxy_redirect off;

            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    }
}

2. Staging环境

docker-compose.yml

version: '3'
services:
  postgres:
    image: postgres
    restart: unless-stopped
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    ports:
      - 5432
    volumes:
      - postgres_staging:/var/lib/postgresql/data

  web:
    image: tmsr-stage:latest
    restart: unless-stopped
    command: >
      bash -c "bundle exec hanami db migrate
      && bundle exec rake initial_settings:add_default_language
      && bundle exec rake initial_settings:add_session_validity
      && bundle exec rake import_user:create
      && bundle exec rake super_admin:create
      && bundle exec rake create_parser_rules:start
      && bundle exec hanami server"
    links:
      - postgres
    depends_on:
      - postgres

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    tty: true
    ports:
        - "${NGINX_PORT}:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - web

volumes:
  postgres_staging:
  nginx:

nginx.conf

user nginx;

events {}

http {
    server {
        listen 80;
        server_name app-stage.my_domain.com;
        return 301 https://$server_name$request_uri;
        }

    upstream app-stage{
        server web:2300;
    }

    server {
        listen 443;
        ssl on;
        server_name app-stage.my_domain.com;
        ssl_certificate /etc/nginx/stage-nginx-selfsigned.crt;
        ssl_certificate_key /etc/nginx/stage-nginx-selfsigned.key;

        location / {
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header X-NginX-Proxy true;

            proxy_pass http://app-stage;
            proxy_redirect off;

            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    }
}

已尝试的操作

  1. 把Staging环境的Nginx端口改为随机映射:
ports:
    - 80
    - 443

启动后端口输出显示Staging的Nginx绑定了主机随机端口(如49230->80、49229->443),但访问Staging域名还是跳转到生产应用。

  1. 给Staging环境指定8443端口:
ports:
  - "${NGINX_PORT}:80"
  - 8443:8443

同时修改Staging的Nginx配置监听8443,此时访问https://app-stage.my_domain.com:8443能正常打开Staging应用,但不指定端口时还是跳生产,这种方案没法支持后续5个环境的部署需求。

正确配置方案

核心问题是同一主机上不能同时有多个容器绑定到主机的80/443端口——生产环境已经占了443,所有发往主机443的请求都会被生产环境的Nginx接收,自然不管域名是哪个都返回生产应用。

最优解是用一个全局Nginx作为统一入口,专门绑定主机的80和443端口,然后根据域名把请求转发到对应环境的内部Nginx,每个环境的Nginx只负责处理自己的应用代理,不需要绑定主机端口。

步骤1:修改两套环境的Nginx端口配置

分别修改生产和Staging环境的docker-compose.yml中Nginx的ports部分,去掉主机端口映射,只保留容器内部端口:

ports:
  # 仅保留容器内部端口,不映射到主机
  - 80
  - 443

步骤2:部署全局Nginx容器

创建全局Nginx的docker-compose.yml:

version: '3'
services:
  global-nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./global-nginx.conf:/etc/nginx/nginx.conf
      # 把两套环境的证书文件挂载到全局Nginx容器内(路径自行调整)
      - ./prod/certs:/etc/nginx/prod-certs
      - ./stage/certs:/etc/nginx/stage-certs
    # 加入生产和Staging环境的Docker网络(默认网络名是「项目名_默认」,比如生产是app_prod_default)
    networks:
      - app_prod_default
      - app_staging_default

networks:
  app_prod_default:
    external: true
  app_staging_default:
    external: true

步骤3:配置全局Nginx的虚拟主机

创建global-nginx.conf:

user nginx;

events {}

http {
    # 生产环境转发规则
    server {
        listen 80;
        server_name my_app.my_domain.com;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl;
        server_name my_app.my_domain.com;
        ssl_certificate /etc/nginx/prod-certs/app-nginx-selfsigned.crt;
        ssl_certificate_key /etc/nginx/prod-certs/app-nginx-selfsigned.key;

        location / {
            # 转发到生产环境的Nginx容器(容器名可通过`docker ps`查看)
            proxy_pass http://app_prod_nginx_1;
            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;
        }
    }

    # Staging环境转发规则
    server {
        listen 80;
        server_name app-stage.my_domain.com;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl;
        server_name app-stage.my_domain.com;
        ssl_certificate /etc/nginx/stage-certs/stage-nginx-selfsigned.crt;
        ssl_certificate_key /etc/nginx/stage-certs/stage-nginx-selfsigned.key;

        location / {
            # 转发到Staging环境的Nginx容器
            proxy_pass http://app_staging_nginx_1;
            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;
        }
    }
}

步骤4:启动所有服务

先重启生产和Staging环境,再启动全局Nginx容器。这样所有发往主机80/443的请求都会被全局Nginx接收,根据域名转发到对应环境的内部Nginx,完美解决端口冲突问题,后续新增环境只需要在全局Nginx里加对应转发规则即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 19:58:08