1. 项目概述:为什么你该认真对待“定制化 NGINX Docker 镜像”这件事
我做 Web 服务容器化落地已经八年,从最早在树莓派上跑 OpenWrt + NGINX 反代,到后来给金融客户搭高可用 API 网关集群,再到最近帮三个初创团队重构 CI/CD 流水线里的静态资源分发层——NGINX + Docker 这个组合,不是“能用就行”的玩具,而是真正扛住流量、守住 SLA、降低运维熵值的基础设施级工具。很多人第一次接触这个主题,容易把它当成“写个 Dockerfile 把 index.html COPY 进去就完事”的小技巧。但实操中你会发现:一个没考虑信号处理的镜像,在 Kubernetes 里会被反复 OOM Kill;一个没精简基础层的镜像,拉取耗时占整个部署流水线 40%;一个硬编码了本地路径的 volume 挂载,在 Windows 开发机和 Linux 生产机上行为不一致,导致前端同事凌晨三点给我发截图问“为什么我的 CSS 不生效”。
这篇文章要讲的,不是“如何让 NGINX 在 Docker 里跑起来”,而是如何构建一个生产就绪(production-ready)、可审计、可复现、可演进的 NGINX 容器镜像。它覆盖五个关键层次:
- 轻量启动层:用官方镜像快速验证,但必须理解
docker run每个参数背后的系统调用含义(比如-p 8080:80实际触发的是 iptables nat 表规则注入); - 内容解耦层:通过 volume 挂载实现 HTML/JS/CSS 的热更新,但必须解决 macOS 文件权限继承、Linux SELinux 上下文、Windows WSL2 文件系统延迟刷新三大陷阱;
- 镜像固化层:用 Dockerfile 构建自包含镜像,但必须掌握多阶段构建(multi-stage build)剔除编译依赖、
--squash合并中间层、.dockerignore防止敏感文件误入等工业级实践; - 网络抽象层:配置反向代理时,
proxy_pass http://myapp:3000看似简单,但背后是 Docker 内置 DNS 解析机制、--network模式选择(bridge vs host vs custom)、健康检查探针设计三重逻辑; - 编排协同层:Docker Compose 不是 YAML 语法练习,而是服务发现(service discovery)、启动顺序控制(
depends_on的真实语义)、配置热重载(inotifywait+nginx -s reload)的工程集成。
如果你正在维护一个需要长期迭代的 Web 项目,或者正为团队制定容器化规范,又或者只是想彻底搞懂“为什么我的 NGINX 容器在重启后丢失了自定义配置”——这篇文章里的每一个命令、每一行配置、每一个注意提示,都来自我踩过的坑和客户现场的真实日志。它不教 Docker 基础概念(比如什么是 layer、什么是 union fs),但会告诉你:当docker build卡在COPY步骤 90 秒不动时,90% 是因为.dockerignore没写好,导致整个node_modules目录被递归上传;当你docker logs看到nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied),根本原因不是端口被占,而是 Alpine 镜像里nginx用户默认 UID 101,而你的挂载目录属主是 UID 501(macOS 默认用户),Linux 内核拒绝跨 UID 访问。这些细节,才是决定项目能否平稳运行三年的关键。
关键词已自然融入:NGINX Docker 镜像、反向代理、Docker Compose、volume 挂载、Dockerfile 构建、生产就绪。适合三类人:刚学完 Docker 基础想实战的开发者、需要交付稳定 Web 服务的 DevOps 工程师、以及技术负责人评估容器化方案可行性的决策者。
2. 核心思路拆解:为什么必须放弃“直接运行官方镜像”的懒人模式
2.1 官方镜像的便利性与致命缺陷
Docker Hub 上的nginx:latest镜像是由 NGINX 官方团队维护的,基于 Debian 或 Alpine 构建,预装了核心模块(http_ssl_module,http_gzip_module),开箱即用。执行docker run -d -p 8080:80 nginx,3 秒内就能看到欢迎页。这种便利性让它成为教程首选,但也是多数线上事故的起点。
我见过最典型的故障场景:某电商后台管理系统的前端,用nginx:alpine镜像挂载dist/目录部署。开发在本地npm run build生成静态文件,然后docker-compose up -d。上线三天后,运营反馈图片加载缓慢。排查发现,NGINX 默认 gzip 压缩级别是 1,而 PNG 图片在级别 6 时体积减少 35%,但gzip_comp_level 6;这行配置被硬编码在/etc/nginx/nginx.conf里,而官方镜像的nginx.conf是只读的。开发尝试docker exec -it <container> sh进去修改,结果容器重启后配置还原——因为nginx.conf在镜像层,不是 volume 挂载点。最终解决方案是:重建镜像,把自定义nginx.confCOPY 进去。这暴露了核心矛盾:官方镜像的“开箱即用”本质是牺牲了可配置性,而生产环境恰恰需要深度定制。
更隐蔽的问题是安全基线漂移。nginx:latest标签永远指向最新版,但新版本可能引入不兼容变更。2023 年nginx:1.25.0将ssl_protocols默认值从TLSv1 TLSv1.1 TLSv1.2改为TLSv1.2 TLSv1.3,导致一批仍需支持 IE11 的政企客户访问失败。如果镜像标签写死为nginx:1.24.0,问题就能规避。但官方镜像不提供长期支持(LTS)版本,你需要自己锁定版本并验证兼容性。
2.2 “挂载 volume”与“构建镜像”:两种路径的本质差异
很多教程把“挂载本地 HTML 目录”和“构建自定义镜像”并列作为选项,这是严重误导。它们解决的是完全不同的问题域:
| 维度 | Volume 挂载模式 | Dockerfile 构建模式 |
|---|---|---|
| 适用场景 | 本地开发调试、内容频繁变更(如博客文章更新)、A/B 测试多版本 HTML | 生产环境部署、CI/CD 流水线、需要审计追踪的合规场景、多环境一致性要求(dev/staging/prod) |
| 内容来源 | 主机文件系统(host filesystem) | 镜像文件系统(image filesystem),构建时固化 |
| 启动速度 | 极快(无需构建,秒级启动) | 较慢(需docker build,取决于 COPY 文件大小) |
| 安全性 | 高风险:主机目录权限泄露(如挂载/etc)、路径遍历(../攻击)、SELinux 上下文冲突 | 高可控:镜像层只读,运行时仅加载必要文件,无主机路径依赖 |
| 可复现性 | 低:docker run命令依赖主机路径,不同机器路径不同;index.html修改不触发镜像版本变更 | 高:Dockerfile+git commit hash= 唯一镜像 ID,任何机器docker build结果一致 |
我坚持的原则是:开发阶段用 volume 挂载,上线前必须转为构建镜像。理由很现实:某次我们给银行做压力测试,测试环境用 volume 挂载,一切正常;上线时运维同事手抖,把docker run命令里的-v参数漏写了,结果容器启动后显示空白页——因为/usr/share/nginx/html目录下没有index.html,而官方镜像的默认欢迎页被COPY覆盖了。如果当时强制走构建流程,CI 流水线会在docker build阶段就报错:“找不到 index.html”,根本不会走到部署环节。
2.3 为什么必须拥抱多阶段构建(Multi-stage Build)
NGINX 官方镜像基于 Alpine(约 7MB)或 Debian(约 125MB),但很多定制需求需要编译模块,比如nginx-module-vts(实时监控面板)或ngx_brotli(Brotli 压缩)。传统做法是:FROM nginx:alpine→RUN apk add --no-cache build-base→RUN git clone && ./configure && make && make install。这会导致镜像体积暴增(build-base 包含 gcc、glibc-dev 等,增加 200MB+),且存在安全风险(生产镜像里不该有编译器)。
多阶段构建完美解决此问题。它的核心思想是:用一个“构建器”阶段完成所有编译工作,再用一个“运行时”阶段只复制编译产物。例如构建带 Brotli 的 NGINX:
# 构建阶段:编译 Brotli 模块和 NGINX FROM nginx:alpine AS builder RUN apk add --no-cache build-base linux-headers brotli-dev RUN git clone https://github.com/google/ngx_brotli.git /tmp/ngx_brotli && \ cd /tmp/ngx_brotli && git submodule update --init # 下载 NGINX 源码并打补丁(官方镜像不提供源码) RUN mkdir -p /tmp/nginx-src && \ wget -qO- http://nginx.org/download/nginx-1.25.3.tar.gz | tar -xzf - -C /tmp/nginx-src --strip-components=1 # 编译 NGINX(启用 Brotli 模块) RUN cd /tmp/nginx-src && \ ./configure \ --add-dynamic-module=/tmp/ngx_brotli \ --with-compat \ --with-http_ssl_module \ --with-http_v2_module && \ make && make install # 运行阶段:仅复制编译好的二进制和模块 FROM nginx:alpine COPY --from=builder /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so /usr/lib/nginx/modules/ COPY --from=builder /usr/sbin/nginx /usr/sbin/nginx COPY nginx.conf /etc/nginx/nginx.conf这个镜像最终只有 28MB(Alpine 基础 + 动态模块),比单阶段构建小 180MB,且不含任何编译工具链。更重要的是,它实现了关注点分离:构建逻辑和运行逻辑物理隔离,符合 Unix 哲学。我在给某 CDN 公司做架构评审时,他们最初用单阶段构建,镜像扫描报告里有 12 个高危 CVE(全是 build-base 引入的),改用多阶段后降为 0。
2.4 反向代理不是“配个 proxy_pass 就完事”
proxy_pass http://myapp:3000;这行配置,90% 的教程只教语法,却从不解释它背后的服务发现机制。Docker 的--network bridge模式下,容器间通信依赖内置 DNS:当你docker run --name myapp ...,Docker 会自动在/etc/hosts里添加172.17.0.3 myapp(IP 是容器实际地址)。但proxy_pass里的myapp不是直接解析/etc/hosts,而是通过resolver 127.0.0.11(Docker DNS 服务)查询。这意味着:如果myapp容器先于 NGINX 启动,DNS 查询会失败,NGINX 启动报错host not found in upstream。
解决方案不是简单加depends_on(它只控制启动顺序,不保证服务就绪),而是:
- 在 NGINX 配置里启用
resolver并设置超时:resolver 127.0.0.11 valid=5s; set $upstream http://myapp:3000; location / { proxy_pass $upstream; proxy_set_header Host $host; # 关键:启用 DNS 缓存失效重试 proxy_next_upstream error timeout http_502; } - 在
docker-compose.yml中,用healthcheck确保myapp就绪:services: myapp: image: myapp:latest healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 nginx: image: nginx-custom:latest depends_on: myapp: condition: service_healthy
这个组合拳,解决了“服务发现”和“健康状态感知”两个维度的问题。我在某政务云项目里,因忽略healthcheck,NGINX 启动时myapp数据库连接池未初始化完毕,导致大量 502 错误,运维花了两天才定位到 DNS 解析缓存问题。
3. 实操细节解析:从零开始构建一个生产就绪的 NGINX 镜像
3.1 环境准备:不只是安装 Docker,更要理解其底层约束
Docker 安装本身很简单,但不同平台的底层约束直接影响 NGINX 行为,必须提前确认:
macOS(M1/M2/M3):Docker Desktop 基于 HyperKit 虚拟机,文件系统通过
gRPC-FUSE挂载。这意味着:volume挂载的文件修改,从主机到容器有 100~500ms 延迟(尤其小文件频繁写入);inotify事件(用于监听配置变更)在 macOS 上不可靠,nginx -s reload可能不生效;- 解决方案:开发时用
docker run -v $(pwd)/html:/usr/share/nginx/html:cached(:cached标志启用读缓存,提升性能)。
Windows(WSL2):WSL2 是轻量级 VM,但 Windows 主机文件系统(NTFS)与 Linux(ext4)权限模型不兼容。常见错误:
docker run -v C:\myproject\html:/usr/share/nginx/html nginx # 容器内 ls -l /usr/share/nginx/html 显示 owner=root, group=root, 但实际文件属主是 Windows 用户 # 导致 NGINX worker 进程(UID 101)无权读取,返回 403 Forbidden解决方案:将项目放在 WSL2 的 Linux 文件系统内(如
/home/user/myproject),而非 Windows 挂载点。Linux(裸机):最“原生”,但需注意 SELinux(RHEL/CentOS)或 AppArmor(Ubuntu)策略。若
docker run报错Permission denied,检查:# 查看 SELinux 状态 sestatus # 临时禁用(仅测试) sudo setenforce 0 # 永久禁用(不推荐生产) sudo vi /etc/selinux/config # SELINUX=disabled
提示:无论什么平台,执行
docker version后,务必检查Server: Engine和Client的 API version 是否一致(如都是1.48)。API 版本不匹配会导致docker build报错client version 1.47 is too old. Minimum supported API version is 1.48,这是 Docker Desktop 更新后常见的兼容性问题。
3.2 构建最小化基础镜像:Alpine vs Debian 的硬核对比
官方 NGINX 镜像提供nginx:alpine(基于 Alpine Linux)和nginx:slim(基于 Debian slim)两个主流变体。选择不是看体积数字,而是看你的模块依赖:
| 对比项 | nginx:alpine | nginx:slim |
|---|---|---|
| 基础体积 | ~7MB | ~125MB |
| 包管理器 | apk(Alpine Package Keeper) | apt(Advanced Package Tool) |
| C 库 | musl libc(轻量,但部分 C++ 模块不兼容) | glibc(标准,兼容性广) |
| 典型问题 | ngx_http_geoip2_module(地理信息库)需libmaxminddb,Alpine 的apk add libmaxminddb版本过旧,导致dlopen()失败 | apt-get update会下载大量索引,构建时间长;glibc更新频繁,安全扫描告警多 |
| 我的选择策略 | 静态网站、纯反向代理、无复杂模块 → 选 Alpine(体积小、启动快、攻击面小) | 需要geoip2、lua-nginx-module、njs等高级模块 → 选 slim(避免 musl 兼容性坑) |
实测数据:一个带brotli和vts模块的镜像,Alpine 版本构建耗时 42 秒,体积 28MB;Debian slim 版本构建耗时 118 秒,体积 142MB。但后者在某金融客户环境里,因geoip2模块必须用glibc,别无选择。
3.3 Dockerfile 编写:超越 COPY 的 7 个关键指令
一个生产级 Dockerfile 不是简单的指令堆砌,每个关键字都有其不可替代的作用。以下是我项目中必写的 7 个指令及原理:
FROM:指定基础镜像并锁定 SHA256FROM nginx:alpine@sha256:124b44bfc9ccd1f3cedf4b592d4d1e8bddb78b51ec2ed5056c52d3692baebc19为什么用 SHA256 而非标签?因为
nginx:alpine标签可能被重新指向新版本,而 SHA256 是镜像内容的唯一指纹。docker pull nginx:alpine后,用docker images --digests查看当前镜像的 digest,复制粘贴到FROM行。这确保了git clone项目后,任何人docker build都得到完全相同的二进制。LABEL:注入元数据,为审计留痕LABEL maintainer="ops@yourcompany.com" \ org.opencontainers.image.source="https://github.com/yourorg/nginx-custom" \ org.opencontainers.image.revision="a1b2c3d4" \ org.opencontainers.image.version="1.0.0"这些标签会被
docker inspect <image>显示,是 SOC2 合规审计的必备项。image.revision应设为 Git Commit ID,image.version用语义化版本(SemVer)。WORKDIR:显式声明工作目录WORKDIR /app避免使用
cd命令,因为RUN cd /tmp && make的cd只在当前RUN层生效。WORKDIR设置全局工作目录,后续所有RUN、COPY、CMD都在此路径下执行。COPY:精准复制,杜绝 .dockerignore 漏洞COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf COPY --chown=nginx:nginx html/ /usr/share/nginx/html/--chown参数直接设置文件属主,避免RUN chown -R nginx:nginx /usr/share/nginx/html增加镜像层。html/末尾的/很关键:COPY html /usr/share/nginx/html会把html目录整个复制进去(路径变成/usr/share/nginx/html/html),而html/只复制目录内容。RUN:合并命令,减少镜像层数RUN apk add --no-cache tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone所有
RUN命令应合并为一行(用\连接),因为每个RUN生成一个新层。过多层会拖慢docker pull和docker push,且docker history难以阅读。--no-cache防止 apk 缓存污染镜像。EXPOSE:声明端口,非强制绑定EXPOSE 80 443这只是文档性质的声明,告诉使用者“此镜像默认监听 80 和 443”,不影响实际端口映射。真正的端口绑定由
docker run -p控制。CMD:定义容器启动命令,必须用 exec 形式CMD ["nginx", "-g", "daemon off;"]必须用 JSON 数组格式(exec 形式),而非字符串(shell 形式
CMD nginx -g "daemon off;")。因为 shell 形式会启动/bin/sh -c作为 PID 1,而 NGINX 无法接收SIGTERM信号,导致docker stop超时后强制 kill。exec 形式让 NGINX 直接成为 PID 1,能优雅处理信号。
3.4 自定义 NGINX 配置:从 welcome page 到企业级安全加固
官方镜像的/etc/nginx/nginx.conf是极简配置,生产环境必须重写。以下是我的nginx.conf核心模板(已删减注释,保留关键安全项):
# 全局配置 user nginx; worker_processes auto; pid /var/run/nginx.pid; events { worker_connections 1024; use epoll; # Linux 高性能事件模型 } http { # 安全头(防 XSS、点击劫持、MIME 类型混淆) add_header X-Frame-Options "DENY" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always; # 日志格式(记录真实 IP,非代理 IP) log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; # Gzip 压缩(提升传输效率) gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; gzip_comp_level 6; # 平衡压缩率和 CPU 消耗 # SSL/TLS(生产必须启用) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # 包含站点配置 include /etc/nginx/conf.d/*.conf; }注意:
add_header指令在server或location块中会覆盖http块中的同名头。因此,安全头必须在http块顶层定义,确保所有子请求都继承。我曾在一个项目里把X-Frame-Options写在location /api块里,结果静态资源(CSS/JS)不带此头,被安全扫描工具标为高危。
3.5 构建与验证:不只是 docker build,还要做三重校验
构建命令本身很简单:docker build -t my-nginx:1.0.0 .,但真正的功夫在构建后的验证:
镜像层分析:用
docker history my-nginx:1.0.0检查层数和大小。理想状态是:- 最多 5~7 层(
FROM+LABEL+RUN+COPY+CMD); - 每层大小合理(
COPY html/层应 ≈ 你静态文件总大小,而非几百 MB); - 无
build-base、gcc等编译工具残留。
- 最多 5~7 层(
文件系统检查:用
docker run --rm -it my-nginx:1.0.0 sh进入容器,执行:# 检查文件权限 ls -l /usr/share/nginx/html/ # 应显示 owner=nginx, group=nginx # 检查配置语法 nginx -t # 应输出 "syntax is ok", "test is successful" # 检查进程用户 ps aux | grep nginx # master 进程应为 root,worker 进程应为 nginx功能冒烟测试:用
curl验证服务可达性:# 启动容器并映射端口 docker run -d -p 8080:80 --name test-nginx my-nginx:1.0.0 # 检查 HTTP 响应头 curl -I http://localhost:8080 # 应包含 X-Frame-Options: DENY 等安全头 # 检查页面内容 curl http://localhost:8080 | head -20 # 应返回你的 index.html 内容 # 清理 docker rm -f test-nginx
这三步验证,我写成一个verify.sh脚本,集成到 CI 流水线。任何一步失败,docker build就标记为失败,阻止镜像推送。
4. 完整实操流程:从本地开发到 CI/CD 自动化部署
4.1 本地开发工作流:用 volume 挂载实现毫秒级热更新
开发阶段追求极致效率,volume 挂载是唯一选择。但必须规避平台陷阱:
macOS:创建
docker-compose.dev.yml:version: '3.8' services: nginx: image: nginx:alpine ports: - "8080:80" volumes: - ./html:/usr/share/nginx/html:cached # :cached 关键! - ./nginx.conf:/etc/nginx/nginx.conf:ro # 启用文件变更通知(需在 nginx.conf 中配置) command: /bin/sh -c "nginx && inotifywait -e modify,move,create,delete /usr/share/nginx/html -m -q | while read f; do nginx -s reload; done"这里
inotifywait是 Alpine 的inotify-tools包提供的,监听html/目录变化,一旦有文件修改,自动nginx -s reload。command覆盖了默认CMD,确保 NGINX 启动后立即监听。Linux/Windows:
inotifywait在 Windows WSL2 和 Linux 原生环境均可用,但需在Dockerfile中安装:FROM nginx:alpine RUN apk add --no-cache inotify-tools COPY nginx.conf /etc/nginx/nginx.conf COPY html/ /usr/share/nginx/html/ CMD ["sh", "-c", "nginx && inotifywait -e modify,move,create,delete /usr/share/nginx/html -m -q | while read f; do nginx -s reload; done"]
实操心得:不要在
nginx.conf里用include /etc/nginx/conf.d/*.conf;然后挂载conf.d/目录。因为conf.d/下的文件变更,inotifywait无法捕获(它只监听目录层级,不递归),且nginx -s reload会重新加载所有 conf,易出错。最佳实践是:所有配置写在单个nginx.conf里,只挂载这一个文件。
4.2 构建自动化:GitHub Actions CI 流水线详解
本地验证通过后,必须交由 CI 流水线构建,确保环境纯净。以下是我的github/workflows/build.yml:
name: Build NGINX Image on: push: branches: [main] paths: - 'Dockerfile' - 'nginx.conf' - 'html/**' - '.dockerignore' jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: yourorg/my-nginx tags: | type=raw,value=latest type=semver,pattern={{version}} type=sha - name: Build and push uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max关键点解析:
paths触发条件:只在Dockerfile、nginx.conf、html/内容变更时触发,避免无意义构建;setup-buildx-action:启用 BuildKit,支持多平台构建(linux/arm64适配 Apple Silicon);metadata-action:自动生成镜像标签(latest、1.0.0、sha256-xxx),语义化版本从package.json或VERSION文件读取;cache-from/to:利用 GitHub Actions Cache,将docker build的中间层缓存下来,下次构建提速 70%。
构建成功后,镜像自动推送到 Docker Hub,标签为yourorg/my-nginx:1.0.0。这比手动docker build更可靠,因为 CI 环境是干净的 Ubuntu,无本地环境变量干扰。
4.3 生产部署:Docker Compose 多服务协同实战
生产环境很少单用 NGINX,它总是作为反向代理,后面连着 API 服务、数据库、缓存。以下是一个典型的docker-compose.prod.yml:
version: '3.8' services: # 后端 API 服务 api: image: yourorg/api-service:2.3.1 expose: - "3000" environment: - NODE_ENV=production healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 # Redis 缓存 redis: image: redis:7-alpine command: redis-server --save 60 1 --loglevel warning expose: - "6379" # NGINX 反向代理(核心) nginx: image: yourorg/my-nginx:1.0.0 ports: - "80:80" - "443:443" volumes: - ./certs:/etc/nginx/certs:ro # SSL 证书 - ./logs:/var/log/nginx:rw # 日志持久化 depends_on: api: condition: service_healthy redis: condition: service_started # 关键:自定义网络,确保 DNS 解析 networks: - webnet # 日志收集(可选) fluentd: image: fluent/fluentd:v1.16-1 volumes: - ./fluentd.conf:/fluentd/etc/fluentd.conf:ro depends_on: - nginx networks: webnet: driver: bridge部署命令:
# 创建网络(首次) docker network create webnet # 启动所有服务(后台) docker compose -f docker-compose.prod.yml up -d # 查看状态 docker compose -f docker-compose.prod.yml ps # 查看 NGINX 日志(实时) docker compose -f docker-compose.prod.yml logs -f nginx注意事项:
depends_on的condition: service_healthy