Nginx 用 Docker 跑,镜像本身不用动,真正要定下来的是三件事:配置怎么挂、日志落到哪、容器和上游怎么互相找到。这三点定好了,后面加站点就是复制一个 conf 文件的事。
一、镜像和目录
用官方镜像,版本写死到小版本:
docker pull nginx:1.27
目录先建出来:
sudo mkdir -p /data/nginx/{conf.d,html,certs,logs}
| 宿主机目录 | 容器内路径 | 用途 |
|---|---|---|
/data/nginx/conf.d |
/etc/nginx/conf.d |
站点配置,官方镜像默认会加载这里的 *.conf |
/data/nginx/html |
/usr/share/nginx/html |
静态文件 |
/data/nginx/certs |
/etc/nginx/certs |
证书,自己约定的路径 |
/data/nginx/logs |
/var/log/nginx |
访问日志和错误日志 |
二、先用 docker run 起一个
docker run -d --name nginx \
-p 80:80 -p 443:443 \
-v /data/nginx/conf.d:/etc/nginx/conf.d:ro \
-v /data/nginx/html:/usr/share/nginx/html:ro \
-v /data/nginx/certs:/etc/nginx/certs:ro \
-v /data/nginx/logs:/var/log/nginx \
-e TZ=Asia/Shanghai \
--restart=always \
nginx:1.27
conf.d、html、certs 都是只读挂载,容器不需要往里写东西。logs 必须可写。
跑起来先看默认页:
curl -I http://127.0.0.1/
docker exec nginx nginx -t
nginx -t 返回 syntax is ok 和 test is successful 两行才算配置能过,这个检查在改完配置后每次都要跑。
三、反向代理
在 /data/nginx/conf.d/app.conf 里写一个最简的反代:
upstream app_backend {
server app:8080;
}
server {
listen 80;
server_name app.example.com;
client_max_body_size 50m;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
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;
proxy_read_timeout 60s;
}
}
两个地方容易忽略:
upstream里写的app是容器名。Docker 内置 DNS 只在同一个自定义网络里解析容器名,Nginx 容器和app容器必须挂在同一个网络上,否则启动时报host not found in upstream "app",整个 Nginx 起不来。proxy_set_header Host $host不能省。默认透传的是$proxy_host,也就是 upstream 名字,后端按 Host 做的域名路由、重定向、Cookie 域都会错。
改完配置,不用重启容器:
docker exec nginx nginx -t
docker exec nginx nginx -s reload
nginx -s reload 是给 master 进程发 HUP,平滑加载新配置,旧连接不断。先 -t 再 reload,配置写错时 -t 会拦下来,reload 不会让服务挂掉。
HTTPS 站点在同一个 server 块里加:
listen 443 ssl;
http2 on;
ssl_certificate /etc/nginx/certs/app.example.com.pem;
ssl_certificate_key /etc/nginx/certs/app.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
证书文件放在 /data/nginx/certs 下,容器里按挂载路径引用。
静态文件单独放一个 server
server {
listen 80;
server_name static.example.com;
root /usr/share/nginx/html;
index index.html;
location /assets/ {
expires 7d;
add_header Cache-Control "public";
}
}
四、用 compose 管起来
/data/nginx/docker-compose.yml:
services:
nginx:
image: nginx:1.27
container_name: nginx
restart: always
ports:
- "80:80"
- "443:443"
environment:
TZ: Asia/Shanghai
volumes:
- /data/nginx/conf.d:/etc/nginx/conf.d:ro
- /data/nginx/html:/usr/share/nginx/html:ro
- /data/nginx/certs:/etc/nginx/certs:ro
- /data/nginx/logs:/var/log/nginx
networks:
default:
name: web
external: true
业务容器加入这个网络,就能用容器名互相访问:
docker network create web # 只需建一次
docker network connect web app
用 compose 的话,在业务侧 compose 文件的网络段里写:
networks:
default:
name: web
external: true
这样两边在同一个网络里,proxy_pass http://app:8080 才能解析。
五、日志
官方镜像里 /var/log/nginx/access.log 和 error.log 是指向 /dev/stdout、/dev/stderr 的软链,所以默认 docker logs nginx 能看到访问日志。一旦把宿主机目录挂到 /var/log/nginx,这两个软链被盖住,日志变成宿主机上的真实文件,docker logs 就空了。这不是故障,是挂载导致的必然结果。
用哪种方式,提前定:
| 方式 | 查看命令 | 轮转 |
|---|---|---|
| 不挂 logs 目录 | docker logs -f nginx |
由 Docker 的 max-size 控制 |
| 挂 logs 目录 | tail -f /data/nginx/logs/access.log |
自己做 logrotate |
挂目录的情况下,加一份 logrotate:
/data/nginx/logs/*.log {
daily
rotate 14
compress
missingok
notifempty
copytruncate
}
copytruncate 是因为容器里的 Nginx 不会收到 logrotate 的 USR1 信号,只能先复制再清空原文件。
要 JSON 格式日志,在 nginx.conf 里定义 log_format;conf.d 里的站点文件只能引用,不能定义全局 format,所以需要挂整个 nginx.conf,或者用 include 拆出来。
六、常见报错
| 现象 | 原因 | 处理 |
|---|---|---|
502 Bad Gateway,错误日志 connect() failed (111: Connection refused) |
上游容器没起,或端口写错 | docker ps 看上游,docker exec nginx getent hosts app 验 DNS |
504 Gateway Time-out |
后端处理慢,超过 proxy_read_timeout |
调大超时,先确认后端本身耗时 |
413 Request Entity Too Large |
上传超过 client_max_body_size,默认只有 1m |
在 server 或 location 里调大 |
启动失败,host not found in upstream "app" |
两个容器不在同一网络 | 建共用网络,或改用宿主机 IP |
| 改了 conf 文件,容器里还是旧内容 | 单文件 bind mount 的 inode 问题 | 挂目录不挂单文件,重启容器 |
403 Forbidden,directory index of "/usr/share/nginx/html/" is forbidden |
目录里没有 index 文件 | 放 index.html,或开 autoindex |
docker logs 没输出 |
挂了 logs 目录,日志写到宿主机文件了 | 看 /data/nginx/logs |
| CentOS 上挂载后 403 或 Nginx 读不到文件 | SELinux 拦了目录 | 挂载加 :z,或 setenforce 0 临时验证 |
七、备份和升级
Nginx 本身没有"数据",要备份的只有配置和证书:
sudo tar -czf /backup/nginx-$(date +%F).tar.gz -C /data nginx
升级镜像就是改 image: nginx:1.27 里的小版本,然后:
docker compose pull nginx
docker compose up -d nginx
docker exec nginx nginx -t
容器重建不影响挂载的配置和日志。升级前先备份配置,nginx -t 过了再放流量。
最后一条限制:client_max_body_size、proxy_read_timeout 这类参数没有全局最优值,得按实际业务的上传大小和后端耗时定,默认值在文件上传和大报表导出场景下基本都要调。