摘要:本文记录在 CentOS 服务器上,使用 Docker Compose 部署 Nginx 并挂载配置、网页、日志、证书的完整流程。所有步骤均按正确操作顺序组织,可直接照做。文末附一键部署脚本。
环境:CentOS 7/8 或 RHEL 系(已启用 SELinux)+ Docker + Docker Compose v2 + Nginx 官方镜像
一、前置检查
确认 Docker 与 Compose 已就绪:
bash
docker -v
docker compose version # v2 插件,注意是 docker compose(空格),不是 docker-compose
若未安装,先装 Docker:
bash
# 安装依赖
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
# 添加阿里云镜像源(国内推荐)
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 安装
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 启动并设置开机自启
sudo systemctl enable --now docker
# 配置镜像加速(国内拉取官方镜像慢)
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
防火墙放行端口:
bash
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
二、创建目录结构
服务数据集中管理,便于备份与迁移:
bash
sudo mkdir -p /home/nginx/{conf/conf.d,html,logs,ssl}
cd /home/nginx
完成后结构如下:
/home/nginx
├── docker-compose.yml # 服务编排
├── nginx.conf.bak # 主配置备份(参考用,不挂载)
├── conf/
│ └── conf.d/
│ └── default.conf # ★ 站点配置,真正生效
├── html/ # ★ 网页文件
│ ├── index.html
│ └── 50x.html
├── logs/ # 日志落盘
└── ssl/ # HTTPS 证书(可选)
注意路径是
conf/conf.d两级,不是conf.d。
三、从镜像提取默认文件
Docker 的 bind mount 是覆盖语义:宿主机目录挂载到容器后,容器内该路径的原有内容会被遮蔽。因此挂载前,必须先把镜像内的默认文件提取到宿主机,否则容器里看到的就是空目录。
用 docker create 创建临时容器(只创建不启动,不占端口),再从镜像提取文件:
bash
# 从镜像创建临时容器
docker create --name nginx-tmp nginx:latest
# 提取网页文件
docker cp nginx-tmp:/usr/share/nginx/html/. /home/nginx/html/
# 提取站点配置,直接放到真正生效的目录
docker cp nginx-tmp:/etc/nginx/conf.d/default.conf /home/nginx/conf/conf.d/default.conf
# 备份主配置(放在根目录,仅作参考)
docker cp nginx-tmp:/etc/nginx/nginx.conf /home/nginx/nginx.conf.bak
# 销毁临时容器
docker rm nginx-tmp
核对结果:
bash
ls -la /home/nginx/html/ # 应有 index.html、50x.html
ls -la /home/nginx/conf/conf.d/ # 应有 default.conf
为什么要这样做
官方镜像的 nginx.conf 中没有任何 server {} 块,只有一行:
nginx
include /etc/nginx/conf.d/*.conf;
所有站点定义都在 conf.d/*.conf 里。如果挂载后这个目录是空的,就不会有任何端口被监听,网站自然访问不了。
四、编写 docker-compose.yml
bash
cat > /home/nginx/docker-compose.yml <<'EOF'
services:
nginx:
image: nginx:latest # 生产建议锁版本,如 nginx:1.29
container_name: nginx-web
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- /home/nginx/conf/conf.d:/etc/nginx/conf.d:ro
- /home/nginx/html:/usr/share/nginx/html:ro
- /home/nginx/logs:/var/log/nginx
- /home/nginx/ssl:/etc/nginx/ssl:ro
- /etc/localtime:/etc/localtime:ro
environment:
TZ: Asia/Shanghai
EOF
配置项说明
| 配置项 | 说明 |
|---|---|
:ro |
只读挂载,防止容器篡改配置与网页。日志目录不可加,否则写不进去 |
restart: always |
开机自启,异常退出自动拉起 |
/etc/localtime + TZ |
双保险,保证日志时间戳与宿主机一致 |
image: nginx:latest |
生产环境建议锁定具体版本,避免自动升级导致配置不兼容 |
是否需要挂载 nginx.conf
默认不需要。镜像自带的主配置已够用,少挂一个文件就少一个出错点。
若确实需要调整 worker_connections、开启 gzip 等,先把备份复制为正式文件再挂载:
bash
cp /home/nginx/nginx.conf.bak /home/nginx/conf/nginx.conf
然后在 volumes 中增加一行:
yaml
- /home/nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro
五、站点配置
官方提取出的 default.conf 开箱即用:
nginx
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;
}
}
作为 conf.d 下唯一的 server,它会自动成为默认站点,无需额外声明 default_server。
静态站点(推荐加上 charset 与 try_files)
bash
cat > /home/nginx/conf/conf.d/default.conf <<'EOF'
server {
listen 80;
server_name your.domain.com;
charset utf-8;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
EOF
try_files让单页应用(Vue/React)刷新子路由时不会 404;纯静态站可以保留,无副作用。
单页应用(SPA)
部署 Vue / React 打包产物,把 dist/ 内容复制到 html/:
bash
cp -r /path/to/your/project/dist/. /home/nginx/html/
配置同上,try_files 是关键,缺了它刷新 /about 这类子路由会返回 404。
反向代理到后端服务
后端服务与 Nginx 在同一个 compose 中,直接用服务名互访(Docker 内置 DNS 自动解析):
nginx
upstream backend {
server app:8080; # app 为 compose 中的服务名
keepalive 32;
}
server {
listen 80;
server_name your.domain.com;
location /api/ {
proxy_pass http://backend/;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
}
}
对应地,后端服务需与 Nginx 处于同一网络:
yaml
services:
nginx:
# ... 上述配置
networks:
- web
app:
image: your-app-image
expose:
- "8080"
networks:
- web
networks:
web:
driver: bridge
HTTPS
把证书文件放入 /home/nginx/ssl/:
bash
cp fullchain.pem /home/nginx/ssl/
cp privkey.pem /home/nginx/ssl/
chmod 600 /home/nginx/ssl/privkey.pem
配置:
nginx
server {
listen 80;
server_name your.domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name your.domain.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}
若暂不使用 HTTPS,可把 compose 中
- "443:443"注释掉,不影响启动。
六、SELinux 配置(CentOS 必做)
CentOS/RHEL 默认启用 SELinux,容器读取 /home 下的挂载目录会被拒绝,导致 403 或 Permission denied。
确认状态:
bash
getenforce # 输出 Enforcing 表示已启用
给目录打容器可读标签:
bash
chcon -Rt svirt_sandbox_file_t /home/nginx
验证标签是否生效:
bash
ls -Z /home/nginx | head
替代方案:在 compose 的每个卷后加
:Z(如...:/etc/nginx/conf.d:ro,Z)。用chcon更直观,也不污染编排文件。
七、启动与验证
全新部署
bash
cd /home/nginx
docker compose up -d
从已有的 docker run 容器迁移
若已有一个同名容器(docker run 启动、未被 compose 管理),需先移除,否则容器名冲突:
bash
docker inspect nginx-web --format '{{range .Mounts}}{{.Source}} -> {{.Destination}} ({{.Mode}}){{"\n"}}{{end}}'
docker stop nginx-web && docker rm nginx-web
删除容器不会删除 宿主机上的
conf/、html/、logs/,这些是 bind mount 目录,数据原地保留。只要 compose 沿用相同的挂载路径,配置、网页、日志全部无缝复用。
然后启动:
bash
cd /home/nginx && docker compose up -d
验证四步
bash
docker compose ps # State 应为 Up
docker compose exec nginx-web nginx -t # 见到 test is successful
curl -I http://localhost # 应返回 HTTP/1.1 200 OK
tail -f /home/nginx/logs/access.log # 访问日志开始产生
浏览器访问 http://服务器IP,应看到 "Welcome to nginx!"。
八、日常运维
修改站点配置后
bash
cd /home/nginx
vi conf/conf.d/default.conf
docker compose exec nginx-web nginx -t # 校验语法,必须见到 successful
docker compose exec nginx-web nginx -s reload # 平滑重载,不中断服务
先
nginx -t再reload是必须养成的习惯。配置有语法错误时直接 reload,会让 Nginx 带着错误继续运行。
修改 docker-compose.yml 后
bash
docker compose up -d # 自动重建发生变化的容器
部署新版本网页
bash
cp -r /path/to/dist/. /home/nginx/html/
# 静态资源无需 reload,刷新浏览器即可
查看日志
bash
docker compose logs -f nginx # 容器标准输出
tail -f /home/nginx/logs/access.log # 落盘访问日志
tail -f /home/nginx/logs/error.log # 落盘错误日志
官方镜像默认把日志软链到
/dev/stdout、/dev/stderr。挂载./logs后软链失效,日志改为落盘到宿主机,docker compose logs便看不到内容------二者二选一,按需取用。
命令速查
| 操作 | 命令 |
|---|---|
| 启动 | docker compose up -d |
| 停止并移除 | docker compose down |
| 重启 | docker compose restart |
| 查看状态 | docker compose ps |
| 实时日志 | docker compose logs -f nginx |
| 落盘日志 | tail -f /home/nginx/logs/access.log |
| 校验配置 | docker compose exec nginx-web nginx -t |
| 热重载 | docker compose exec nginx-web nginx -s reload |
九、一键部署脚本
新建机器直接执行,无需手工逐步操作:
bash
#!/bin/bash
set -e
BASE=/home/nginx
# 1. 创建目录
mkdir -p ${BASE}/{conf/conf.d,html,logs,ssl}
# 2. 从镜像提取默认配置与网页
docker create --name nginx-tmp nginx:latest
docker cp nginx-tmp:/usr/share/nginx/html/. ${BASE}/html/
docker cp nginx-tmp:/etc/nginx/conf.d/default.conf ${BASE}/conf/conf.d/default.conf
docker cp nginx-tmp:/etc/nginx/nginx.conf ${BASE}/nginx.conf.bak
docker rm nginx-tmp
# 3. 编写 compose
cat > ${BASE}/docker-compose.yml <<'EOF'
services:
nginx:
image: nginx:latest
container_name: nginx-web
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- /home/nginx/conf/conf.d:/etc/nginx/conf.d:ro
- /home/nginx/html:/usr/share/nginx/html:ro
- /home/nginx/logs:/var/log/nginx
- /home/nginx/ssl:/etc/nginx/ssl:ro
- /etc/localtime:/etc/localtime:ro
environment:
TZ: Asia/Shanghai
EOF
# 4. SELinux 打标签
if command -v getenforce >/dev/null && [ "$(getenforce)" = "Enforcing" ]; then
chcon -Rt svirt_sandbox_file_t ${BASE}
fi
# 5. 启动
cd ${BASE}
docker compose up -d
# 6. 验证
docker compose ps
docker compose exec nginx-web nginx -t
curl -I http://localhost
使用:
bash
chmod +x deploy-nginx.sh
./deploy-nginx.sh
十、注意事项
按顺序操作时,留意以下六点即可避开绝大多数问题。
1. 挂载前先提取默认文件
bind mount 是覆盖语义,宿主机空目录会遮蔽容器内原有内容。务必先执行第三节的提取操作。
2. docker cp 用的是容器名,不是镜像名
bash
docker cp nginx-web:/etc/nginx/nginx.conf ./ # ✓ 容器名
docker cp 7e91f2ed87a6:/etc/nginx/nginx.conf ./ # ✓ 容器 ID
容器名见 docker ps 的 NAMES 列。
3. 配置文件放在 conf/conf.d/ 且后缀为 .conf
主配置 include 的是 conf.d/*.conf,其他后缀不会被加载。改错目录或写错后缀都表现为"改了不生效"。
4. 多个 server 时只能有一个 default_server
重复声明会报 a duplicate default server for 0.0.0.0:80。单一 server 无需声明,它会自动成为默认站点。
5. SELinux 标签要提前打
目录为空时容器读不到文件,SELinux 不会报错;放进真实文件后才暴露,属于延迟触发,容易漏掉。
6. 改配置后必须 reload
bash
docker compose exec nginx-web nginx -t && docker compose exec nginx-web nginx -s reload
总结
整个部署流程可以浓缩为五步:
- 建目录 ------
/home/nginx/{conf/conf.d,html,logs,ssl} - 提文件 ------ 用
docker create从镜像提取默认配置与网页 - 写编排 ------ compose 中对齐挂载路径,配置与网页用
:ro - 打标签 ------ CentOS 上执行
chcon -Rt svirt_sandbox_file_t /home/nginx - 启服务 ------
docker compose up -d,再用nginx -t与curl验证
其中最关键的是第 2 步。理解"bind mount 是覆盖语义"以及"官方镜像的 nginx.conf 不含 server 块"这两点,就能明白为什么必须先提取文件------否则容器虽然显示 Up,却没有任何端口监听,网站根本访问不了。
最后提醒:docker ps 的 Up 状态只说明进程存活,不代表服务可用 。判断 Nginx 是否正常工作,请用 nginx -t 加 curl 实际验证。