Ubuntu 22.04 下 Docker 部署 Nginx 的完整实践指南

发布时间:2026/6/23 18:25:40
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没开 ICMPping不通他以为是系统坏了重装了三次系统。所以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 Enginesudo 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 的版本号都显示出来。如果只显示 ClientServer 部分为空或报错说明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 v1echo { exec-opts: [native.cgroupdrivercgroupfs] } | sudo tee /etc/docker/daemon.json sudo systemctl restart dockercgroupfs是 cgroups v1 的驱动名。改完配置必须restartreload不生效。再次sudo docker versionServer 应该正常显示。注意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 包而是遵循 OCIOpen 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.3Debian确保最大兼容性。你可以这样拉取docker pull nginx:1.25.3-alpine docker pull nginx:1.25.3拉取完成后用docker images查看本地镜像列表确认REPOSITORY为nginxTAG为你指定的版本IMAGE ID是一串哈希值。3.3 本地运行并验证 Nginx 容器docker run的最小必要参数解析现在让我们用docker run启动第一个 Nginx 容器。最简命令是docker run -d -p 8080:80 --name my-nginx nginx:1.25.3-alpine拆解每个参数-ddetached 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。我养成的习惯是把这两条命令写成一个 aliasalias dcheckdocker ps docker logs $(docker ps -q --filter namemy-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的-vvolume参数。4.2 创建并挂载自定义 Nginx 配置一个可复用的模板在宿主机上创建一个目录来存放 Nginx 配置mkdir -p ~/nginx-config/{conf.d,html}conf.d用于存放站点配置如default.confhtml用于存放静态文件。然后创建一个最简default.confcat ~/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.htmlecho h1Hello 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和stderrDocker 会捕获它们供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文件。你可以用 Lets 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 /CCN/STBeijing/LBeijing/OMyOrg/CNlocalhost然后修改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 \ --memory512m \ --cpus1.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--memory512m是绝对上限超过即被 OOM Killer 杀死--cpus1.0是 CPU 时间片的权重1.0 表示最多占用一个完整核心的 100% 时间。这两个参数应该根据你的应用实际负载来设定。我的经验是一个处理静态文件的 Nginx256MB 内存 0.5 CPU 就绰绰有余如果要做复杂的 Lua 脚本处理或大量 SSL 加解密则需相应提高。此外为容器添加健康检查Healthcheck让 Docker 守护进程能主动探测 Nginx 是否真的“活着”而不仅仅是进程在运行docker run -d \ --health-cmdcurl -f http://localhost:80 || exit 1 \ --health-interval30s \ --health-timeout3s \ --health-retries3 \ --health-start-period40s \ -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或unhealthydocker inspect my-nginx-health可以看到详细的健康状态历史。5.2 常见错误速查表那些让你抓狂的“小问题”的终极解法错误现象可能原因排查与解决命令我的实操心得docker: command not foundDocker 未安装或PATH未包含/usr/binwhich docker,echo $PATHUbuntu 22.04 桌面版有时PATH不包含/usr/bin在~/.bashrc中添加export PATH/usr/bin:$PATHcannot connect to the docker daemon at unix:///var/run/docker.sockdockerd服务未启动或当前用户不在docker组sudo systemctl status docker,groups最快验证sudo docker ps。如果sudo可以说明只是用户组问题如果sudo也不行说明服务没起来unknown shorthand flag: d in -ddocker命令未找到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 :8080Ubuntu 22.04 自带的snapd有时会占用 8080sudo 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-statusUbuntu 22.04 默认启用 AppArmorsudo 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_NAMEmy-nginx-prod NGINX_IMAGEnginx:1.25.3-alpine CONFIG_DIR$HOME/nginx-config start() { echo Starting $NGINX_NAME... docker run -d \ --name $NGINX_NAME \ --restartunless-stopped \ --memory256m \ --cpus0.5 \ --health-cmdcurl -f http://localhost:80 || exit 1 \ --health-interval30s \ -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.shchmod x nginx-manager.sh然后就可以用./nginx-manager.sh start启动./nginx-manager.sh update更新镜像了。--restartunless-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和--restartunless-stopped并