Docker 进阶篇(二) Docker Compose 部署 Nginx 实践

摘要:本文记录在 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 -treload 是必须养成的习惯。配置有语法错误时直接 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 psNAMES 列。

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

总结

整个部署流程可以浓缩为五步:

  1. 建目录 ------ /home/nginx/{conf/conf.d,html,logs,ssl}
  2. 提文件 ------ 用 docker create 从镜像提取默认配置与网页
  3. 写编排 ------ compose 中对齐挂载路径,配置与网页用 :ro
  4. 打标签 ------ CentOS 上执行 chcon -Rt svirt_sandbox_file_t /home/nginx
  5. 启服务 ------ docker compose up -d,再用 nginx -tcurl 验证

其中最关键的是第 2 步。理解"bind mount 是覆盖语义"以及"官方镜像的 nginx.conf 不含 server 块"这两点,就能明白为什么必须先提取文件------否则容器虽然显示 Up,却没有任何端口监听,网站根本访问不了。

最后提醒:docker psUp 状态只说明进程存活,不代表服务可用 。判断 Nginx 是否正常工作,请用 nginx -tcurl 实际验证。

相关推荐
码--到成功1 小时前
Docker、Kubernetes安装系列 一
docker·容器·kubernetes
是乐乐啊呀2 小时前
常用破解软件
容器
bruce1282 小时前
K8s Pod 异常重启排查
云原生·容器·kubernetes
FoldWinCard3 小时前
基于k8s高可用web集群
前端·容器·kubernetes
cui_hao_nan3 小时前
Docker学习3
docker·容器
玉&心4 小时前
在Docker中部署前端和后端的实现
运维·docker·容器
wdfk_prog4 小时前
Docker 常用语法与命令:CLI、Dockerfile 与 Compose 速查
运维·docker·容器
无敌糖果4 小时前
K8S network namespace错误分析
docker·容器·kubernetes
郝开5 小时前
Docker Compose 模块化多环境配置规范指南:示例搭建Kibana 9.4.2
docker·容器·kibana