ARTICLE DETAIL

资讯详情

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

Docker安装与nginx反向代理实战:从零部署到避坑指南

Docker安装与nginx反向代理实战:从零部署到避坑指南 做后端这几年我越来越确信一件事很多看起来复杂的架构问题一旦拆到容器层面思路会清晰很多。今天这篇内容不端着讲原理就做一件事——带你把 Docker 从零装好再跑一个 nginx并用它完成反向代理配置。中间会踩到哪些坑我会按自己的实战经验提前标出来。适合刚接触容器、被各种配置文件绕晕的同学也适合那些已经在服务器上手工编译过 nginx、受够了依赖地狱的老手。整个流程走下来你能独立部署 nginx 静态站点能看懂一份反向代理配置里每一项参数的含义也能自己写出多服务场景下的 nginx 转发规则。顺便说一句nginx 官方镜像比在服务器上手动编译安装省心太多这也是我当年转用 Docker 的直接原因。1. 安装 Docker 前先把镜像和容器的关系搞明白1.1 Docker 到底是什么为什么用容器跑 nginx 更省心如果你第一次接触 Docker可以把它想象成一个“软件集装箱码头”。每条船、每个集装箱都是一个独立的镜像集装箱里装着你应用运行所需的全部依赖操作系统依赖库、配置文件、运行时环境。Docker 做的事情就是把这些集装箱搬运到任意一台安装了 Docker 引擎的服务器上一键启动运行结果完全一致。传统方式部署 nginx 时最常见的坑是系统环境不一致。你在自己电脑上写的配置放到 CentOS 服务器上可能因为 openssl 版本不同、nginx 编译参数不同、依赖库缺失直接启动失败。而 nginx 官方镜像把编译好的二进制、运行库、默认配置全部打包好你只需要拉下来跑起来不用关心宿主机的系统环境。这就是容器相对于传统部署方式最核心的优势环境一致性和快速交付。1.2 三个核心概念镜像、容器、仓库很多新手把“镜像”和“容器”混为一谈这会导致后面配置挂载、修改文件时完全找不着北。镜像Image一个只读的模板包含了程序运行的一切文件和默认配置。它相当于一个“光盘”烧录进去之后你不会去改它而是基于它去创建实例。容器Container镜像的运行实例。每运行一次镜像就会生成一个可写层你在里面创建文件、修改配置、安装软件都发生在这一层。容器可以启动、停止、删除删掉之后再从一个新容器开始就等于系统“恢复出厂设置”。仓库Registry存放镜像的中央仓库。最常用的是 Docker Hub你可以把它理解成 npm 或 Maven 的公共仓库不过里面放的是操作系统级别的打包产物。理解这三个概念之后后面所有命令都会变得很自然从仓库拉取镜像到本地基于镜像启动容器在容器里修改配置把数据目录通过挂载卷暴露给宿主机。这套逻辑搞通了Docker 的基本盘就稳了。2. 不同操作系统安装 Docker 的具体操作2.1 Windows 系统安装 Docker DesktopWindows 上安装 Docker 目前的主流方案是 Docker Desktop它依赖 WSL2 后端。整体思路是在 Windows 里跑一个轻量级 Linux 子系统Docker 引擎运行在这个子系统中Windows 上的命令行工具与它通信。安装前先确认两件事BIOS 里已经开启虚拟化任务管理器性能页能看到“虚拟化: 已启用”系统是 Win10 64 位专业版、企业版或教育版Win11 则更加流畅。接着去 Docker 官网下载 Docker Desktop 安装包安装过程中默认勾选“Use WSL 2 instead of Hyper-V”等待安装完成。如果你发现官方安装包下载速度很慢可以留意国内云厂商或软件镜像站提供的 Docker Desktop 安装包镜像下载后检查文件哈希与官方一致再安装。装完启动 Docker Desktop首次启动会自动初始化 WSL2 内核稍等片刻界面上提示 Docker Engine 已运行就说明安装成功。这类桌面工具的问题大多集中在 WSL2 内核版本过低遇到“WSL 2 installation is incomplete”错误时去微软官方文档更新一下 WSL2 内核补丁即可。2.2 Linux 系统安装 Docker EngineLinux 服务器上安装 Docker很多人会图省事直接用发行版仓库里的 docker.io 包但那个版本经常比较旧。我的建议是用 Docker 官方源安装。Ubuntu 和 Debian 系的典型流程是这样# 安装工具包配置官方 apt 源 sudo apt update 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 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-ce sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.ioCentOS / RHEL 系的流程类似只是包管理器换成 yum然后通过 systemctl 管理sudo systemctl enable --now docker安装完成之后一定要执行一个操作把当前用户加入 docker 用户组这样不用每次敲命令都加 sudo。sudo usermod -aG docker $USER然后退出终端重新登录让用户组变更生效。这一步如果漏了后面跑 docker 命令都会报 permissions 错误而很多新手会以为是 Docker 坏了实际上只是当前用户没有访问 docker.sock 的权限。2.3 配置镜像加速源解决下载慢的问题几乎所有刚装完 Docker 的人都遇到过同一个尴尬拉取 nginx 镜像时进度条半天不走或者直接超时。这很可能不是网络断线而是默认镜像仓库的连通性不稳定。解决办法是配置镜像加速源也就是 registry mirror。Linux 环境下编辑或创建 /etc/docker/daemon.json{ registry-mirrors: [ https://你的加速地址 ] }然后重启 Docker 服务使配置生效sudo systemctl daemon-reload sudo systemctl restart dockerWindows 版的 Docker Desktop 更简单打开 Settings进入 Docker Engine 标签页把 JSON 里的 registry-mirrors 字段填好点击 Apply Restart。注意加速地址建议到各家云厂商开发者文档里找最新的这类地址变化较快不同区域可用性也不一样。配置完可以用 docker info 命令查看 Registry Mirrors 是否成功列出看到了就说明已生效。2.4 验证安装是否成功的两个命令装完任何环境第一件事是验证。先看版本信息docker version能看到 Server 部分的输出说明 Docker 引擎正常运行。如果只有 Client 部分而 Server 连接失败多半是 Docker daemon 没启动或者当前用户没权限访问 socket。再跑一次经典 hello-worlddocker run hello-world这条命令会从仓库拉取一个极小的镜像然后打印一段欢迎信息。它能正常输出意味着 Docker 的拉取、镜像创建、容器运行完整链路已经通了。到这里环境准备工作就告一段落。3. Docker 镜像与容器基础操作这里都是高频命令3.1 最常用的十个 Docker 命令Docker 命令很多但日常开发用到频率最高的就一小撮。我把它们按使用场景分组镜像相关docker pull 拉取镜像docker images 查看本地镜像docker rmi 删除镜像docker build 构建镜像。容器相关docker run 创建并启动容器docker ps 查看运行中的容器加 -a 查看全部docker start / stop / restart 控制容器启停docker rm 删除容器docker exec 进入容器执行命令。日志与状态docker logs 查看容器输出docker stats 查看资源占用docker inspect 查看容器详细配置。举个例子拉取 nginx 镜像并启动一个测试容器一条命令就能完成docker run -d --name webserver -p 8080:80 nginx:alpine这条命令里 -d 表示后台运行--name 给容器命名-p 把容器的 80 端口映射到宿主机 8080 端口。然后浏览器访问 http://localhost:8080就能看到 nginx 默认欢迎页。如果看不到先看容器是否启动成功docker ps -a再看看日志docker logs webserver。排查顺序一定是先看状态、再看日志。3.2 端口映射与挂载卷容器和宿主机沟通的两座桥容器本身是一个隔离环境宿主机默认无法直接访问容器内的端口文件也不能直接读写容器内部的数据。所以需要两座“桥”端口映射和挂载卷volume。端口映射解决的是网络访问问题。语法是 -p 宿主机端口:容器端口。比如 nginx 容器内部监听 80我们想用宿主机 80 访问就写 -p 80:80。如果想多个 nginx 同时跑可以分别映射到 8080、8081 等不同宿主机端口。挂载卷解决的是数据持久化问题。容器一旦被删除容器内创建的文件就全部丢失这是容器设计使然。为了让配置文件、日志、业务数据在容器重建后依然保留我们会把宿主机的目录映射到容器内部-v /宿主机路径:/容器路径。挂载目录是 Docker 实战里最值得花时间理解的一个知识点因为 nginx 部署、MySQL 数据存储、Redis 持久化全都要依靠它。3.3 强制删除、进入容器、拷贝文件这些细节操作容器状态不同删除命令也不一样。运行中的容器直接 docker rm 会报错需要先 docker stop 再删除或者用 docker rm -f 强制删除。这在做 nginx 配置调整、想快速重建容器时非常常用我一般都直接用 -f 参数省一步操作。进入容器内部有两种常用方式docker exec -it webserver sh注意nginx 官方镜像基于 Debian 或 Alpine默认没有 bash所以用 sh。如果想在容器里执行单条命令比如测试 nginx 配置是否正确可以直接写docker exec webserver nginx -t另一个细节是容器和宿主机之间拷贝文件docker cp 本地文件路径 容器名:/容器路径 docker cp 容器名:/容器路径 本地文件路径docker cp 适合临时改一下容器里的文件但不建议作为常态化配置手段。因为容器重建之后拷贝进去的东西就没了长期运维仍然推荐用挂载卷来管理配置。4. 用 Docker 部署 nginx 静态站点从拉镜像到挂目录4.1 拉取 nginx 镜像并启动第一个容器nginx 官方镜像有两个常见标签nginx:latest 和 nginx:alpine。alpine 镜像基于 Alpine Linux体积小很多约 25MB适合生产环境使用latest 基于 Debian包更全适合需要调试的场景。入门阶段我建议直接上 alpine体积小、启动快出问题的面也小。启动一个静态站点服务完整命令如下docker run -d \ --name nginx-web \ -p 80:80 \ -v /data/project1:/usr/share/nginx/html/project1 \ -v /data/nginx/conf.d:/etc/nginx/conf.d:ro \ nginx:alpine这条命令做了三件事后台启动一个名为 nginx-web 的容器把宿主机的 80 端口映射到容器 80 端口把两个宿主机目录分别挂载到容器的静态文件目录和配置目录。其中 conf.d 挂载加了 :ro 后缀意思是容器内这个目录只读防止容器内部误改配置。这个细节容易忽略但能让配置管理更可控。4.2 nginx 配置文件在容器里的位置这个必须记牢很多人在容器里找 nginx 配置时晕头转向因为镜像里的路径和手工编译安装的路径不一样。基于官方镜像关键路径如下主配置/etc/nginx/nginx.conf自定义站点配置/etc/nginx/conf.d/ 目录下默认有一个 default.conf静态文件根目录/usr/share/nginx/html日志文件/var/log/nginx/access.log 和 error.log重点是nginx.conf 主程序里默认有一行 include /etc/nginx/conf.d/*.conf; 这意味着你要添加新站点不需要改主配置只要在 conf.d 目录下放一个以 .conf 结尾的文件再重载 nginx 即可。这个设计让多站点管理非常清爽。修改配置两种方式一是进容器直接改适合临时验证二是宿主机写好通过挂载卷映射进去这才是长期项目的推荐做法。后面所有配置文件我都放在宿主机目录里再挂载进容器容器一旦出问题可以随时重建配置不会丢。4.3 一台 nginx 挂多个项目目录别再走错路需求场景很常见一个服务器上部署了前端项目 A 和 B想共用同一个 nginx通过不同路径区分比如 /proj1 和 /proj2。做法就是给每个项目准备一个目录全部挂载进去。docker run -d \ --name nginx-web \ -p 80:80 \ -v /data/www/project1:/usr/share/nginx/html/project1 \ -v /data/www/project2:/usr/share/nginx/html/project2 \ -v /data/nginx/conf.d:/etc/nginx/conf.d:ro \ nginx:alpine然后在 conf.d 目录下的配置文件里写好 location 规则server { listen 80; server_name _; location /proj1/ { alias /usr/share/nginx/html/project1/; index index.html; } location /proj2/ { alias /usr/share/nginx/html/project2/; index index.html; } }这里有一个高频坑location 里用了 alias 还是 root。root 会拼接完整路径alias 则是直接替换 location 匹配部分。用 root 加项目路径时nginx 会去 /usr/share/nginx/html/project1/proj1 找文件很容易出现 404。我在最初写静态站时就在这里栽过改成 alias 之后立刻正常。5. nginx 反向代理实战配置把请求转发给后端服务5.1 先搞清楚反向代理在这条链路里扮演的角色反向代理这个词的字面意思容易让人绕晕。我换个说法它就是一个“前台接待员”。你访问某个域名或端口前台接待员根据请求路径决定把请求转给后面的哪个业务部门。比如访问 /api转给后端 Java 服务访问 /admin转给管理后台访问 /返回静态页面。在 Docker 场景下反向代理还有额外的妙用容器之间通过内部网络互相访问外部客户端只能访问映射出来的端口。nginx 作为整个服务群的统一入口只需要对外暴露一个 80/443 端口内部再按规则转发到各个容器的对应端口。这样做的好处是安全、灵活多个服务复用一套 TLS 证书、一套访问控制策略非常符合实际生产环境的要求。5.2 一份可以直接抄作业的 nginx 反向代理配置假设我们有一个后端服务运行在容器的 8080 端口服务名是 app1还有一个静态前端站点。要用 nginx 做转发配置写在 /data/nginx/conf.d/reverse.conf 里内容如下server { listen 80; server_name api.example.com; location / { proxy_pass http://app1: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; } } server { listen 80; server_name www.example.com; root /usr/share/nginx/html; index index.html; }解释一下关键参数proxy_pass 指定转发目标地址这里用的是容器名 app1 加端口 8080前提是 nginx 和后端服务在同一个 Docker 网络里proxy_set_header 是转发时补齐请求头否则后端应用拿不到真实的客户端 IP日志统计会有偏差部分依赖 HTTPS 协议判断的应用也容易出问题。配置文件写好后测试语法并重载docker exec nginx-web nginx -t docker exec nginx-web nginx -s reloadnginx -t 用来检查语法输出 syntax is ok 再重载这一步能避免绝大多数配置错误引起的服务中断。5.3 多服务、多域名反向代理怎么扩展真实项目里往往不止一个后端服务。管理后台、用户端 API、文件服务每个都想用独立的域名或路径访问。这时只需要在 conf.d 目录下多建几个 server 块或者在同一个 server 块里按 location 分开处理。多数网友问我最多的问题就是 location 路径匹配规则。简单记忆精确匹配location /login优先级最高常用于精准拦截。前缀匹配location /api/当请求路径以 /api/ 开头时命中。正则匹配location ~ .php$按正则表达式匹配优先级低于精确匹配高于普通前缀匹配。默认匹配location /兜底处理所有未命中的请求。例如把 API 和静态文件放在同一个域名下server { listen 80; server_name example.com; location /api/ { proxy_pass http://app1:8080; proxy_set_header Host $host; } location /files/ { alias /data/files/; } location / { root /usr/share/nginx/html; index index.html; } }这里有一个容易踩的坑proxy_pass 后面是否带路径带不带会影响转发规则。带路径时比如 proxy_pass http://app1:8080/nginx 会用完整的原始请求路径替换掉 location 前缀不带路径时nginx 保持原始 URI 不变。具体行为有细微差别建议有疑问时先打日志确认实际请求路径。5.4 用 Docker Compose 编排 nginx 加多个应用服务手动 docker run 跑一堆容器还要自己创建网络、串联依赖命令会越来越长。这时引入 Docker Compose它用一份 YAML 文件描述所有服务一条 docker compose up -d 就能把整个服务群全部拉起。下面是 nginx 加两个后端服务的示例 docker-compose.ymlservices: nginx: image: nginx:alpine container_name: nginx-web ports: - 80:80 volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./html:/usr/share/nginx/html depends_on: - app1 - app2 app1: image: myapp1:latest container_name: app1 expose: - 8080 app2: image: myapp2:latest container_name: app2 expose: - 8081注意几个细节app1 和 app2 使用的 expose 关键字只暴露端口给 Docker 内部网络并不映射到宿主机这样外部无法直接访问后端服务只能通过 nginx 转发nginx 里的 proxy_pass 直接写 http://app1:8080 和 http://app2:8081因为 Compose 默认会在同一个网络里建立服务名的 DNS 解析。这个设计既清晰又安全是我在本地开发最常用的组合。6. 常见问题排查与避坑记录这些我全踩过6.1 镜像拉取慢、超时怎么办镜像拉取慢除了配置镜像加速源之外还有两个容易被忽略的原因。一是在大陆网络下直接拉取 Docker Hub 官方镜像速度不稳定此时优先检查 registry-mirrors 是否生效二是拉取的镜像体积过大比如 nginx:latest 有 190MB而 nginx:alpine 只有约 25MB如果只是跑服务没必要拉臃肿版本。如果加速地址不生效可以执行 docker info 查看 Registry Mirrors 字段下方是否列出了你配置的地址。没列出来大概率是 daemon.json 语法错误或者 Docker Desktop 的配置没有 Apply。改完配置后docker systemctl restart docker或者 Docker Desktop 里点 Restart千万不要省略重启步骤。6.2 端口被占用怎么处理启动容器时报错 bind: address already in use 是最常见的端口冲突。处理步骤很简单先找到占用端口的进程然后决定是停掉该进程还是给容器换端口。sudo lsof -i :80 # 或者 sudo netstat -tunlp | grep :80如果 80 端口被系统已有的 nginx 或 Apache 占用建议先停掉宿主机自带的 Web 服务或者把 Docker 容器映射到另一个端口比如 -p 8080:80。另外注意容器停止后端口会自动释放如果停掉了容器但端口依然占用考虑是不是有多个同名容器在跑用 docker ps -a 查一下。6.3 改了配置文件为什么始终不生效这个问题八成出在挂载路径上。容器内配置文件路径和宿主机挂载路径不匹配你在宿主机改了文件容器里看到的还是旧内容。排错时先进入容器确认实际内容docker exec nginx-web cat /etc/nginx/conf.d/reverse.conf如果文件内容没有变化问题基本可以断定是挂载卷没生效。检查 docker run 或 docker-compose.yml 里的 volumes 字段确保宿主机路径是绝对路径或者相对路径相对于 compose 文件而言正确。还有一种情况配置文件修改正确但忘记重载 nginx。每次修改完 conf 文件后都养成执行 nginx -t nginx -s reload 的习惯能省掉大量无效排查时间。6.4 反向代理出现 403 或 502 的排查思路反向代理报 403 和 502 的含义完全不同排查方向也要分开。403 Forbidden 常见原因有三个一是静态文件目录权限不足容器内 nginx 用户无法读取挂载目录修复方法是让宿主机目录至少对其他用户有 r-x 权限二是没有 index 文件nginx 默认查找 index.html 找不到时直接报错三是 location 匹配到了禁止访问的规则比如 deny all。502 Bad Gateway 则说明 nginx 成功收到了请求但转发目标不可达。按顺序检查后端容器是否在运行docker ps服务名和端口是否写对容器间通信要用服务名而非 localhost两个容器是否在同一个 Docker 网络Compose 自动建网手动 run 需要加入相同网络后端服务本身是否启动成功docker logs 后端容器。我见过最多的错误就是把 proxy_pass 写成了 http://localhost:8080在容器内 localhost 指向的是 nginx 容器自己自然无法访问后端。6.5 日志怎么看容器日志和 nginx 日志别搞混容器日志和 nginx 业务日志是两回事。docker logs nginx-web 输出的是容器主进程的标准输出nginx 官方镜像把 access.log 和 error.log 都软链接到了 /dev/stdout 和 /dev/stderr所以能直接看到访问日志。但如果你有一个容器内挂载了自定义日志目录docker logs 就不会包含业务日志。此时需要去宿主机挂载目录看文件。建议在排查问题的时候先 docker logs 看启动和访问情况如果信息不够再进容器或用挂载日志分析。线上问题排查日志永远比别人告诉你的经验更接近真相。6.6 给刚入门的人几个实在建议第一不要试图记住所有命令。记不住 docker run 的参数不是问题工程上没人靠背命令干活而是靠文档和模板。把一份能用的 docker run 和 docker-compose.yml 保存在自己的代码库里需要时改一改。第二容器命名要有规律。我有段时间给容器起名 nginx1、新的容器 nginx2最后根本分不清哪个挂载了哪份配置。后来统一按“服务名-用途-序号”的方式命名配合 docker ps 的 --format 参数输出可读列表管理起来清晰得多。第三生产环境尽量少用 docker run 裸启动凡是要长期运行的服务都交给 Compose 或更上层的编排工具。裸启动的命令很难追溯别人接手时完全不知道这些容器是怎么来的配置文件又是哪一份。把这些可复现的东西统一交给 docker-compose.yml 管理才是一个可持续维护的方案。我个人在实际操作中的体会是Docker 最大的价值不是让你记住多少命令而是把你从“环境能不能跑”这件事里解放出来。nginx 反向代理只是第一步等你熟悉这套流程之后MySQL、Redis、Node 服务都能用同样的方式快速编排起来。真遇到配不明白的地方先去看日志日志永远比配置文件诚实。希望这篇文章能帮你少走几段弯路也希望你装好 Docker、跑起 nginx 之后能像我当初一样由衷觉得早知道容器这么省事真该早点用。
返回列表