ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Docker部署Nginx完全指南:从容器配置到反向代理与HTTPS实战

Docker部署Nginx完全指南:从容器配置到反向代理与HTTPS实战 阿里云服务器上跑服务我自己的固定搭配就是“国内云主机 Docker容器 Nginx反向代理”。上一篇文章我们把基础环境捋了一遍这篇直接进入重头戏用Docker把Nginx部署起来并且把配置彻底讲透。很多朋友卡在这一步不是说Nginx容器跑不起来而是跑起来之后不会配或者配了之后不生效最后越调越乱。这篇文章的目标很简单看完之后你能做到三件事第一在阿里云服务器上用Docker拉起一个Nginx容器第二把静态站点、反向代理、多站点这些最常见的需求配置好第三遇到端口冲突、证书不生效、配置丢失这类问题的时候能自己快速排查不用满世界找答案。我尽量把每一步的参数、原因、坑都写明白不只是给你一行能复制的命令还会告诉你这行命令为什么这么写出了问题怎么改。1. 为什么我坚持用Docker跑Nginx1.1 对比传统方式省掉的不只是安装那一步有的人可能会问我就部署一个Nginxyum install nginx或者apt install nginx一下不就行了干嘛非要折腾Docker这个问题我以前也纠结过。直接装确实简单一条命令的事。但实际维护起来你就知道区别了。传统方式下Nginx的配置文件分散在/etc/nginx/、/usr/local/nginx/conf/之类的地方不同Linux发行版路径还不一样。你要是同时维护好几台服务器一台CentOS一台Ubuntu配置文件路径、默认目录、甚至编译参数都有差异很容易精神分裂。Docker方式最大的好处是环境一致性。镜像本身就是打包好的Nginx运行环境无论你的云服务器是什么系统跑起来的Nginx行为完全一致。我自己的经验是只要把配置文件通过目录挂载的方式放到宿主机上后续的修改、备份、迁移都非常干净。想回滚一个版本换一个镜像tag重新起容器就行比yum remove再install不知道快到哪里去了。1.2 哪些场景适合用这个方案不是所有场景都非得用Docker跑Nginx但你如果属于下面这几种情况这个方案是很合适的静态站点托管前端项目打包出来的dist目录扔进容器共享的html目录里Nginx直接serve。反向代理/网关服务器上跑着多个后端服务比如一个Java的8080端口、一个Python的8000端口由Nginx统一接收外部请求再按路径或者域名转发到对应服务。多站点共存一台服务器上挂好几个网站域名每个域名一套独立的Nginx配置互不干扰。HTTPS证书统一管理证书文件放在宿主机挂载的cert目录里Nginx容器负责终止SSL证书的续期、替换都很方便。你要是第一次上手就用这套思路做后面扩展也方便。2. 动手前的准备清单2.1 云服务器基础环境确认动手之前先把云服务器的环境确认清楚。我以阿里云服务器为例一般默认的镜像是Ubuntu 22.04或者CentOS 7.9也有Alibaba Cloud Linux。先看一下系统版本和架构登录服务器后执行cat /etc/os-release uname -m不出意外的话x86_64架构是主流。如果你搞的是ARM架构的实例比如倚天实例拉取镜像的时候要注意选择arm64的tag不过Nginx官方镜像一般都带多架构支持直接拉默认就行。接下来是有可能被忽略但实际上很重要的一步安全组放行端口。阿里云服务器的80和443端口第一次使用之前默认是不放行的。你去控制台找到安全组规则把入方向的80HTTP、443HTTPS端口加进去。这个步骤不做后面容器跑起来了浏览器就是死活访问不到。再检查一下宿主机80端口有没有被占ss -lntp | grep -E :80|:443如果已经有东西占着80端口后边跑Nginx容器会有冲突。最稳妥的办法是先把占用端口的东西停掉或者改端口。2.2 Docker安装与镜像加速服务器如果还没装Docker这里给出一个最稳的安装方式。阿里云服务器如果是22.04系统我建议直接用官方脚本安装省事、不容易踩源的问题curl -fsSL https://get.docker.com | sh systemctl enable docker systemctl start docker国内服务器拉Docker Hub的镜像本身就慢Nginx镜像虽然不算大但要是后面你还要拉MySQL、Redis这些不配加速器会等到怀疑人生。阿里云提供了一个容器镜像加速器服务你在控制台搜“容器镜像服务”里面能看到专属的加速地址格式像这样https://xxxxx.mirror.aliyuncs.com。把它写进Docker配置文件mkdir -p /etc/docker tee /etc/docker/daemon.json -EOF { registry-mirrors: [https://xxxxx.mirror.aliyuncs.com] } EOF systemctl daemon-reload systemctl restart docker验证一下Docker装好没有执行docker version能看到Client和Server两段信息就说明没问题。2.3 部署目录规划配置和数据不放在容器里这是我自己用Docker跑Nginx以来最大的心得之一容器是无状态的配置和数据必须放在宿主机上。你要是把配置直接写在容器里哪天不小心删了容器或者重新拉一个新版本镜像之前的所有配置全部没了想哭都来不及。所以一开始就要规划好目录结构。我在服务器上建了这么一套/opt/nginx/ ├── conf.d/ # 站点配置文件每个站点一个.conf ├── html/ # 静态资源根目录 ├── logs/ # 容器日志输出目录 └── cert/ # SSL证书目录创建目录的命令很直接mkdir -p /opt/nginx/{conf.d,html,logs,cert}这套目录后面全部挂载进容器配置改起来非常方便直接改宿主机上的文件然后在容器里reload一下Nginx就行。3. 从拉镜像到跑起第一个Nginx容器3.1 选择镜像版本不是越新越好Nginx官方镜像有几种tag很常用镜像标签说明适用场景nginx:latest最新版跟着官方更新不推荐生产环境直接用版本变动不可控nginx:stable稳定版更新慢生产环境推荐nginx:alpineAlpine Linux为基础体积极小追求小体积时推荐我个人建议用nginx:stable-alpine或者nginx:stable。Alpine版镜像体积在25MB左右普通版在60MB左右差别不算大但Alpine的包管理工具跟CentOS/Ubuntu不一样你如果习惯进容器执行apt那还是用普通stable版更顺手。3.2 最核心的docker run命令逐参数拆解环境准备好了直接创建一个Nginx容器。下面这个命令是我实际部署中最常用的一版包含80和443的端口映射以及目录挂载docker run -d \ --name nginx \ -p 80:80 \ -p 443:443 \ -v /opt/nginx/conf.d:/etc/nginx/conf.d \ -v /opt/nginx/html:/usr/share/nginx/html \ -v /opt/nginx/logs:/var/log/nginx \ -v /opt/nginx/cert:/etc/nginx/cert \ --restartalways \ nginx:stable看不懂没关系逐项拆解-d后台模式运行容器在后台跑不会占住你的终端。--name nginx给容器起个名字叫nginx后面管理容器时方便引用。如果不指定Docker会随机生成一个名字不太利于识别。-p 80:80端口映射宿主机80端口映射到容器80端口。左边是宿主机端口右边是容器端口。-p 443:443同样映射HTTPS端口后面配SSL证书用。-v /opt/nginx/conf.d:/etc/nginx/conf.d目录挂载。把宿主机上我们准备好的配置目录挂到容器里的Nginx配置目录。这样你改宿主机文件容器内部同步生效。-v /opt/nginx/html:/usr/share/nginx/html挂载Nginx默认的静态资源目录。-v /opt/nginx/logs:/var/log/nginx把容器里Nginx的日志输出到宿主机目录排查问题时直接看宿主机的文件就行。-v /opt/nginx/cert:/etc/nginx/cert证书目录挂载这是我自己习惯加的一项。--restartalways容器异常退出或者服务器重启后自动启动。这个参数很重要否则你重启一次云服务器Nginx容器就再也不会自动起来了。注意-p 80:80这个映射只要宿主机80端口没被占用就能直接用。如果你服务器上还有其他Web服务占着80可以把左边改成8080之类的端口但这样访问的时候就要带端口号了不太优雅建议还是把80腾出来给Nginx。3.3 验证部署是否成功容器创建之后验证一下docker ps看到nginx容器状态是Up就基本成功了。接着在服务器本机用curl验证一下curl http://localhost如果返回一段Nginx的默认HTML内容说明Nginx已经在正常工作。再用浏览器直接访问服务器的公网IP也应该看到同样内容。如果浏览器访问不到优先检查安全组规则和云控制台的防火墙设置这个最容易被忽略。从这一步开始你已经有了一台运行在Docker容器里的、随时可以通过宿主机配置文件改配置的Nginx服务器。4. Nginx配置文件完整拆解4.1 搞清楚谁是主配置文件进入正题之前先理清楚容器的目录结构。Nginx容器里的主配置文件是/etc/nginx/nginx.conf这个文件一般不直接改因为它定义了Nginx进程、全局事件、worker进程数等基础设置。再看内容你会发现有一行include /etc/nginx/conf.d/*.conf;这行决定了虚拟主机配置文件都放在/etc/nginx/conf.d/目录下。所以我们平时要写的站点配置全部放在这个挂载目录里主配置基本不用碰。这也是我为什么要求把/opt/nginx/conf.d挂载到容器/etc/nginx/conf.d的原因。在/opt/nginx/conf.d/下新建一个配置文件比如default.confserver { listen 80; server_name your_domain_or_ip; root /usr/share/nginx/html; index index.html index.htm; location / { try_files $uri $uri/ /index.html; } }server_name填你的域名没有域名就填服务器公网IP。root和index定义了Nginx找文件的位置和默认首页文件名。这个配置文件结构就是Nginx最基础、最核心的单位一个server块对应一个站点服务。4.2 location匹配机制网上很少讲透的规则location是整个Nginx配置里的头号难点。很多人反代配不明白、路径重写看不懂根本原因就是没理解location的匹配顺序。location按字符分成四类优先级从高到低location /精确路径精准匹配。完全一样才命中匹配上之后立马生效后面的不再看。location ^~ /前缀路径前缀匹配。命中之后不再检查正则location等于说最高优先级的“前缀匹配”。location ~ 正则路径或~*不区分大小写按正则匹配。按配置文件里出现的先后顺序匹配。location /普通前缀普通前缀匹配所有没有写、^~、~的前缀都属于这一类。匹配到最长前缀的生效。光说规则比较抽象我举个实际例子。假设配置里有下面几项location / { return 200 精确匹配根路径; } location ^~ /static/ { return 200 前缀匹配static; } location ~ \.(js|css)$ { return 200 正则匹配js/css; } location /api/ { return 200 普通前缀匹配api; }请求https://example.com/时因为写了location /直接命中第一个走的就是精确匹配。请求https://example.com/static/app.js时^~ /static/直接命中char都不带犹豫的最后的结果是“前缀匹配static”。这种情况下后面的正则匹配js走不到。请求https://example.com/assets/app.js时没有精确和强制前缀命中开始走正则命中~ \.(js|css)$输出“正则匹配js/css”。请求https://example.com/api/user时没有更高级的匹配只能走普通前缀命中/api/。这个机制搞明白之后绝大多数location配置问题都能想通。比如“为什么我写的正则不生效”八成是因为前面有个^~前缀提前拦截了“为什么精确匹配的路径不生效”八成是路径写错了跟请求不一致。注意location顺序会影响正则匹配的结果普通前缀匹配和正则匹配的优先级是“先最长前缀、再正则”这个跟配置文件里location的先后顺序无关但正则之间才讲先后顺序。4.3 静态站点的完整配置与SPA路由处理部署一个前端项目最常见的就是把打包好的文件放到html目录里再配置一个server块。比如前端项目构建后生成dist目录把dist里的内容拷到/opt/nginx/html/myapp/下然后新增一个配置文件myapp.confserver { listen 80; server_name app.example.com; root /usr/share/nginx/html/myapp; index index.html; location / { try_files $uri $uri/ /index.html; } }这里关键在try_files那行。它的意思是按顺序尝试寻找文件$uri是请求的路径比如/about如果找到文件就直接返回$uri/是尝试找目录如果都找不到就把请求转发给/index.html。这一步对前端SPA单页应用来说极其重要。因为Vue或React的路由是前端自己控制的访问https://app.example.com/about时服务器上并没有/about这个真实文件如果不加try_files兜底Nginx会直接返回404。加了之后所有不存在的路径都会回到index.html由前端路由接管。4.4 反向代理配置实战一个入口带飞全部后端服务反向代理是Nginx在微服务架构下最常用的能力。我现在负责的一个项目前端一个Vue站点后端分了好几个微服务用户只访问一个域名剩下的转发全部由Nginx完成。最简单的一个反向代理配置server { listen 80; server_name api.example.com; location /api/ { proxy_pass http://127.0.0.1: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; } }这个配置的含义所有/api/开头的请求转发给本机8080端口的Java服务。注意proxy_pass这里有个常见小坑如果proxy_pass后面不带路径就是只有IP和端口末尾没有斜杠那么转发的时候会把原始URI原样带过去如果带了路径比如http://127.0.0.1:8080/那么/api/这一段会被替换掉。举个例子请求https://api.example.com/api/user/list上面这个配置转发到后端的URL是http://127.0.0.1:8080/api/user/list如果你把proxy_pass写成http://127.0.0.1:8080/那后端收到的就是http://127.0.0.1:8080/user/list。proxy_set_header三兄弟的作用也要理解一下。Host $host保证后端能拿到原始的域名否则后端拿到的可能是127.0.0.1:8080很多根据域名做判断的功能就会失效X-Real-IP和X-Forwarded-For是用来传递用户真实IP的因为如果Nginx不显式传递后端看到的IP是Nginx容器或者宿主机自身的日志里全是同一个IP后续做安全策略或者审计就不好搞了。如果你的后端服务还是走WebSocket的很多实时通讯、协同编辑工具都要那还需要加上两个Headerproxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade;没有这两行WebSocket连接会在握手阶段失败。还有一点我需要说一下location里配反代时容易踩的坑。如果你后端接口路径统一有前缀比如所有接口都是/api/开头那反代配置最稳的方式是只配一个location前缀。如果你在后端也保留/api那就会出现/api/api双重前缀的问题这个我初期调试的时候也踩过。4.5 多站点共存一个Nginx挂多个域名“本地虚拟机多端口Nginx开发环境多站点自定义域名配置”这种需求在服务器上也同理。一台阿里云ECS你完全可以挂好几个网站。做法就是在一个server里加多个server块每个server_block负责一个域名。比如要同时跑两个前端项目server { listen 80; server_name site1.example.com; root /usr/share/nginx/html/site1; index index.html; location / { try_files $uri $uri/ /index.html; } }server { listen 80; server_name site2.example.com; root /usr/share/nginx/html/site2; index index.html; location / { try_files $uri $uri/ /index.html; } }当请求带着不同的域名过来Nginx就用server_name去匹配对应的server块。如果多个server_name都不匹配则默认走第一个配置的server块你可以在第一个server块里写个return 444之类的错误码做兜底。如果你手里没有域名也想在本地或者开发环境模拟多站点可以在自己的电脑上改hosts文件把site1.example.com和site2.example.com都解析到服务器IP同样能验证效果。这里有一个我自己实践下来的经验不同的站点最好把日志分开存放方便排查。可以在每个server块里加access_log /var/log/nginx/site1.access.log main; error_log /var/log/nginx/site1.error.log;因为日志目录已经挂载到宿主机了所以直接去/opt/nginx/logs/下就能看到对应文件。4.6 SSL证书配置与“替换不生效”实战排错HTTPS现在是标配没有证书的网站在浏览器里直接标红“不安全”。阿里云自己就能申请免费的SSL证书每年可以领一次证书的品牌和到期时间会变但用法都差不多。把证书文件和密钥文件上传到服务器的/opt/nginx/cert/目录然后配置HTTPS server块。一个标准的HTTPS配置长这样server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/cert/example_com.pem; ssl_certificate_key /etc/nginx/cert/example_com.key; root /usr/share/nginx/html; 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这是最常用的配置方式。关于证书很多朋友遇到一个非常经典的问题“我明明替换了证书文件为什么访问还是旧证书”我自己也趟过一次。排查思路是这样的先确认你换到宿主机cert目录里的文件真的是新证书openssl x509 -in /opt/nginx/cert/example_com.pem -noout -dates看一下证书有效期如果显示的还是旧证书的时间说明文件替换错了或者文件名、路径没对上。还要在容器里执行一下配置测试docker exec nginx nginx -t如果提示OK再reload让Nginx重新读取配置docker exec nginx nginx -s reload不要用restart容器的方式来刷证书nginx -s reload才是平滑重载配置的唯一标准姿势reload不会中断现有连接而且能让新证书生效。最后浏览器端也要强制刷新清缓存旧证书的证书缓存经常会在本地差异环境下被浏览器死拉住CtrlF5强刷一次基本能解决。5. 常见问题与排查技巧实录5.1 端口被占用导致容器起不来症状是docker run命令返回端口冲突的错误提示port is already allocated或者bind: address already in use。这种情况在服务器上非常常见尤其是8080端口其他后端服务占用的概率极高。排查方式ss -lntp | grep :80找到占用进程的PID看看它是什么程序能停就停不能停就换端口。如果你不想在宿主机80端口跑Nginx可以改成-p 8080:80然后访问时用http://公网IP:8080。5.2 容器可以跑但浏览器死活访问不到优先检查云服务器控制台的安全组规则其次检查云盾/防火墙是否放行了端口。这个问题上面提过但值得再次强调因为出现概率太高了。我在阿里云上第一次配服务就卡在这容器日志显示一切正常Nginx也在监听80但外部就是连不进来后来才发现是安全组没放行。另外还有一种情况云服务器可能是按量计费的如果你用的是IPv6地址默认没有公网入口得确认你访问的是公网IPv4地址。5.3 配置改了但Nginx没反应很多新手改配置文件之后发现不生效第一反应是重启容器但重启了还是没反应。这就要检查你是不是改对了地方。如果你走的是目录挂载方案那么宿主机上的/opt/nginx/conf.d/*.conf等价于容器里的/etc/nginx/conf.d/*.conf这两个路径指向同一份文件。改完之后必须让Nginx重新加载配置正确姿势是docker exec nginx nginx -t docker exec nginx nginx -s reload两条命令一起用第一条检查语法如果语法错了第二条不会执行避免把Nginx搞挂。5.4 看日志排查一切问题的终极手段Nginx报错了先看日志别瞎猜。因为日志目录做了挂载直接看宿主机文件tail -f /opt/nginx/logs/error.log404类问题主要看访问日志tail -f /opt/nginx/logs/access.log看访问日志你会发现导致404的请求路径到底是什么、最终返回了什么状态码这些信息比你在浏览器按F12看Network面板更底层、更精确。注意如果每个站点单独配置了access_log路径日志会写到各自的文件里。最后补充几个实用小技巧我实际操作中习惯在修改任何配置之前先备份一下原文件cp /opt/nginx/conf.d/default.conf /opt/nginx/conf.d/default.conf.bak_$(date %Y%m%d)待到确认配置无误后再清理这个备份也不迟。另外配置文件的命名最好有规律比如按域名来命名example.com.conf这样一眼就知道哪个配置对应哪个站点别所有文件都叫default.conf或test.conf时间长了你自己都分不清。还有一个小建议如果你需要在服务器上同时跑Nginx、后端服务、数据库、缓存这一整套栈纯用docker run命令去管理容器会越来越乱。后面可以考虑引入docker compose或者更自动化的工具把这些服务的启动参数统一写进一个yml文件里一条命令全部拉起那又是另一篇实战故事了。关于Docker部署Nginx的内容基本就到这了配置部分看起来细碎但全是日常用得上的东西。按这套思路走哪怕遇到问题你也有清晰的排查路径。
返回列表