这是《阿里云服务器快速部署服务》系列的第二篇。上一篇我们把ECS的初始化、安全组放行、SSH远程登录这些前置工作收尾了,这一篇直接上手Docker部署Nginx。我尽量把整个过程讲得跟实操现场一样——命令怎么敲、目录怎么建、配置里每段是干什么的、踩了哪些坑,全部摊开说。
为什么第一篇和第二篇之间隔了这么久?因为我后来发现,光讲Nginx部署其实撑不起一篇完整的文章,真正复杂的是后面那一堆配套:怎么规划宿主机目录、怎么挂载配置、怎么让Nginx和MySQL、Java应用联动、怎么把HTTP升级到HTTPS。这些不交代清楚,你照着网上的命令敲完,容器是能跑,但一遇到真实业务就懵。所以这篇干脆把Docker部署Nginx从“能跑”到“能用、能维护”的全过程都写了一遍。
适合谁看?刚买了一台阿里云服务器、想用Docker跑Web服务的同学,以及已经在容器里跑Nginx但被配置和权限问题弄到头秃的人。有Docker基础最好,没有也行,我默认你从零开始,每一步命令都可以直接复制。
1. 为什么我要用Docker部署Nginx
1.1 容器化对比传统安装,优势到底在哪
很多人的第一反应是:服务器上直接apt install nginx不就行了,为什么要绕一圈用Docker?这个想法没错,传统方式在单机场景下确实简单。但我这几年的体会是,一旦你需要在同一台服务器上跑多个服务、多个版本、甚至帮客户部署多套环境,传统安装的痛点会很快暴露出来。
首先是依赖污染。apt装Nginx会把一堆配置文件散落在/etc/nginx、/var/log/nginx、/usr/share/nginx/html这些地方,卸载的时候还不一定清得干净。其次是版本锁定。Ubuntu软件源里的Nginx版本通常比较旧,你想用上新特性,就得手动加PPA或者编译安装,维护成本直线上升。最头疼的是迁移和回滚:换一台服务器,所有配置、编译参数、依赖全要重新来一遍。
Docker把这些麻烦全部打包解决了。Nginx官方镜像把Nginx和它运行所需的最小系统环境封装在一起,宿主机只管跑容器,不关心里面装了什么东西。配置目录、日志目录、站点文件目录全部通过挂载方式暴露到宿主机,我只需要维护/data/nginx这一个目录,就等同于管理了整个Nginx实例。想升级?拉一个新镜像,换个容器跑起来;出问题?把旧的容器启动回来,十秒钟完成回滚。这种体验用过一次就很难回去。
1.2 选型背后的几个具体考量
镜像选择上,我推荐nginx:1.26-alpine而不是nginx:latest或者nginx:1.26。Alpine版本体积小,镜像大概只有40MB左右,比Debian底座的版本小一半还多,拉取快、占用磁盘少。更重要的是Alpine默认不携带bash等重型工具,攻击面相对更小。至于为什么不用latest,原因很简单:latest标签会跟随官方更新,你没法控制线上跑的到底是哪个版本,某天拉取后行为可能发生变化。固定到具体的小版本号,配合镜像Digest校验,才是生产环境该有的态度。
目录规划上,我把Nginx的所有持久化数据单独拎出来,放在/data/nginx下面,下面分成conf、conf.d、html、logs、cert五个子目录。这样做的逻辑是:容器本身是无状态的,删掉重建完全不影响数据,所有需要保留的东西都落在宿主机目录上。后面不管是迁移还是备份,打包这一个目录就够了。
启动参数上,我会加上--restart always。服务器重启之后,容器能自动拉起来,不用每次手动去docker start。对于生产环境来说,这个参数几乎等于保险绳,值得养成习惯。
2. 部署前准备:从干净系统到Docker就绪
2.1 服务器配置与安全组放行
先说硬件底线。跑Nginx本身非常轻量,但考虑到后面还要部署MySQL、Java应用这些配套服务,建议ECS至少选择2核2G的规格。操作系统我用的Ubuntu 22.04 LTS,这个版本在阿里云上很常见,软件源和内核都比较新,Docker支持也最顺利。
安全组是阿里云服务器特有的一个环节,很多人第一次在这上面卡住:服务器上服务明明起来了,外部就是访问不了,十有八九是安全组没放行。Nginx要对外提供HTTP和HTTPS服务,需要在安全组的入方向规则里放行80和443端口;SSH管理端口22一般默认已经放行。这里特别提醒一句:像3306(MySQL)、6379(Redis)这类数据库端口,千万不要在安全组里对0.0.0.0/0全放行,等后面部署数据库的时候,我会建议你只让它们在Docker内网里通信。
2.2 Docker安装完整命令
阿里云ECS默认是没有装Docker的,需要手动安装。这里用官方源安装,一套命令走完:
# 更新系统包 sudo apt update && sudo apt upgrade -y # 安装Docker依赖 sudo apt install -y ca-certificates curl gnupg lsb-release # 添加Docker官方GPG密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 添加Docker软件源 echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装Docker引擎和插件 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin装完验证一下:
sudo docker version sudo docker compose version能看到Client和Server版本信息就说明安装成功了。Server端有输出很关键,因为很多问题是Docker服务没启动导致的。如果Server信息没出来,执行sudo systemctl enable --now docker启动服务,并设置开机自启。
另外补充一句,如果你手边有Windows或macOS电脑,本地调试阶段完全可以用Docker Desktop把环境先跑通,目录结构跟服务器保持一致,然后再把整套目录原封不动传到服务器上。这样能避免很多“本地可以、服务器不行”的尴尬。
2.3 调整Docker守护进程参数
Docker装好后,我习惯先改一下守护进程配置再开始用。新建或编辑/etc/docker/daemon.json:
{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" }, "storage-driver": "overlay2" }这里最实用的是日志限制。Docker默认会无限收集容器日志,一个Nginx容器如果访问量大,几天就能积累几个G的日志文件,把磁盘挤爆。设置max-size: 50m和max-file: 3后,单个容器日志最多占150MB,超过就自动轮转。这个参数我在生产环境吃过亏,晚设置不如早设置。
改完重启Docker:
sudo systemctl restart docker sudo docker run hello-world看到 “Hello from Docker!” 说明环境就绪。
3. 拉起第一个Nginx容器
3.1 拉取镜像与首次验证
先把镜像拉下来:
sudo docker pull nginx:1.26-alpine第一次先别挂载任何目录,直接裸跑一个测试容器,目的是验证端口和Docker网络:
sudo docker run --name nginx-test -p 80:80 -d nginx:1.26-alpine然后访问http://服务器公网IP,能看到Nginx默认的欢迎页,说明80端口通了,安全组也没问题。这一步能帮你把“服务器问题”和“Docker问题”分开,后面排查会轻松很多。
验证完先不要删这个容器,我们还要它帮忙把默认配置文件拷出来。
3.2 复制默认配置并启动正式容器
我不建议从零手写一份nginx.conf,因为Nginx默认配置里有很多隐性细节,比如mime.types、fastcgi相关参数,漏掉一个都可能让你后面踩坑。最稳妥的做法是让官方镜像生成好默认配置,我们复制出来再改。
# 创建宿主机目录结构 sudo mkdir -p /data/nginx/{conf,conf.d,html,logs,cert} # 从测试容器中复制默认配置 sudo docker cp nginx-test:/etc/nginx/nginx.conf /data/nginx/conf/nginx.conf sudo docker cp nginx-test:/etc/nginx/conf.d/default.conf /data/nginx/conf.d/default.conf # 删除测试容器 sudo docker rm -f nginx-test测试容器删掉后,启动正式容器。这是全篇最关键的一条命令:
sudo docker run -d \ --name nginx \ --restart always \ -p 80:80 \ -p 443:443 \ -v /data/nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /data/nginx/conf.d:/etc/nginx/conf.d:ro \ -v /data/nginx/html:/usr/share/nginx/html:ro \ -v /data/nginx/logs:/var/log/nginx \ -v /data/nginx/cert:/etc/nginx/cert:ro \ nginx:1.26-alpine逐个解释一下挂载参数的含义:
/data/nginx/conf/nginx.conf挂到容器内的主配置文件路径,:ro表示只读,防止容器内误改。/data/nginx/conf.d是站点配置目录,Nginx会自动include这个目录下所有.conf文件,我们后续的虚拟主机配置都放这里。/data/nginx/html挂到Nginx默认的站点根目录。注意我用的是:ro,静态文件只在宿主机上管理,容器内不需要写。/data/nginx/logs挂到/var/log/nginx,这样Nginx的access.log和error.log直接写到宿主机,方便用logrotate统一处理。/data/nginx/cert挂到/etc/nginx/cert,专门放SSL证书。
启动后做三件事验证:
# 看容器运行状态 sudo docker ps # 看启动日志有没有报错 sudo docker logs nginx # 进容器里检查配置语法 sudo docker exec nginx nginx -t三件事都通过的话,访问服务器IP应该还是能看到Nginx欢迎页,因为站点目录还是空的,走的是默认配置。
3.3 理解Nginx容器的工作方式
跟传统Nginx最大的不同是:你现在不能用systemctl reload nginx了,所有操作都要通过docker exec来完成。常用的几条命令我先列出来:
# 修改配置后,先检查语法再重载 sudo docker exec nginx nginx -t sudo docker exec nginx nginx -s reload # 查看实时访问日志 sudo tail -f /data/nginx/logs/access.log # 查看错误日志 sudo tail -f /data/nginx/logs/error.log # 进入容器内部调试 sudo docker exec -it nginx sh把这些命令记熟,后面的操作会顺手很多。尤其要养成nginx -t先检查再reload的习惯,这能帮你省掉大量“改完配置站挂了”的麻烦。
4. Nginx配置拆解:从全局到站点
4.1 主配置文件nginx.conf的结构
复制出来的默认nginx.conf内容比较长,核心结构其实是这样的:
user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; client_max_body_size 10m; gzip on; include /etc/nginx/conf.d/*.conf; }几个关键点说一下。worker_processes auto让Nginx根据CPU核心数自动开启工作进程,这是当前版本推荐的配置,不需要手动指定数字。client_max_body_size 10m这行是给后面的业务留的口子,默认Nginx限制上传文件大小只有1MB,如果你的应用涉及文件上传,这里不改迟早会踩坑。gzip on开启压缩,对文本类资源(HTML、CSS、JS)的传输体积能减少70%左右,性价比极高。
最重要的是最后一行的include /etc/nginx/conf.d/*.conf,它意味着所有站点配置都放在conf.d目录里,主配置文件在正常情况下不需要频繁修改。
4.2 server块与location匹配优先级
站点配置写在conf.d目录下的配置文件里,推荐一个站点一个文件,命名用域名。比如example.com.conf。一个最基础的server块长这样:
server { listen 80; server_name example.com; root /usr/share/nginx/html; index index.html; }server_name是虚拟主机的关键,Nginx会根据请求的Host头去匹配这个字段。同一台服务器上可以挂多个不同域名的站点,每个域名一个server块即可。这里顺便把Nginx的location匹配机制讲透,因为这是被问得最多、也最容易记混的点。
Nginx在匹配location时,有一套明确的优先级顺序:
=精确匹配:location = /favicon.ico,完全相等才命中,命中后不再继续匹配。^~前缀匹配:location ^~ /static/,如果命中,就不再检查后面的正则。- 正则匹配
~(区分大小写)和~*(不区分大小写):按配置文件中的书写顺序逐个匹配。 - 普通前缀匹配:
location /,取最长匹配前缀。
实际配置中,最常见的组合是:
location = /favicon.ico { log_not_found off; } location ^~ /static/ { alias /usr/share/nginx/html/static/; expires 7d; } location /api/ { proxy_pass http://backend:8080; }/favicon.ico用精确匹配,避免每次页面请求都打一条404日志;/static/用前缀加^~,让静态资源直接走Nginx文件系统,不落到后端应用;/api/则交给反向代理转发。这套结构是我在多个项目里验证过的通用范式,你可以直接抄。
4.3 静态站点与反向代理配置实例
先看静态站点配置。假设你的前端打包产物放在/data/nginx/html/myapp,访问域名根路径时指向这个目录:
server { listen 80; server_name example.com; location / { root /usr/share/nginx/html/myapp; index index.html; try_files $uri $uri/ /index.html; } location /files/ { alias /data/files/; autoindex off; } }这里有两个细节值得注意。第一,try_files $uri $uri/ /index.html是配合前端路由使用的,比如Vue或React的history路由模式,刷新子路由页面时不至于404。第二,location /files/用了alias而不是root,这俩的区别是:root会把location的路径拼接到根目录后面,alias则是直接用指定目录替换。很多人在这里写错,导致文件路径多了一层目录,本地怎么测都正常,上服务器就是404。
再来看反向代理。这是Nginx在服务端场景下最核心的能力,通常用于把请求转发给后端的Java、Node或Python应用:
server { listen 80; server_name api.example.com; location / { proxy_pass http://backend:8080; 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_connect_timeout 10s; proxy_read_timeout 60s; }proxy_set_header这几行不是可有可无的装饰,后端应用经常需要靠它们获取真实的客户端IP和协议。尤其是X-Forwarded-For,如果缺失,后端的日志里所有请求的IP都是Nginx容器的内网地址,排查问题会非常难受。
至于proxy_pass里的http://backend:8080,背后其实涉及Docker容器间通信的问题。后面单独讲。
5. HTTPS证书接入与强制跳转
5.1 证书来源与上传
现在主流浏览器对HTTP站点的警告越来越严格,HTTPS已经是标配而不是可选项。证书来源一般两条路:一是阿里云控制台申请免费SSL证书,通常有效期一年,申请后下载Nginx格式的证书包;二是用Let‘s Encrypt这类自动化签发工具,配合acme.sh脚本实现自动续期。
不管哪条路,最终你要拿到两个文件:证书文件(.pem或.crt)和私钥文件(.key)。把这两个文件上传到服务器的/data/nginx/cert目录:
sudo mkdir -p /data/nginx/cert # 上传证书文件到此目录,建议命名为 example.com.pem 和 example.com.key这里有个容器特有的权限坑要说清楚。Nginx容器内的worker进程是以nginx用户跑的,uid是101。挂载进来的证书文件,如果宿主机上权限是默认的600且属主是root,容器里的nginx用户是读不了的,结果就是启动报错cannot load certificate。稳妥的做法是:
sudo chown 101:101 /data/nginx/cert/example.com.pem /data/nginx/cert/example.com.key sudo chmod 644 /data/nginx/cert/example.com.pem sudo chmod 600 /data/nginx/cert/example.com.key这不是玄学,是文件权限在容器和宿主机之间共享的必然结果,遇到启动报错先检查这里。
5.2 SSL站点配置示例与强制跳转
证书放好后的站点配置:
server { listen 443 ssl; http2 on; server_name example.com; ssl_certificate /etc/nginx/cert/example.com.pem; ssl_certificate_key /etc/nginx/cert/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; root /usr/share/nginx/html/myapp; index index.html; location / { try_files $uri $uri/ /index.html; } } server { listen 80; server_name example.com; return 301 https://$host$request_uri; }第二个server块的作用是把所有HTTP请求301跳转到HTTPS,这是最常见的强制跳转写法。return 301是唯一正确的做法,不要用meta刷新或者JS跳转,那些对搜索引擎不友好。
注意http2 on;这个写法,只适用于Nginx 1.25.1以上的版本。旧版本用的是listen 443 ssl http2;,如果你用的是我们推荐的1.26-alpine镜像,直接写http2 on;就行了。很多网上的教程还在用旧写法,套到新版本上可能会报错,这里特别提醒一下。
5.3 证书替换不生效的三大原因
HTTPS配置好之后,每年总会遇到一次证书到期需要替换的场景。很多人换完证书发现浏览器还是报旧证书,或者干脆显示证书错误,一般逃不出这三个原因:
第一,只是上传了新证书文件,Nginx并没有重新加载。证书文件在启动时被读入内存,你需要执行sudo docker exec nginx nginx -s reload让新证书生效。注意不是重启容器,reload足够。
第二,浏览器缓存。特别是你频繁测试时,浏览器会缓存证书状态,换个无痕窗口测一下基本就能排除这个因素。
第三,路径写错或权限没改。替换证书后,如果文件名变了,记得同步修改nginx配置里的ssl_certificate路径;如果权限设置不对,容器加载时会直接报错。我习惯在替换后用sudo docker exec nginx nginx -t先做语法检查,再reload,一套流程走下来基本不会出错。
6. 常见问题与排查技巧实录
6.1 容器启动失败或反复重启
这是频率最高的一类问题。症状很明显:docker ps看不到容器,或者看到状态一直显示Restarting,用docker logs nginx查看日志。
我把常见的几种情况整理成一张表,方便对照:
| 现象 | 常见原因 | 排查手段 |
|---|---|---|
| 启动即退出,日志显示端口被占用 | 宿主机80端口已被其他进程占用 | sudo ss -lntp | grep :80查占用进程 |
| 反复重启,日志显示配置错误 | 挂载的nginx.conf或conf.d配置语法有误 | sudo docker exec nginx nginx -t定位错误行 |
| 启动失败,无法加载证书 | 证书文件权限不足或路径错误 | sudo docker exec nginx ls -l /etc/nginx/cert检查 |
| 启动后外部无法访问 | 安全组未放行80/443,或云盾拦截 | 控制台检查安全组规则 |
先说端口占用。如果你在服务器上已经用apt装过Nginx或者有其他Web服务,80端口肯定是占着的。解决方式二选一:停掉并卸载旧的Nginx,或者给Docker容器换端口。生产环境我建议清掉旧服务,因为端口不一致后面引来的麻烦更多。
再说配置错误。挂载配置文件是有风险的,因为Nginx启动时会读取挂载进来的配置,一个语法错误就可能导致容器起不来。我的经验是:每次修改配置文件后,先在宿主机上检查一遍内容,再执行nginx -t验证,最后reload。不要跳过任何一步。
6.2 修改配置不生效与404、403问题
“改了半天配置,浏览器访问还是老样子”是新手最常见的问题。根源几乎都是同一个:你改的是宿主机上的/data/nginx目录,但Nginx读的是容器内的/etc/nginx。挂载是单向的,宿主机的改动会同步进容器,但不要直接在容器内部去改配置,容器一旦重建,内部改动全部丢失。
正确的操作流程是:
# 1. 在宿主机上编辑配置文件 sudo vim /data/nginx/conf.d/example.com.conf # 2. 验证语法 sudo docker exec nginx nginx -t # 3. 重载配置 sudo docker exec nginx nginx -s reload至于404和403,各有一个最常见的原因。404大概率是root和alias用混了,我前面已经讲过,location /files/配alias /data/files/才能正确映射,如果写成root /data/files/,实际访问路径会变成/data/files/files/xxx,不404才怪。403则是权限或者index指令的问题,要么是目录权限不足,容器内的nginx用户无法读取;要么是目录下没有index.html文件,且autoindex off时Nginx会直接拒绝访问。
排查这类问题有一个标准方法:看日志。访问一次出错页面,然后sudo tail -n 50 /data/nginx/logs/error.log,Nginx会把具体原因写得很清楚,比瞎猜快得多。
6.3 多容器端口协调:Nginx与MySQL、Java应用如何共存
部署服务很少只有一个Nginx,MySQL、Redis、Java应用这些容器迟早要一起上。这里最忌讳的是“每个容器都直接映射端口到宿主机”。比如MySQL的3306端口,如果你把它暴露到公网,等于把数据库裸奔在互联网上,扫描机器人分分钟爆破你的密码。
正确做法是给容器建一个独立的Docker网络,让它们通过容器名互相访问。我一般这样操作:
# 创建专用网络 sudo docker network create app-net # 启动MySQL,只加入网络,不暴露端口到宿主机 sudo docker run -d \ --name mysql \ --network app-net \ --network-alias mysql \ -e MYSQL_ROOT_PASSWORD=yourpassword \ -v /data/mysql:/var/lib/mysql \ mysql:8.0 # 启动后端Java应用,同样加入网络 sudo docker run -d \ --name backend \ --network app-net \ --network-alias backend \ -p 8080:8080 \ your-app-image注意这里有个细节:我把后端的8080端口暴露到了宿主机,因为Nginx容器要能访问它。但更好的方式是让Nginx也加入app-net网络,然后直接用容器名访问:
# 把已创建的nginx容器连接到app-net网络 sudo docker network connect app-net nginx连接之后,Nginx的proxy_pass http://backend:8080就能通过容器名解析到后端的IP,即使后端容器重启导致IP变化,也不受影响。MySQL更简单,Java应用在同一个网络里直接用主机名mysql:3306连接,完全不需要知道IP。
这套方案的额外好处是:3306、6379这些端口从头到尾都没有暴露到宿主机外部,安全组的风险面大幅缩小。
7. 维护与性能笔记
7.1 日志轮转与磁盘空间管理
Nginx容器的日志通过挂载已经写到了/data/nginx/logs,接下来要解决的是日志增长问题。访问量大的站点,access.log一天涨几百MB很正常,不处理的话三个月就能把磁盘塞满。
Docker守护进程的日志轮转参数我在前面已经配置好了,但Nginx自身日志的轮转需要额外处理。Linux上标准的方案是logrotate,新建一个/etc/logrotate.d/nginx文件:
/data/nginx/logs/*.log { daily rotate 14 compress delaycompress missingok notifempty sharedscripts postrotate sudo docker exec nginx nginx -s reopen endscript }这里有个知识点:Nginx写日志用的是文件描述符,直接mv日志文件不会让Nginx重新打开新文件,必须执行nginx -s reopen来通知它重新打开日志文件。postrotate里的命令就是干这个的。不写这一步,日志轮转之后会发现旧文件被删了,新文件却不产生内容,或者出现奇怪的重复写入。
文件描述符这个问题,本质原因是Nginx进程还在往旧文件句柄上写数据,logrotate只是替换了目录里的文件名。这个坑我踩过,现在每次配置日志策略都会先想想“reopen做了没有”。
7.2 镜像升级与回滚实操
前面提到固定镜像版本,但版本总归要升级的。比如Nginx出了新的补丁版本,你想更新到1.26.x的最新补丁,操作流程是:
# 1. 拉取新镜像 sudo docker pull nginx:1.26-alpine # 2. 停掉旧容器,但不删除 sudo docker stop nginx sudo docker rename nginx nginx-bak # 3. 用相同参数启动新容器 sudo docker run -d \ --name nginx \ --restart always \ -p 80:80 \ -p 443:443 \ -v /data/nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /data/nginx/conf.d:/etc/nginx/conf.d:ro \ -v /data/nginx/html:/usr/share/nginx/html:ro \ -v /data/nginx/logs:/var/log/nginx \ -v /data/nginx/cert:/etc/nginx/cert:ro \ nginx:1.26-alpine # 4. 验证新容器正常后,删除备份容器 sudo docker rm nginx-bak如果新版本有问题,回滚也很简单:把nginx-bak重命名回nginx,重新启动即可。整个过程不超过一分钟,这就是容器化部署在运维上的最大红利。
还有一个建议:如果你的服务器上同时运行着多个服务,最好给每个容器加上--restart always之外还挂一个健康检查脚本,定时curl一下首页看是否返回200。这样即使凌晨三点出问题,至少日志能告诉你故障是从哪个时间点开始的。
这套配置我在几台ECS上反复验证过,最深的体会是:Docker部署Nginx,最大的坑其实不在Docker本身,而在于你对目录规划、文件权限和容器网络的理解。把/data/nginx这个目录当成Nginx的全部家当,所有操作围绕它展开,思路会清晰很多。下一篇我会接着写MySQL容器化部署和它与Nginx的联动配置,如果你照着这篇把环境搭好了,后面基本上就是水到渠成的事。