Ubuntu 22.04 下 Docker 部署 Nginx 的完整实践指南
1. 项目概述:为什么在 Ubuntu 22.04 上用 Docker 跑 Nginx 不是“多此一举”,而是生产级落地的起点
你刚配好一台全新的 Ubuntu 22.04 LTS 服务器,想快速起一个静态网站或 API 网关,第一反应可能是sudo apt install nginx—— 这没错,但如果你接下来要部署第二个服务(比如一个 Python Flask 后端),第三个(比如一个 Node.js 管理后台),第四个(比如一个 Redis 缓存代理层),问题就来了:所有服务共用同一套系统级配置、日志路径、用户权限、SSL 证书管理方式;Nginx 的sites-available目录里堆满不同项目的配置片段,一不小心 reload 就全站 502;更别说开发环境和测试环境的 Nginx 版本不一致,导致上线前才发现proxy_buffering off在 1.18 里有效,在 1.22 里行为变了。这时候,“用 Docker 跑 Nginx”就不是炫技,而是把“Web 服务交付”这件事,从“手工搭积木”升级为“标准化封装+原子化部署”。它解决的不是“能不能跑起来”,而是“能不能稳定、可复现、可审计、可回滚地跑起来”。核心关键词Nginx、Docker、Ubuntu 22.04、docker run、docker pull,每一个都指向一个确定的技术动作:docker pull是获取可信镜像的入口,docker run是启动隔离实例的指令,Ubuntu 22.04 是当前最主流的 LTS 基础平台,而 Nginx 是那个被封装进容器、对外提供 HTTP 流量调度能力的“门面担当”。这个项目适合三类人:刚从传统运维转向云原生的新手,需要快速验证架构想法的开发者,以及正在为团队制定标准化部署规范的 SRE。它不教你怎么写正则重写规则,也不讲 event 模型底层原理,而是聚焦在“从零到一,让一个 Nginx 容器在你的 Ubuntu 22.04 机器上稳稳当当地监听 80 端口,并能被外部访问”这个最小闭环上。后面所有高级玩法——反向代理、HTTPS 终结、负载均衡、与 Compose 协同、对接 CI/CD——都建立在这个闭环之上。我带过十几支小团队做容器化迁移,90% 的人卡在第一步:docker: command not found或cannot connect to the docker daemon。所以,我们不跳步,不假设,从apt update开始,每一步都告诉你为什么这么写、不这么写会掉进什么坑。
2. 环境准备与基础依赖安装:Ubuntu 22.04 上 Docker 的“正确打开方式”
2.1 验证系统基础状态与网络连通性
在敲任何docker命令之前,先确认你的 Ubuntu 22.04 系统本身是干净、可用的。这不是形式主义,而是很多“Unknown shorthand flag: 'd' in -d”错误的根源——它往往不是 Docker 命令写错了,而是 Docker 根本没装上,或者 shell 找不到docker这个二进制文件。打开终端,执行:
lsb_release -a你应该看到类似这样的输出:
Distributor ID: Ubuntu Description: Ubuntu 22.04.4 LTS Release: 22.04 Codename: jammy这确认了你确实在 Ubuntu 22.04 环境中。接着检查网络:
ping -c 3 8.8.8.8如果超时,说明网络不通,后续docker pull必然失败。此时不要急着装 Docker,先解决网络问题。常见原因包括:虚拟机未桥接网络、云服务器安全组未放行 ICMP、公司内网 DNS 解析异常。我见过最离谱的一次,是某客户在阿里云 ECS 上,因为默认安全组只开了 22 和 80,没开 ICMP,ping不通,他以为是系统坏了,重装了三次系统。所以,ping是第一道过滤网。
提示:
ping成功后,再试curl -I https://docker.com,确认 HTTPS 访问也正常。Ubuntu 22.04 默认使用systemd-resolved,有时会与某些 DNS 配置冲突,如果curl失败而ping成功,大概率是 DNS 问题,临时解决办法是sudo systemctl restart systemd-resolved。
2.2 安装 Docker Engine:绕过apt install docker.io的陷阱
Ubuntu 官方仓库里的docker.io包,版本通常滞后于上游 Docker 官方发布。以 Ubuntu 22.04 为例,其docker.io默认版本是 20.10.x,而 Docker 官方稳定版已是 24.0.x。版本差异看似不大,实则影响深远:旧版 Docker 对 cgroups v2 的支持不完善,可能导致容器在高负载下 OOM 被杀;新版docker run的--cpus、--memory参数在旧版中行为不一致;更重要的是,docker.io包的 systemd 服务名是docker.io,而官方包是docker,混用会导致systemctl start docker报错“找不到服务”。因此,我们必须走 Docker 官方推荐的安装路径。
第一步:卸载可能存在的旧版本(如果之前装过):
sudo apt-get remove docker docker-engine docker.io containerd runc第二步:安装必要的系统依赖:
sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg lsb-release这里ca-certificates是为了 HTTPS 证书校验,curl是下载工具,gnupg用于密钥验证,lsb-release用于识别发行版代号(jammy)。缺一不可,尤其是ca-certificates,没有它,后续添加 Docker GPG 密钥会失败。
第三步:添加 Docker 的官方 GPG 密钥:
sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg注意:gpg --dearmor是将 ASCII-armored 密钥转为二进制格式,这是 Ubuntu 22.04 APT 的新要求。老教程里直接apt-key add已被弃用,强行使用会报错“Command 'apt-key' is deprecated”。
第四步:添加 Docker 的稳定版软件源:
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null$(dpkg --print-architecture)输出amd64或arm64,$(lsb_release -cs)输出jammy,这两者组合确保源地址精准匹配你的系统。手动写死jammy是可以的,但用命令替换更健壮,避免手误。
第五步:更新源并安装 Docker Engine:
sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugindocker-ce是社区版引擎,docker-ce-cli是命令行工具,containerd.io是底层容器运行时,docker-buildx-plugin和docker-compose-plugin是现代构建和编排必需的插件。全部装上,一步到位。
2.3 启动并验证 Docker 服务:cannot connect to the docker daemon的根治方案
安装完成后,Docker 服务并不会自动启动。必须手动启用并启动:
sudo systemctl enable docker sudo systemctl start dockerenable是设置开机自启,start是立即启动。然后验证:
sudo docker version你应该看到 Client 和 Server 的版本号都显示出来。如果只显示 Client,Server 部分为空或报错,说明dockerd进程没起来。此时执行:
sudo systemctl status docker查看详细状态。最常见的原因是dockerd启动失败,日志里有failed to start daemon: failed to dial ...。这通常指向两个方向:一是/var/run/docker.sock文件权限问题,二是 cgroups 配置冲突。Ubuntu 22.04 默认使用 cgroups v2,而某些旧内核或虚拟化环境可能不兼容。解决方案是强制 Docker 使用 cgroups v1:
echo '{ "exec-opts": ["native.cgroupdriver=cgroupfs"] }' | sudo tee /etc/docker/daemon.json sudo systemctl restart dockercgroupfs是 cgroups v1 的驱动名。改完配置必须restart,reload不生效。再次sudo docker version,Server 应该正常显示。
注意:
sudo docker run hello-world是终极验证。它会自动pull镜像并运行一个测试容器。如果成功,你会看到 “Hello from Docker!” 的欢迎信息。如果失败,错误信息就是你下一步排查的唯一线索,不要跳过这一步。
2.4 将当前用户加入 docker 组:告别无处不在的sudo
每次docker命令都加sudo,不仅麻烦,更存在安全隐患:sudo docker拥有等同于 root 的权限,一个恶意镜像就能完全控制你的主机。Docker 官方文档明确建议,将信任的用户加入docker组来规避sudo。执行:
sudo usermod -aG docker $USER-aG表示“追加到组”,$USER是当前用户名。执行完后,必须退出当前终端并重新登录,或者运行newgrp docker刷新组权限。否则docker ps仍会报错“permission denied”。验证方法:
docker ps -a如果返回一个空列表(没有报错),说明成功。这是后续所有操作流畅性的基石。我见过太多人卡在这一步,反复执行usermod却不重启 shell,然后怀疑是 Docker 安装出了问题,白白浪费数小时。
3. Nginx 镜像拉取与本地验证:docker pull的底层逻辑与避坑指南
3.1 理解docker pull的工作原理:不只是“下载一个文件”
docker pull nginx这条命令背后,是一整套镜像分发与验证机制。它不是简单地从某个 URL 下载一个 tar 包,而是遵循 OCI(Open Container Initiative)标准,与远程 Registry(这里是 Docker Hub)进行 HTTPS 通信,完成以下步骤:首先,向https://registry.hub.docker.com/v2/发送认证请求(匿名用户也有 token);其次,查询nginx镜像的 manifest(清单文件),该文件描述了镜像由哪些 layer(层)组成,每个 layer 的 SHA256 摘要值是多少;最后,按需下载这些 layer,并在本地/var/lib/docker/overlay2/目录下以内容寻址的方式存储。docker pull的输出如Using default tag: latest、latest: Pulling from library/nginx,就是在告诉你它正在解析 manifest 并逐层拉取。
为什么强调这个?因为当你遇到dockers search 失败但是 i可以 docker pull这类矛盾现象时,就能快速定位:search是查询 Registry 的索引服务,pull是下载镜像数据,两者走的不是同一个后端。search失败,很可能是 Docker Hub 的搜索 API 限流或临时不可用,而pull依然能成功,因为镜像数据本身是缓存且高可用的。所以,不要迷信docker search,它只是辅助工具,docker pull才是核心动作。
3.2 选择正确的 Nginx 镜像标签:latest是毒药,alpine是双刃剑
Docker Hub 上的nginx官方镜像有多个标签(tag),最常见的是latest、1.25、1.25-alpine、mainline。新手最容易犯的错误,就是无脑docker pull nginx,结果拉到latest。latest标签在官方镜像中并不指向“最新稳定版”,而是“最新构建成功的版本”,它可能是mainline(主线开发版),包含实验性功能,稳定性未经充分验证。生产环境必须指定明确的语义化版本,如nginx:1.25.3。
另一个常见选择是nginx:alpine。Alpine Linux 是一个极简的发行版,基础镜像只有 ~5MB,而nginx:1.25.3的 Debian 基础镜像是 ~140MB。体积小意味着启动快、传输快、攻击面小。但它也有硬伤:Alpine 使用musl libc而非glibc,某些依赖glibc动态库的第三方 Nginx 模块(如nginx-module-vts)无法在 Alpine 中编译运行;apk包管理器的生态远不如apt丰富,安装curl、vim等调试工具需要额外学习命令。对于纯静态文件服务或标准反向代理,alpine是绝佳选择;如果你计划在容器内编译模块或需要丰富的调试工具,debian基础镜像更稳妥。
我自己的实践是:开发和测试环境用nginx:1.25.3-alpine,追求轻量和速度;预发布和生产环境用nginx:1.25.3(Debian),确保最大兼容性。你可以这样拉取:
docker pull nginx:1.25.3-alpine docker pull nginx:1.25.3拉取完成后,用docker images查看本地镜像列表,确认REPOSITORY为nginx,TAG为你指定的版本,IMAGE ID是一串哈希值。
3.3 本地运行并验证 Nginx 容器:docker run的最小必要参数解析
现在,让我们用docker run启动第一个 Nginx 容器。最简命令是:
docker run -d -p 8080:80 --name my-nginx nginx:1.25.3-alpine拆解每个参数:
-d:detached mode,后台运行。这是生产部署的标配,容器启动后不占用当前终端。-p 8080:80:端口映射。左边8080是宿主机端口,右边80是容器内 Nginx 监听的端口。-p是--publish的缩写,-d是--detach的缩写。错误信息unknown shorthand flag: 'd' in -d的出现,99% 是因为docker命令本身没找到(环境变量 PATH 问题),而不是-d写错了。--name my-nginx:给容器指定一个易记的名字。如果不指定,Docker 会随机生成一个如clever_mccarthy的名字,不利于管理。nginx:1.25.3-alpine:镜像名和标签,告诉 Docker 用哪个镜像启动。
执行后,命令会立即返回一个长字符串(容器 ID),表示容器已启动。用docker ps查看运行中的容器,你应该能看到my-nginx在列表中,PORTS列显示0.0.0.0:8080->80/tcp。
验证是否成功:在宿主机上执行curl http://localhost:8080,应该返回 Nginx 的默认欢迎页面 HTML。如果返回curl: (7) Failed to connect to localhost port 8080: Connection refused,说明容器没起来或端口没映射对。此时docker logs my-nginx是第一排查手段,它会输出容器启动时的标准输出和错误日志。Nginx 容器启动失败,最常见的原因是配置文件语法错误,或nginx.conf中指定了不存在的路径。
实操心得:永远在
docker run后立刻执行docker ps和docker logs。我养成的习惯是,把这两条命令写成一个 alias:alias dcheck='docker ps && docker logs $(docker ps -q --filter name=my-nginx | head -n1)',一键检查。
4. 容器化 Nginx 的核心配置与持久化:从“能跑”到“能用”的关键跃迁
4.1 为什么不能直接修改容器内的配置文件?
当你docker exec -it my-nginx sh进入容器,发现/etc/nginx/nginx.conf可以编辑,nginx -t测试也通过,nginx -s reload也能生效。但这是一个危险的幻觉。Docker 容器的设计哲学是“不可变基础设施”(Immutable Infrastructure)。容器一旦创建,其文件系统层(除了 volume)就不应被修改。原因有三:一是容器重启后,所有在容器内做的修改都会丢失,因为它们写在了可写层(upperdir),而重启会重建这个层;二是无法版本化管理配置变更,今天改了一行,明天忘了,上线就出问题;三是违背了“一次构建,处处运行”的原则,你的容器在测试环境能跑,在生产环境可能因配置微小差异而失败。
所以,正确的做法是:将 Nginx 配置文件作为外部文件挂载进容器。这需要用到docker run的-v(volume)参数。
4.2 创建并挂载自定义 Nginx 配置:一个可复用的模板
在宿主机上创建一个目录来存放 Nginx 配置:
mkdir -p ~/nginx-config/{conf.d,html}conf.d用于存放站点配置(如default.conf),html用于存放静态文件。然后,创建一个最简default.conf:
cat > ~/nginx-config/conf.d/default.conf << 'EOF' server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } EOF这个配置和官方镜像默认的几乎一样,但它是你完全掌控的。接着,创建一个简单的index.html:
echo "<h1>Hello from Dockerized Nginx on Ubuntu 22.04!</h1>" > ~/nginx-config/html/index.html现在,用-v参数将这些目录挂载进容器:
docker run -d \ -p 8080:80 \ --name my-nginx-custom \ -v ~/nginx-config/conf.d:/etc/nginx/conf.d:ro \ -v ~/nginx-config/html:/usr/share/nginx/html:ro \ nginx:1.25.3-alpine关键点解析:
-v ~/nginx-config/conf.d:/etc/nginx/conf.d:ro:将宿主机的conf.d目录,只读(ro)挂载到容器的/etc/nginx/conf.d。只读是为了防止容器内进程意外修改配置。-v ~/nginx-config/html:/usr/share/nginx/html:ro:同理,挂载静态文件目录。:ro后缀至关重要。没有它,挂载是读写(rw)的,虽然方便,但破坏了配置的不可变性,且存在安全风险。
启动后,curl http://localhost:8080应该返回你自定义的 HTML。此时,所有配置变更都在宿主机上进行,修改default.conf后,只需docker exec my-nginx-custom nginx -s reload重载配置,无需重启容器。
4.3 日志持久化:让access.log和error.log不随容器消失
Nginx 容器默认将日志输出到stdout和stderr,Docker 会捕获它们,供docker logs查看。这很方便,但有两个问题:一是日志只保留在 Docker 的 JSON 文件中,容量有限,会轮转丢弃;二是无法用tail -f实时监控,也无法用logrotate进行归档。生产环境需要将日志写入宿主机文件系统。
官方 Nginx 镜像已经将access.log和error.log的路径配置为/var/log/nginx/access.log和/var/log/nginx/error.log。我们只需将这个目录挂载出去:
mkdir -p ~/nginx-config/logs docker run -d \ -p 8080:80 \ --name my-nginx-logs \ -v ~/nginx-config/conf.d:/etc/nginx/conf.d:ro \ -v ~/nginx-config/html:/usr/share/nginx/html:ro \ -v ~/nginx-config/logs:/var/log/nginx:rw \ nginx:1.25.3-alpine注意,这里日志目录用了:rw(读写),因为 Nginx 进程需要往里面写日志。挂载后,~/nginx-config/logs/下就会生成access.log和error.log。你可以用tail -f ~/nginx-config/logs/access.log实时观察访问记录。这比docker logs -f更灵活,也更符合 Linux 系统管理员的操作习惯。
注意:挂载日志目录后,
docker logs命令将不再输出日志内容,因为它现在被重定向到了文件。这是预期行为,不是错误。
4.4 SSL/TLS 配置:在容器中启用 HTTPS 的标准流程
要在容器中启用 HTTPS,你需要两样东西:一个 SSL 证书(.crt文件)和一个私钥(.key文件)。你可以用 Let's Encrypt 的certbot在宿主机上申请,或者用 OpenSSL 生成自签名证书用于测试。
生成自签名证书(仅测试用):
mkdir -p ~/nginx-config/ssl openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout ~/nginx-config/ssl/nginx.key \ -out ~/nginx-config/ssl/nginx.crt \ -subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=localhost"然后,修改default.conf,添加一个监听 443 端口的 server 块:
cat >> ~/nginx-config/conf.d/default.conf << 'EOF' server { listen 443 ssl; server_name localhost; ssl_certificate /etc/nginx/ssl/nginx.crt; ssl_certificate_key /etc/nginx/ssl/nginx.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { root /usr/share/nginx/html; index index.html index.htm; } } EOF最后,启动容器时,挂载 SSL 目录,并映射 443 端口:
docker run -d \ -p 8080:80 \ -p 8443:443 \ --name my-nginx-https \ -v ~/nginx-config/conf.d:/etc/nginx/conf.d:ro \ -v ~/nginx-config/html:/usr/share/nginx/html:ro \ -v ~/nginx-config/logs:/var/log/nginx:rw \ -v ~/nginx-config/ssl:/etc/nginx/ssl:ro \ nginx:1.25.3-alpine现在,curl -k https://localhost:8443(-k忽略证书验证)应该能访问到 HTTPS 页面。生产环境请务必使用由权威 CA 签发的证书,并将server_name设置为你的真实域名。
5. 生产就绪的关键配置与故障排查:从“玩具”到“生产系统”的最后一公里
5.1 资源限制与健康检查:防止 Nginx 容器吃光服务器资源
一个失控的 Nginx 容器,理论上可以 fork 出成百上千个 worker 进程,耗尽宿主机内存,导致整个系统卡死。Docker 提供了--memory和--cpus参数来硬性限制容器的资源使用。
例如,限制容器最多使用 512MB 内存和 1 个 CPU 核心:
docker run -d \ --memory="512m" \ --cpus="1.0" \ -p 8080:80 \ --name my-nginx-limited \ -v ~/nginx-config/conf.d:/etc/nginx/conf.d:ro \ -v ~/nginx-config/html:/usr/share/nginx/html:ro \ nginx:1.25.3-alpine--memory="512m"是绝对上限,超过即被 OOM Killer 杀死;--cpus="1.0"是 CPU 时间片的权重,1.0 表示最多占用一个完整核心的 100% 时间。这两个参数应该根据你的应用实际负载来设定。我的经验是:一个处理静态文件的 Nginx,256MB 内存 + 0.5 CPU 就绰绰有余;如果要做复杂的 Lua 脚本处理或大量 SSL 加解密,则需相应提高。
此外,为容器添加健康检查(Healthcheck),让 Docker 守护进程能主动探测 Nginx 是否真的“活着”,而不仅仅是进程在运行:
docker run -d \ --health-cmd="curl -f http://localhost:80 || exit 1" \ --health-interval=30s \ --health-timeout=3s \ --health-retries=3 \ --health-start-period=40s \ -p 8080:80 \ --name my-nginx-health \ -v ~/nginx-config/conf.d:/etc/nginx/conf.d:ro \ -v ~/nginx-config/html:/usr/share/nginx/html:ro \ nginx:1.25.3-alpine--health-cmd是执行的检测命令,curl -f会在 HTTP 状态码非 2xx/3xx 时返回非零退出码,触发失败;--health-interval是检测间隔;--health-timeout是单次检测超时时间;--health-retries是连续失败多少次才标记为 unhealthy;--health-start-period是容器启动后,等待多久才开始健康检查(给 Nginx 初始化留出时间)。配置后,docker ps的STATUS列会显示healthy或unhealthy,docker inspect my-nginx-health可以看到详细的健康状态历史。
5.2 常见错误速查表:那些让你抓狂的“小问题”的终极解法
| 错误现象 | 可能原因 | 排查与解决命令 | 我的实操心得 |
|---|---|---|---|
docker: command not found | Docker 未安装,或PATH未包含/usr/bin | which docker,echo $PATH | Ubuntu 22.04 桌面版有时PATH不包含/usr/bin,在~/.bashrc中添加export PATH="/usr/bin:$PATH" |
cannot connect to the docker daemon at unix:///var/run/docker.sock | dockerd服务未启动,或当前用户不在docker组 | sudo systemctl status docker,groups | 最快验证:sudo docker ps。如果sudo可以,说明只是用户组问题;如果sudo也不行,说明服务没起来 |
unknown shorthand flag: 'd' in -d | docker命令未找到,shell 解析-d时找不到主程序 | type docker,command -v docker | 这是典型的 PATH 问题,不是命令语法错误。不要去查docker run文档,先查docker命令在哪 |
Bind for 0.0.0.0:8080 failed: port is already allocated | 端口被其他进程占用 | sudo ss -tulpn | grep ':8080',sudo lsof -i :8080 | Ubuntu 22.04 自带的snapd有时会占用 8080,sudo snap disable <service>可以禁用 |
curl: (7) Failed to connect to localhost port 8080 | 容器未运行,或端口映射错误,或防火墙拦截 | docker ps,sudo ufw status | 如果docker ps看不到容器,docker logs <name>;如果能看到,curl http://127.0.0.1:8080(用 IP 而非 localhost)排除 DNS 问题 |
nginx: [emerg] open() "/etc/nginx/conf.d/default.conf" failed (13: Permission denied) | SELinux 或 AppArmor 限制,或挂载目录权限不足 | ls -ld ~/nginx-config/conf.d,sudo aa-status | Ubuntu 22.04 默认启用 AppArmor,sudo aa-disable /usr/bin/docker临时禁用(不推荐),更好的办法是chmod 755宿主机目录 |
docker run后容器立即退出(Exited (1)) | Nginx 启动失败,通常是配置语法错误 | docker logs <name>,docker run -it --rm nginx:alpine nginx -t | 先用--rm和-it模式运行,直接看到nginx -t的输出,比在后台容器里查日志快十倍 |
5.3 容器生命周期管理:启动、停止、更新、清理的标准化流程
一个生产环境的容器,绝不是docker run启动就完事了。你需要一套标准化的生命周期管理脚本。下面是一个我日常使用的nginx-manager.sh脚本框架:
#!/bin/bash # nginx-manager.sh NGINX_NAME="my-nginx-prod" NGINX_IMAGE="nginx:1.25.3-alpine" CONFIG_DIR="$HOME/nginx-config" start() { echo "Starting $NGINX_NAME..." docker run -d \ --name $NGINX_NAME \ --restart=unless-stopped \ --memory="256m" \ --cpus="0.5" \ --health-cmd="curl -f http://localhost:80 || exit 1" \ --health-interval=30s \ -p 80:80 \ -p 443:443 \ -v $CONFIG_DIR/conf.d:/etc/nginx/conf.d:ro \ -v $CONFIG_DIR/html:/usr/share/nginx/html:ro \ -v $CONFIG_DIR/logs:/var/log/nginx:rw \ -v $CONFIG_DIR/ssl:/etc/nginx/ssl:ro \ $NGINX_IMAGE } stop() { echo "Stopping $NGINX_NAME..." docker stop $NGINX_NAME } restart() { echo "Restarting $NGINX_NAME..." docker restart $NGINX_NAME } update() { echo "Updating $NGINX_NAME to $NGINX_IMAGE..." docker pull $NGINX_IMAGE docker stop $NGINX_NAME docker rm $NGINX_NAME start } logs() { docker logs -f $NGINX_NAME } status() { docker ps -f name=$NGINX_NAME } case "$1" in start) start ;; stop) stop ;; restart) restart ;; update) update ;; logs) logs ;; status) status ;; *) echo "Usage: $0 {start|stop|restart|update|logs|status}" ;; esac将它保存为nginx-manager.sh,chmod +x nginx-manager.sh,然后就可以用./nginx-manager.sh start启动,./nginx-manager.sh update更新镜像了。--restart=unless-stopped是关键,它确保容器在宿主机重启后能自动拉起,是生产环境的必备选项。
5.4 安全加固:最小权限原则在 Nginx 容器中的实践
最后,也是最重要的,是安全。一个暴露在公网的 Nginx 容器,就是服务器的第一道门。加固要点如下:
以非 root 用户运行:官方 Nginx 镜像默认以
nginx用户(UID 101)运行 worker 进程,这很好。但主进程(master)仍是 root,用于绑定 80/443 端口。你可以通过--user参数强制所有进程以非 root 用户运行,但这需要修改 Nginx 配置,将listen改为非特权端口(如 8080),然后用宿主机的iptables或nginx反向代理做端口转发。对于大多数场景,保持默认即可,因为容器本身就是一个隔离边界。禁用不必要的模块:官方镜像已经精简,但如果你自己编译,务必禁用
--without-http_autoindex_module、--without-http_geo_module等不常用的模块,减少攻击面。配置文件最小化:删除
conf.d中所有不用的.conf文件。一个default.conf文件,只保留你真正需要的server块。使用只读文件系统:在
docker run中添加--read-only参数,让容器的根文件系统变为只读。这能阻止绝大多数针对容器文件系统的攻击。当然,你需要同时用-v挂载/var/log/nginx和/tmp(如果 Nginx 需要临时文件)为读写。定期更新镜像:Nginx 和基础 OS 都会有安全漏洞。订阅 Docker Hub 的
nginx镜像更新通知,或用watchtower这样的工具自动更新容器。
我个人在生产环境中,一定会启用--read-only和--restart=unless-stopped,并