如何解决同一主机多套带SSL的Docker+Nginx环境访问异常问题?
我在同一主机上运行两套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"; } } }
已尝试的操作
- 把Staging环境的Nginx端口改为随机映射:
ports: - 80 - 443
启动后端口输出显示Staging的Nginx绑定了主机随机端口(如49230->80、49229->443),但访问Staging域名还是跳转到生产应用。
- 给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

