Docker实战指南:容器部署Nginx反向代理与配置详解
2026/9/20 15:08:55 网站建设 项目流程

1. 为什么学 Docker 总是“看完就忘”——先想清楚它解决什么问题

先聊个现象。很多人学 Docker 都是这么个流程:看一遍安装教程,敲几个 hello-world,觉得“哦原来是这么回事”,然后关上电脑,三个月后要用的时候,连docker run参数都记不全。

问题出在哪?不是记性差,是压根没搞明白 Docker 到底是干嘛的。它不是一个“装完就完事”的软件,而是一套改变你部署方式的工作流。你不理解这套工作流,硬背命令毫无意义。

一句话说清楚:Docker 帮你把应用连同它的运行环境一起打包,做成一个“集装箱”,在任何装 Docker 的机器上都能直接跑起来。

这句话怎么理解?以前要部署一个 nginx 服务器,你得先装系统、装依赖、改配置、开端口,每一步都可能出幺蛾子——换个机器就得重来一遍。Docker 的思路是:我把 nginx 和它需要的系统环境、依赖库、配置文件全部封装成一个镜像,你拿过去docker run一下,3 秒启动,行为完全一致。

用个生活类比:你从老家带了一罐腌菜去外地,怕路上碎了,就用泡沫箱打包,里三层外三层,贴好标签,到了目的地拆开直接吃,味道和在老家一模一样。Docker 镜像就是这个泡沫箱,你装的是啥、怎么装、里面什么结构,全在里头。

所以学习 Docker 的正确路径,不是把命令当单词背,而是带着问题去实践:我希望我的服务能一键部署、能快速迁移、能把环境依赖一次搞定。带着这个目标,你自然会理解为什么需要镜像、为什么需要容器、为什么需要端口映射,而不是“哦原来 docker run 后面要加 -p 啊”。

这篇博文就是按这个路径走的:先装环境,再理解核心概念,然后搭一个真正能用的 nginx 反向代理,最后把配置管理这套也理顺。整个过程我尽量还原实际踩坑经历,你跟着走一遍,比看十篇教程有用。

2. 安装这一步就有讲究:桌面版还是引擎版,别一上来就踩坑

2.1 不同系统下的安装选择对比

很多人直接搜“Docker 安装教程”,然后对着一个老掉牙的 Linux 教程在 Windows 上折腾,折腾半天装不上——因为压根不在一个频道。

先把安装策略理清楚,分三种情况:

操作系统推荐方案原因注意事项
Windows 10/11 家庭版/专业版Docker Desktop for Windows官方支持,带图形界面,配置最简单必须开启 WSL2 和 BIOS 虚拟化,否则装不上
Windows 老版本/不想装桌面版Docker Toolbox(不推荐)基于 VirtualBox,老古董了性能和兼容性都很差,遇到问题别死磕,换个方案
macOSDocker Desktop for Mac同样官方支持,体验最好Intel 和 Apple Silicon 芯片版本不同,别下载错
Ubuntu/Debian 服务器Docker Engine(命令行版)服务器上用桌面版没意义,占资源通过 apt 安装官方仓库版本,别用系统自带的旧版
CentOS/RHELDocker Engine同上注意 Docker 官方对 CentOS 7 的支持情况,内核太旧会有问题

说几个重点。

Windows 用户,最怕遇到的问题是“Docker Desktop 打不开”。百分之八十的原因是 BIOS 没开虚拟化。怎么查?打开任务管理器,性能选项卡,看右下角“虚拟化”是不是“已启用”。如果显示禁用,重启进 BIOS,找到 Intel VT-x 或 AMD-V 的选项,打开它,Windows 的“虚拟机平台”和“适用于 Linux 的 Windows 子系统”这两个功能也要在“启用或关闭 Windows 功能”里勾上。

另一个高频问题是 WSL2 版本太老。打开 PowerShell 执行wsl --update,确认内核是最新的。我见过不少人卡在这一步,Docker Desktop 提示“WSL 2 installation is incomplete”,其实就一条命令的事。

Ubuntu 服务器用户,直接按官方文档跑:

sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

装完后验证一下:

sudo docker run hello-world

看到 “Hello from Docker!” 就说明装好了。这一步跑通,后面基本顺风顺水。

2.2 安装完成后必做的两条配置

装完不是就完事了,有两件事必须做,不做后面会烦死。

第一件事,把当前用户加入 docker 组,省得每次敲命令都加 sudo:

sudo usermod -aG docker $USER newgrp docker

这一步做完,重新登录终端,docker ps就不需要 sudo 了。注意加组之后要重新登录会话才生效,我当时以为没生效,折腾了半天才发现是终端会话没刷新。

第二件事,配置镜像加速。这个我放在后面“镜像下载”部分细说,但先说一句:默认源在国内环境下下载镜像非常慢,你第一次跑docker pull nginx如果等了五分钟还在转圈,别怀疑人生,不是网的问题,是源的问题。解决办法是配置国内可用的镜像源。常用地址包括 Docker 官方中国镜像、中科大镜像、阿里云容器镜像服务等。配置完重启 Docker 服务即可生效。这个属于老生常谈,但具体怎么选、怎么配,我在后面“镜像下载加速方案”里给出了实测对比。

2.3 Linux 服务器内核和驱动的一个老坑

如果你在 Linux 服务器上装 Docker,有一个只在特定情况下出现、但出现就很扎心的问题:老内核和旧存储驱动不兼容。

Docker 的存储驱动用的是 OverlayFS,这玩意儿在内核 4.0 以上才能用。CentOS 7 默认内核是 3.10,Docker 会退回到旧的 devicemapper 驱动,而 devicemapper 有个毛病:磁盘空间会快速耗尽,容器一多就报no space left on device

解决办法:要么升级内核到 4.x 以上,要么在 Docker 配置文件/etc/docker/daemon.json里指定存储驱动:

{ "storage-driver": "overlay2" }

改完重启 Docker:

sudo systemctl restart docker

我在早期的 CentOS 7 服务器上就被这个坑折磨过两天。容器跑着跑着突然“磁盘满了”,实际df -h看根分区还有几十 G 空闲——就是这个存储驱动的锅。如果你也遇到这种诡异现象,查一下存储驱动准没错。

3. 三个核心概念和一组高频命令——够你应付 90% 的日常操作

3.1 镜像、容器、仓库,用搬家来理解

网上讲 Docker 概念的教程很多,但大多数讲得跟论文似的,越看越糊涂。我用搬家来类比,你一分钟就能记住。

  • 镜像(Image):就是一个“封装好的搬家箱子”。箱子里面装的不是你具体搬家的那一天有多少东西,而是一整套:操作系统的基础环境、软件程序的代码、依赖库、配置文件、启动命令。你把箱子拿过来,on,打开就是一个能跑的 nginx。箱子本身是只读的,你改不动它,改完只能重新打包一个新箱子。
  • 容器(Container):就是“箱子被打开运行起来”的那个瞬间。同一个镜像可以打开很多次,跑起来的每个实例相互隔离、互不影响。比如你用同一个 nginx 镜像起两个容器,一个跑 80 端口,一个跑 8080 端口,它们互不干扰。你对容器做的任何修改(比如改了配置文件),只对这个容器生效,不会污染镜像。
  • 仓库(Repository):就是“存放箱子的仓库”。你在本地构建好镜像,推到仓库里存着,换台机器从仓库拉下来就能用。Docker Hub 是最大的公共仓库,企业一般搭私有仓库。

命令层面,高频操作只有这几个:

# 拉取一个镜像到本地 docker pull nginx # 查看本地有哪些镜像 docker images # 基于镜像运行一个容器 docker run -d --name my-nginx -p 8080:80 nginx # 查看正在运行的容器 docker ps docker ps -a # 包括已停止的 # 进入容器内部执行命令 docker exec -it my-nginx bash # 查看容器日志 docker logs -f my-nginx # 停止、启动、删除容器 docker stop my-nginx docker start my-nginx docker rm my-nginx # 删除镜像 docker rmi nginx

这些命令不是背下来的,是你实际操作两遍就自然记住了。特别是docker run -d -p --name这个组合,后面做 nginx 反代的时候天天用,跑几次想忘都忘不掉。

3.2 端口映射是怎么回事

刚接触 Docker,最难理解的一个概念就是端口映射。你可能会有疑问:我明明在容器里启动了 nginx,默认监听 80 端口,为什么宿主机上访问不到?

原因其实一句话:容器在 Docker 的虚拟网络里,有自己的 IP(比如 172.17.0.2),宿主机访问不到容器的 IP,也访问不到容器内部的端口。你需要把宿主机的某个端口“映射”到容器的 80 端口上,这样访问宿主机的 8080,就等于访问容器的 80。

所以这条命令:

docker run -d --name web -p 8080:80 nginx

的意思是:把宿主机的 8080 端口,转发到容器的 80 端口。访问http://宿主机IP:8080,就等价于访问容器里的http://172.17.0.2:80

再理解一下-p 80:80:如果宿主机的 80 端口空闲,你可以直接映射成-p 80:80,这样访问http://宿主机IP就能直接看到 nginx 页面,不用加端口号。生产环境一般就是这么用的。

3.3 挂载目录:让配置“留”下来

容器默认是临时的。你docker rm删掉容器,里面改过的配置、产生的数据,全没了。这在生产环境是不可接受的。解决办法是“挂载(mount)”——把宿主机的某个目录直接“映射”到容器里,容器写文件,实际写的是宿主机的目录。

docker run -d --name nginx \ -p 80:80 \ -v /home/user/nginx/html:/usr/share/nginx/html \ -v /home/user/nginx/conf:/etc/nginx/conf.d \ nginx

这条命令的作用是:把宿主机的/home/user/nginx/html目录映射为容器的/usr/share/nginx/html(nginx 存放网页文件的目录),把宿主机/home/user/nginx/conf映射为容器的/etc/nginx/conf.d(nginx 读取额外配置的目录)。

这样配置文件的修改就简单了:在宿主机上编辑文件,容器里同步生效,不用进容器敲 vim。而且即使容器删了重建,配置和数据还在宿主机上,不会丢。

我觉得挂载目录这个功能,是 Docker 真正“生产力”的来源之一。没有挂载,容器就是个玩具;有了挂载,容器才能承载真实业务。

4. 第一次正式实战:用 Docker 跑一个 nginx 容器

4.1 写一个最简单的 HTML 页面作为验证

光说不练假把式。我们从零开始,完整走一遍“用 Docker 部署一个 nginx 并访问到它”的过程。

先在宿主机创建一个目录,放一个简单的 HTML 文件:

mkdir -p ~/nginx-demo/html cd ~/nginx-demo/html vim index.html

内容随意,比如:

<!DOCTYPE html> <html> <head> <title>Docker Nginx Demo</title> </head> <body> <h1>Hello from Docker Nginx!</h1> </body> </html>

然后基于 nginx 镜像启动容器,把刚才的目录挂载进去,并映射端口:

docker run -d \ --name nginx-demo \ -p 8080:80 \ -v ~/nginx-demo/html:/usr/share/nginx/html \ nginx

这里我故意用 8080 来映射,避开 80 端口可能的冲突,也顺便演示端口映射。

然后浏览器访问http://localhost:8080(如果你在服务器上操作,访问http://服务器IP:8080;如果 Windows 下 Docker Desktop,访问http://localhost:8080即可)。能看到 “Hello from Docker Nginx!” 就成功了。

这时候有几个常用命令可以验证一下:

# 看容器状态 docker ps # 看容器实时日志(Ctrl+C 退出) docker logs -f nginx-demo # 进入容器内部 docker exec -it nginx-demo bash # 在容器里看进程和配置文件(你已经进入容器了) ps aux | grep nginx ls /etc/nginx/

我特别建议你“进容器内部”这一步多待一会儿,把 nginx 的配置目录结构翻一遍。见过真面目之后,很多概念自然就通了:比如/etc/nginx/nginx.conf是主配置文件,/etc/nginx/conf.d/是放自定义配置的地方,/usr/share/nginx/html是放网页文件的地方。

4.2 端口冲突和镜像老版本这两个小问题

第一次跑容器,十有八九会遇到端口冲突。

错误提示大概长这样:

docker: Error response from daemon: driver failed programming external connectivity on endpoint nginx-demo: Bind for 0.0.0.0:80 failed: port is already allocated.

意思是宿主机的 80 端口已经被占了。怎么排查?

# 看谁占了宿主机 80 端口 sudo lsof -i :80 # 或者 netstat -tunlp | grep 80

查到占用进程后,两种处理:要么停掉占用的服务,要么把映射端口改成别的,比如-p 8081:80

另一个容易被忽略的是镜像版本问题。默认docker pull nginx拉取的是 latest 标签,这是官方 Dockerfile 里定义的最新稳定版。但最新版在特定机器上可能因为系统 libc 版本或架构不同,启动时报错。稳妥做法是指定版本:

docker pull nginx:1.27-alpine docker run -d --name nginx-demo -p 8080:80 nginx:1.27-alpine

alpine这个标签特别推荐:它基于 Alpine Linux,体积只有十几 MB,比标准版小了一个数量级,日常用完全够了。生产环境里我基本都用 alpine 变种,省磁盘、拉取快、攻击面小,缺点是对某些依赖第三方 .so 库的应用可能装起来麻烦,nginx 完全没问题。

4.3 为什么容器退出后像“消失”了一样

很多人第一次用 Docker 都会有这么一个困惑:我docker run起了一个容器,然后 Ctrl+C 退出或 Ctrl+Q 退出,怎么再找也找不到了?

比如有人这么操作:

docker run --rm -it nginx bash

然后输入exit,容器就被删除了——因为--rm参数的意思是“容器退出时自动删除”。这本身没问题,但新手容易产生误解,以为容器丢了。

更常见的场景是,容器因为错误退出了。比如里面启动的进程崩溃,容器停止运行。这时候docker ps看不到它,要用docker ps -a才能看到。排查死掉容器的方法:

# 看所有容器(包括已停止的) docker ps -a # 看容器的日志,找崩溃原因 docker logs 容器ID或名字

我见过太多人一遇到容器退出的情况,第一反应就是删掉重建,从不看日志。其实绝大多数问题,docker logs里都有答案。养成“先看日志、再下结论”的习惯,能少走很多弯路。

顺便再说一个关键认知:容器的生命周期和它里面跑的进程是绑定的。如果容器里跑的是 nginx 这种前台进程,nginx 活着容器就活着;nginx 挂了容器就停。所以 Docker 里跑服务,必须保证是前台进程,不能像在系统里那样service nginx start然后后台启动就完事。这也是 nginx 官方镜像的默认 CMD 是nginx -g "daemon off;"的原因——强制前台运行,让容器保持存活。理解了这个原理,你就知道为什么自定义 Dockerfile 时最后一条命令通常不能加&或者写一个会直接退出的脚本。

5. 反向代理的本质与配置:从单站点到多站点转发

5.1 反向代理到底是什么

这一步是整篇博文的核心。部署 nginx 从来不只是为了让你敲个 IP 访问个静态页面,真正的日常用途之一是反向代理。

概念先搞明白:正向代理是你主动去配置,通过代理服务器访问外网资源,代理服务器代替你请求外部网站,然后在把响应返回给你。而反向代理是在服务器端配置的,客户端不知道你的请求实际上被转发到了哪台后端机器,最后返回结果给客户端。

听起来有点绕,画个实际场景就秒懂。

假设你有一个域名www.example.com,服务器上跑着两个应用:

  • 一个 Java 后端服务,监听 8081 端口
  • 一个 Python 后端服务,监听 8082 端口

你希望用户访问http://www.example.com/api/java/的时候,请求被转发到 8081;访问http://www.example.com/api/python/的时候,转发到 8082。这时候 nginx 就充当“调度员 + 门卫”的角色,根据路径把请求分流到对应的后端。

用户并不知道也不关心 8081、8082 的存在,他们只知道你提供了一个统一的入口www.example.com。这就是反向代理。

5.2 nginx 配置文件结构速览

nginx 的配置,核心是搞清楚两个文件的关系:

  • /etc/nginx/nginx.conf:主配置文件,定义全局行为,比如运行用户、进程数、事件模型,以及http块。
  • /etc/nginx/conf.d/*.conf:额外配置,会被主配置里的include /etc/nginx/conf.d/*.conf;语句自动加载。

实践中的建议:别在主配置文件里堆配置,每个站点/服务建一个独立的 conf 文件,放在 conf.d 目录,结构清晰,便于管理。这也正是 Docker 挂载目录的用武之地——宿主机的conf目录挂载成容器里的/etc/nginx/conf.d,以后新增反代配置,只需要在宿主机新建一个.conf文件,重启 nginx 即可。

一个最简反向代理配置长这样:

server { listen 80; server_name www.example.com; location / { proxy_pass http://192.168.1.100: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; } }

逐行解释一下这段配置背后的逻辑:

  • server块:定义一个虚拟主机。listen 80监听 80 端口,server_name指定域名。如果不写server_name,那这个 server 块匹配所有请求。
  • location /:匹配所有以/开头的请求(也就是所有请求)。
  • proxy_pass http://192.168.1.100:8080;:核心指令,把匹配到的请求转发到这个地址。
  • 三个proxy_set_header:把客户端真实 IP、请求头等信息传给后端。不设置的话,后端拿到的 IP 全是 nginx 所在机器的 IP,客户端真实 IP 就丢了。很多后端做日志分析、人机校验的应用,都是依赖X-Real-IP来获取真实 IP 的,这个头必须带上。

5.3 实战:Docker 里两台 nginx 互做反向代理

光看配置不落地,等于白学。我们来一个完全基于 Docker 的实战:跑两个 nginx 容器,然后用第三个 nginx 容器做反向代理,把请求转发到前两个。

第一步:起两个“陆后”服务,模拟不同的后端应用

# 第一个“后端”容器 docker run -d --name app1 \ -v ~/nginx-demo/app1:/usr/share/nginx/html \ nginx:1.27-alpine # 第二个“后端”容器 docker run -d --name app2 \ -v ~/nginx-demo/app2:/usr/share/nginx/html \ nginx:1.27-alpine

分别在这两个 html 目录里放个不同的 index.html,内容分别写“This is App 1”和“This is App 2”。

注意这里我没有映射端口——因为这两个容器不需要对宿主机暴露,它们只在内网被反代容器访问。这演示了 Docker 网络的一个优势:容器间可以直接用容器名互相访问,不需要走宿主机端口。

第二步:起反代容器,把两个端口暴露出去

先写一个反代配置文件proxy.conf,放在宿主机~/nginx-demo/proxy/目录下:

upstream app_cluster { server app1:80; server app2:80; } server { listen 80; location / { proxy_pass http://app_cluster; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

然后启动反代容器:

docker run -d --name nginx-proxy \ -p 80:80 \ -v ~/nginx-demo/proxy:/etc/nginx/conf.d:ro \ --link app1:app1 \ --link app2:app2 \ nginx:1.27-alpine

重点来了:这里如果不加--link或者不放到同一个自定义网络里,反代容器里是解析不了app1app2这两个主机名的,proxy_pass http://app1:80会直接报 “host not found in upstream”。

Docker 有个默认网络叫 bridge,容器默认都连在这个网络上,但 bridge 网络默认不做 DNS 解析。容器间通信有两种推荐方式:

  1. 老式:用--link参数,简单粗暴,但已经过时了,官方不推荐新项目用。
  2. 推荐:创建一个自定义 bridge 网络,把这个网络里所有容器加入,Docker 自动做 DNS 解析。

操作方式:

docker network create my-net docker run -d --name app1 --network my-net \ -v ~/nginx-demo/app1:/usr/share/nginx/html \ nginx:1.27-alpine docker run -d --name app2 --network my-net \ -v ~/nginx-demo/app2:/usr/share/nginx/html \ nginx:1.27-alpine docker run -d --name nginx-proxy --network my-net \ -p 80:80 \ -v ~/nginx-demo/proxy:/etc/nginx/conf.d:ro \ nginx:1.27-alpine

这样容器间通过容器名解析,稳定可靠。浏览器访问http://localhost:80,多刷新几次,你会看到 App1 和 App2 交替出现——这就是upstream的轮询负载均衡。

5.4 多域名反向代理:一个容器转发到 N 个后端

上面演示的是“同一路径、多台后端”的负载均衡,下面演示更常见的“不同域名 / 不同路径,转发到不同后端”。

比如你有两个服务:

  • blog.example.com→ 转发到一个静态博客
  • api.example.com→ 转发到一个后端接口服务

判断不同站点,靠的就是server_name。在同一个反代容器里,可以配置多个server块:

# blog.conf server { listen 80; server_name blog.example.com; location / { proxy_pass http://blog_backend:80; } } # api.conf server { listen 80; server_name api.example.com; location / { proxy_pass http://api_backend:8080; } }

这里有个关键细节:同一个容器里,多个 server 块监听同一个 80 端口时不冲突,因为 nginx 根据Host请求头(也就是用户访问的域名)来路由。请求头里带的 Host 是blog.example.com,就走第一个 server 块;Host 是api.example.com,就走第二个。

这就是反向代理的“调度员 + 门卫”核心理念的进阶版:一个入口,N 个出口,全凭 Host 头来分流。

我可以实际告诉你一个经验:proxy_passlocation搭配时,是否保留路径前缀是个高频坑。比如:

# 访问 /api/xxx → 转发到 http://backend:8080/api/xxx location /api/ { proxy_pass http://backend:8080; } # 访问 /api/xxx → 转发到 http://backend:8080/xxx(注意路径前缀被去掉了) location /api/ { proxy_pass http://backend:8080/; # 带了这个斜杠 }

第二种写法,/api/前缀会被“吃掉”,后端收到的是/xxx。如果你的后端路由是基于/api前缀的,这样写会把接口全部 404 掉。但凡换了反代地址,一定要先确认路径映射关系,这个坑我踩过不止一次。

5.5 重启和验证配置的正确姿势

nginx 配置改完,不需要重启整个容器,发送重载信号即可:

# 在宿主机执行,进入容器 docker exec nginx-proxy nginx -t

nginx -t测试配置语法是否正确,然后重载:

docker exec nginx-proxy nginx -s reload

nginx -t这一步绝对不能省。我见过太多人改完配置直接 reload,语法错了 nginx 直接退出,服务全挂。尤其是我这种习惯“批量改配置”的人,改三四个文件后一起reload,一个文件少个分号,整个 nginx 全部挂掉,回滚的时间比谨慎测试的时间长十倍。

更稳妥的做法是:在宿主机用docker cp把配置文件复制进容器,或者直接在宿主机编辑(用了挂载目录就不需要复制),然后nginx -t验证无误后nginx -s reload

5.6 反向代理故障排查三板斧

反代配置不复杂,但真出问题的时候,新手容易一头雾水。分享三个排查技巧,按顺序执行,九成问题都能定位。

第一板斧:看 nginx 日志。错误日志在容器的/var/log/nginx/error.log,或者直接用docker logs nginx-proxy看容器 stdout 输出。最常见的报错有:

  • connnect() failed (111: Connection refused):后端服务没启动,或者端口不对
  • host not found in upstream "app1":容器间 DNS 解析不了,检查网络是否同一个
  • worker_connections are not enough:连接数上限被撑爆,调配置

第二板斧:在宿主机和后端容器上双向验证连通性。在反代容器里:

docker exec -it nginx-proxy sh # 容器里如果有 curl 或 wget 就直接请求后端,没有就安装/换基础镜像 curl http://app1:80 curl http://app2:80

如果这里能通,说明容器间网络没问题;如果连不通,那就不是 nginx 配置的问题,是 Docker 网络的问题。

第三板斧:确认后端确实返回了预期内容。有时候反代配置没问题,但页面报 502 或 504。502 通常是后端连不上,504 通常是后端起响应了但超时。可以请求后端实际地址(先直接映射个端口验证),看后端口到底通不通。

6. 后续怎么进阶:镜像、Compose 和日常维护的几条建议

6.1 自己写一个 Dockerfile 才是入门的真正分界线

能流畅地玩docker rundocker ps、配置 nginx 反代,你已经超过很多“半吊子”了。但要说真正入门了,我建议动手写一个自己的 Dockerfile——相比直接拉现成镜像,自己写 Dockerfile 是理解镜像构建原理的关键一步。

为什么这么重要?因为直接从 Docker Hub 拉镜像,你不关心镜像里面怎么搭的;一旦写 Dockerfile,你就被迫思考:基础镜像选什么、依赖怎么装、文件怎么拷、启动命令怎么写、环境变量怎么传。

写一个最简单的例子:部署一个静态站点,用 nginx。

# 用官方 nginx 镜像做基础 FROM nginx:1.27-alpine # 把自己的网页文件复制过去 COPY index.html /usr/share/nginx/html/index.html # 声明端口 EXPOSE 80 # 默认命令 CMD ["nginx", "-g", "daemon off;"]

构建和使用:

docker build -t my-static-site:1.0 . docker run -d --name static-site -p 9090:80 my-static-site:1.0

这个 Dockerfile 简单到看一眼就懂,但它背后的逻辑不简单:镜像是一层一层叠加的。FROM拉取的 nginx 镜像是一层,COPY命令又叠一层新的。每一层都是只读的,构建时会做缓存——如果你改了index.html重新构建,只有COPY这一层会重新执行,前面的层直接走缓存,构建速度快到起飞。这个机制是理解 Docker 镜像管理和优化 Dockerfile 的核心,值得专门花时间体会。

进阶一点的 Dockerfile 会涉及多阶段构建、环境变量、健康检查探针,这些后面可以单独开一篇聊。

6.2 镜像下载加速方案对比

国内环境拉取 Docker Hub 镜像,速度不稳定,经常几十 KB/s,一个几百 MB 的镜像能下半小时。配置镜像加速几乎是国内用户的必经之路。

镜像加速的原理不复杂:把 Docker 默认的镜像源替换成一个国内可以较快访问的镜像仓库地址。Docker 支持在/etc/docker/daemon.json里配置registry-mirrors,列出多个镜像源地址,拉取镜像时 Docker 会按顺序尝试。

实测对比(基于我自己的网络环境,你自己测试可能不同):

镜像源速度体验稳定性是否需要登录
Docker Hub 官方源慢或不稳定不稳定不需要
阿里云容器镜像服务稳定需要实名认证,每个账号有专属加速地址
中科大镜像较快依赖网络环境不需要
Docker 官方中国镜像较快时有波动不需要

配置方法如下,编辑/etc/docker/daemon.json(没有就新建):

{ "registry-mirrors": [ "https://your-user-id.mirror.aliyuncs.com", "https://docker.mirrors.ustc.edu.cn" ] }

然后重启 Docker:

sudo systemctl restart docker

配置完可以用docker info查看是否生效,输出里会有一行Registry Mirrors:,列出你配置的地址。

6.3 Docker Compose:多容器服务的脚手架

单容器玩熟之后,很快会遇到多容器协作的场景:一个 web 应用,前面 nginx 反代,后面一个 MySQL、一个 Redis,可能还有后端应用。手动一个个docker run也能跑,但每次都要回忆参数顺序,还容易搞混。

Docker Compose 就是来解决这个问题的:把多个容器的启动参数写到一个 YAML 文件里,一条命令全部启动,一条命令全部停止。

拿我们前面那个“两台后端 + 一台反代”的例子来说,用 Compose 写是这样的:

version: "3.8" services: app1: image: nginx:1.27-alpine volumes: - ~/nginx-demo/app1:/usr/share/nginx/html app2: image: nginx:1.27-alpine volumes: - ~/nginx-demo/app2:/usr/share/nginx/html nginx-proxy: image: nginx:1.27-alpine ports: - "80:80" volumes: - ~/nginx-demo/proxy:/etc/nginx/conf.d:ro depends_on: - app1 - app2

启动:

docker compose up -d

就这一条命令,compose 会帮你在一个独立的网络上创建网络、启动容器、配置容器间 DNS 解析(所以app1app2这些主机名在反代容器里直接可用)。停止:

docker compose down

depends_on的作用是指定启动顺序——先启动 app1 和 app2,再启动 nginx-proxy,避免反代容器启动时后端还没就绪导致 DNS 解析失败。这个字段在 Compose 里非常实用。

顺带说一句,Compose 文件里的语法和docker run参数对应关系很好记:ports对应-pvolumes对应-venvironment对应-edepends_on对应手动控制启动顺序。熟练了一个,另一个基本不用学。

6.4 日常维护利器:日志清理和容器资源限制

容器跑久了,你会发现磁盘空间悄无声息地变小。这主要是因为:

  1. 容器日志无限增长(默认情况下 Docker 不会自动切割日志)
  2. 构建镜像产生的悬空镜像(dangling images)堆积
  3. 停止的容器和旧的构建缓存

一条命令处理悬空镜像和停止容器:

docker system prune -af

该命令会删除所有停止的容器、所有没有被使用的网络、所有悬空镜像和构建缓存。注意-a的意思是“删除所有”,如果你有不在运行的容器但还想留着,别加-a,只执行docker system prune我一般会加--volumes把没用的匿名卷也清掉:

docker system prune -af --volumes

另一个容易忽略的问题是日志增长。给 nginx 反代容器加日志切割配置,在启动时指定:

docker run -d --name nginx-proxy \ --log-driver json-file \ --log-opt max-size=10m \ --log-opt max-file=3 \ nginx:1.27-alpine

这样每个日志文件最多 10MB,最多保留 3 个,老日志自动轮转,不会无限膨胀。生产环境的容器都应该加上这个配置,不然后期撑爆磁盘只是时间问题。

写在最后的一些零碎经验

这篇实战走下来,安装、核心概念、跑容器、反向代理、进阶方向基本都覆盖了。最后分享几条纯个人的体会。

第一,学 Docker 别死记命令,要“用场景带动命令”。你折腾一次“容器间互相访问时用容器名互相 ping”,比背十遍“docker network create”有用得多。真到用的时候,自然想得起来这条命令该用在哪。

第二,nginx 反代配置的原则是“小步快跑,持续验证”。改一个块就nginx -t一次,验证有效后再改下一个。别学我一开始那样一口气堆完所有配置再一次性 reload,出了问题都不知道是哪一段语法挂了。

第三,容器化部署不等于无忧运维。Docker 和 nginx 本身不解决高可用问题——容器挂了就是挂了,nginx 反代断了就是断了。要真正高可用,得搭配健康检查、重启策略、编排系统(比如 K8s)这些基础设施。初学者阶段,先把单机这一套玩明白,后面再演进到集群是完全自然的路径。

第四,也是最重要的一条:实践遇到的报错是最宝贵的学习材料。我见过很多人在报错面前选择直接重装,一次两次可以,但每次都绕过问题,就永远学不会排查思维。凡是docker logs能看到的错误,都有解;凡是nginx -t报的语法错误,都在配置里能改。忍住重装的手,一步步拆解问题,你会发现自己成长得比想象中快。

把这些话记在心里,配合上面的实战步骤走一遍,你就已经具备独立用 Docker 部署 nginx 反向代理的基础能力了。接下来想深挖哪个方向——Docker Compose 编排、Dockerfile 优化、还是 nginx 更多高级功能,都是水到渠成的事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询