资讯详情

.NET 项目 Docker 化 + Nginx 反向代理,一篇讲透

📅 2026/10/6 20:50:25 | 华诺云谱 👁 阅读
.NET 项目 Docker 化 + Nginx 反向代理,一篇讲透
大家好我是码农刚子。前几天帮朋友排查一个 .NET 项目的部署问题他说「我本地跑得好好的一上服务器就崩」。翻了一下他的 Dockerfile问题挺典型的——单阶段构建、镜像 800MB、端口没映射对、Nginx 配置抄了份三年前的老模板。今天把这一套从头到尾理一遍从 Dockerfile 到 Compose 再到 Nginx 反向代理每一步讲清楚「为什么这么写」而不是只给一段能跑的代码。1. 先搞懂你到底需要几层构建很多人写 .NET 的 Dockerfile上来就FROM mcr.microsoft.com/dotnet/sdk:8.0然后 build run 全在一个镜像里。结果镜像动不动七八百 MB部署拉取慢得要死。正确的做法是多阶段构建——用 SDK 镜像编译用 ASP.NET Core 运行时镜像跑。打个比方你盖房子的时候需要起重机SDK但住进去的时候不需要把起重机也留在家里。核心思路构建阶段用 SDK 镜像大而全运行阶段用运行时镜像小而精最终只保留运行时那一层。来看一份标准的 ASP.NET Core Dockerfile# 第一阶段构建publish FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src # 先拷贝 .csproj利用 Docker 缓存 COPY *.csproj . RUN dotnet restore # 再拷贝全部源码 COPY . . RUN dotnet publish -c Release -o /app/publish # 第二阶段运行runtime FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime WORKDIR /app COPY --frombuild /app/publish . EXPOSE 8080 ENTRYPOINT [dotnet, YourApp.dll]这里有两个细节很多人踩坑。第一个为什么先拷贝 .csproj 再拷贝全部代码因为 Docker 是分层缓存的。如果每次改一行代码就要重新 restore 一次 NuGet 包那构建速度会慢到怀疑人生。先拷贝 .csproj → restore → 再拷贝源码 → publish这样只要 .csproj 没变restore 那一层就能直接用缓存。第二个端口到底是 80 还是 8080这是个经典坑。.NET 8 开始官方 ASP.NET Core 镜像的默认端口从 80 改成了 8080。如果你还照着老教程写 EXPOSE 80容器跑起来之后你会发现怎么都访问不到——不是服务没启动是端口不对。2. Docker Compose把多服务串起来如果你的项目只有一个 Web 应用docker run 一下就够了。但现实场景里通常不止一个服务——Web 应用 数据库 Redis Nginx四五个容器是常态。这时候 Docker Compose 就是标配。来看一份典型的 docker-compose.ymlversion: 3.8 services: webapp: build: context: ./YourApp dockerfile: Dockerfile ports: - 5000:8080 environment: - ASPNETCORE_ENVIRONMENTProduction - ConnectionStrings__DefaultHostdb;Databaseappdb;Usernamepostgres;Passwordsecret depends_on: - db - redis restart: unless-stopped db: image: postgres:16-alpine environment: - POSTGRES_DBappdb - POSTGRES_PASSWORDsecret volumes: - postgres_data:/var/lib/postgresql/data restart: unless-stopped redis: image: redis:7-alpine volumes: - redis_data:/data restart: unless-stopped volumes: postgres_data: redis_data:说几个容易忽略的点。depends_on 只保证启动顺序不保证服务就绪。很多人以为加了 depends_onWeb 就会等数据库准备好再启动。其实不是它只等容器启动不等服务就绪。PostgreSQL 容器起来了但数据库初始化还没完成这时候 Web 去连就会报错。解决办法有两种一是在程序里加重试逻辑推荐用 Polly二是用 healthcheck condition。# 在 db 服务里加健康检查 healthcheck: test: [CMD-SHELL, pg_isready -U postgres] interval: 5s timeout: 5s retries: 5 # 在 webapp 的 depends_on 里指定条件 depends_on: db: condition: service_healthy redis: condition: service_started数据一定要挂 volume。数据库的数据、Redis 的持久化文件、用户上传的文件这些都不能放在容器里。容器一删数据就没了到时候哭都来不及。3. Nginx 反向代理为什么需要它有人会问Kestrel 本身就是 Web 服务器直接暴露端口不行吗为什么还要在前面加一层 Nginx这个问题问得好。Kestrel 确实是个不错的服务器但它的设计目标是处理动态请求。静态文件、负载均衡、SSL 终止、限速、压缩这些事交给 Nginx 效率高得多。可以这么理解——Kestrel 是做菜的厨师Nginx 是前厅的服务员。厨师也能端菜但专业的事交给专业的人来做整体效率更高。来看一份完整的 Nginx 反向代理配置# /etc/nginx/conf.d/your-app.conf server { listen 80; server_name yourdomain.com www.yourdomain.com; # 静态文件直接由 Nginx 处理 location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff2?)$ { root /var/www/your-app/wwwroot; expires 30d; access_log off; } # 动态请求转发给 Kestrel location / { proxy_pass http://webapp:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; 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; proxy_buffering off; proxy_read_timeout 100s; } }这段配置看起来长但核心就两件事静态文件自己处理动态请求转发给后端。重点说几个 header 的作用很多人抄了配置但不知道为什么要加。X-Forwarded-For 和 X-Forwarded-Proto——告诉后端用户的真实 IP 和请求协议http 还是 https。没有这两个头你的 .NET 程序拿到的 IP 永远是 Nginx 容器的 IP而且会以为所有请求都是 http 的。Host——保留原始域名。不然重定向的时候会跳转到内部地址用户就懵了。Upgrade 和 Connection keep-alive——支持 WebSocket 和长连接。如果你的应用用了 SignalR这两行必须有。光配置 Nginx 还不够.NET 这边也要配合一下。在 Program.cs 里加上转发头中间件// Program.cs 里在 builder.Build() 之前加 builder.Services.ConfigureForwardedHeadersOptions(options { options.ForwardedHeaders ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); // 在 app.UseHttpsRedirection() 之前加 app.UseForwardedHeaders();KnownNetworks 和 KnownProxies 清空是因为 Docker 环境里代理 IP 不固定不清除的话转发头会被忽略——这个坑我踩过两次。4. 把 Nginx 也放进 Compose 里既然所有服务都容器化了Nginx 当然也不应该单独装在宿主机上。把 Nginx 加到 docker-compose.yml 里整个部署就完整了services: webapp: build: ./YourApp expose: - 8080 # 注意这里用 expose 而不是 ports # 因为只需要被 Nginx 访问不需要暴露到宿主机 restart: unless-stopped nginx: image: nginx:alpine ports: - 80:80 - 443:443 volumes: - ./nginx/conf.d:/etc/nginx/conf.d - ./nginx/ssl:/etc/nginx/ssl - ./nginx/logs:/var/log/nginx depends_on: - webapp restart: unless-stopped这里有个细节webapp 用 expose不用 ports。expose 只是声明容器内部用哪个端口不会映射到宿主机。所有外部请求都走 Nginxwebapp 不直接对外——安全上更干净。而且在同一个 Compose 网络里服务之间可以直接用服务名通信所以 Nginx 配置里proxy_pass http://webapp:8080直接就能通不需要查 IP。5. 几个踩过的坑最后说几个我自己踩过、也见别人踩过的坑帮大家省点时间。坑一HTTPS 重定向死循环Nginx 做了 SSL 终止和后端之间走的是 HTTP。但 .NET 里开了app.UseHttpsRedirection()它一看请求是 HTTP就重定向到 HTTPS。结果就是用户请求到 NginxHTTPS→ Nginx 转发给后端HTTP→ 后端说你应该用 HTTPS → 重定向 → 又到 Nginx → 无限循环。解决办法确保 UseForwardedHeaders 在 UseHttpsRedirection 之前调用这样 .NET 就能通过 X-Forwarded-Proto 头知道原始请求是 HTTPS 的就不会瞎重定向了。坑二时区不对Docker 容器默认时区是 UTC国内用户会发现时间差了 8 小时。日志时间不对、定时任务不准都是这个原因。在 docker-compose.yml 里给服务加上环境变量就行environment: - TZAsia/Shanghai坑三SignalR 连接不上如果用了 SignalR 或者 Blazor ServerNginx 配置里必须加上 WebSocket 支持的那几行 header而且proxy_buffering要关掉。还有个容易忽略的点proxy_read_timeout默认是 60 秒WebSocket 连接如果 60 秒没有数据传输就会被断开。建议调到 100 秒以上或者在应用层加心跳。坑四文件上传大小限制Nginx 默认请求体大小只有 1MB传个稍微大一点的文件就 413 报错。在 server 块里加一行client_max_body_size 50M;同时 .NET 那边也要调Program.cs里的MaxRequestBodySize两边都要放开才行。6. 收尾把这一套串起来之后部署就变成了一件很简单的事。代码提交了服务器上docker compose up -d --build一下完事。我一直觉得部署这件事的理想状态是「boring」——不需要什么花活稳定、可重复、出问题能快速回滚。一句话总结Dockerfile 做多阶段构建控体积Compose 做多服务编排管依赖Nginx 做反向代理扛流量——三件套各司其职部署就稳了。这篇里提到的所有配置文件都是我在生产环境用过的可以直接拿去改改用。如果有什么我没覆盖到的场景评论区聊。原文链接.NET 项目 Docker 化 Nginx 反向代理一篇讲透 - 码录集
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑