
写在前面:为什么用 Docker Compose?比单容器部署好在哪?
做容器化部署时,单靠docker run命令逐个启动 MySQL、Redis、后端、Nginx 容器会非常繁琐 ------ 不仅要记大量命令参数,还得手动控制容器启动顺序、配置网络联动,一旦服务器重启,所有容器要重新逐个启动,维护成本极高。
Docker Compose 是 Docker 官方的多容器编排工具,核心是通过一个docker-compose.yml配置文件,集中管理所有容器的镜像、端口、挂载、环境变量、网络、依赖关系等所有配置,能完美解决单容器部署的痛点。
Docker Compose 核心优点(也是本次部署选择它的原因)
- 一键启停 :一条
docker-compose up -d启动所有服务,docker-compose down停止并清理,无需逐个执行docker run/docker stop; - 配置统一:所有容器配置集中在一个 yaml 文件,易编辑、易备份,后续修改只需改配置文件,无需记复杂命令;
- 容器自动联动 :自动创建专属网络,容器间通过服务名 即可通信,无需手动配置网络;可通过
depends_on控制启动顺序,解决服务依赖问题; - 环境一致性:配置文件可跨环境复用,本地测试、服务器部署用同一套配置,避免 "本地能跑,服务器不行";
- 易维护易迁移:项目目录 + 配置文件 + 离线镜像包,可直接迁移到其他服务器,解压后一键启动,无需重新配置;
- 数据卷 / 网络自动管理 :自动创建数据卷、自定义网络,无需手动执行
docker volume create/docker network create。
相比单容器部署,Docker Compose 让多服务容器化部署的效率提升数倍,尤其适合后端 + 前端 + 数据库 + 缓存 + 代理这类多组件的项目部署,也是目前中小型项目容器化的主流方案。
一、部署环境准备(提前确认,避免后续兼容问题)
1. 虚拟机环境(个人测试用,非生产)
- 系统:CentOS 7.9(最小化安装,已配置静态 IP:192.168.1.100,虚拟 IP,替换真实 IP)
- 内存:4G(建议不低于 2G,否则 Docker 容器启动可能卡顿)
- 硬盘:50G(足够存放镜像、项目文件和数据库数据)
- 网络:能访问外网(前期拉取镜像 / 安装依赖用,后期可断网运行)
2. 软件版本(全程统一版本,避免兼容问题)
- Docker:Docker CE 24.0.7(CentOS7 稳定版)
- Docker Compose:V2.27.1(解决旧版配置兼容问题)
- 后端:.NET 8(本地 VS2022 发布到 publish 文件夹)
- 前端:Vue3(本地 yarn 打包到 dist 文件夹)
- MySQL:8.0(Docker 镜像,数据持久化)
- Redis:7-alpine(轻量版,适合容器部署)
- Nginx:alpine(轻量版,代理前端静态文件 + 后端接口)
3. 本地准备文件(提前打包好,上传到虚拟机)
- 后端:publish 文件夹(VS2022 发布后的.NET8 项目文件,含核心 dll、配置文件)
- 前端:dist 文件夹(Vue3 打包后的静态文件,含 index.html、css、js)
- 镜像 tar 包:rdif-all-images.tar(离线镜像包,含 MySQL、Redis、Nginx 等 6 个所需镜像,解决网络拉取超时)
- 配置文件:my.cnf(MySQL 配置)、nginx.conf(Nginx 配置)、init.sql(MySQL 初始化 SQL)、docker-compose.yml(核心编排文件)
二、前期准备工作(必做,奠定部署基础)
1. CentOS7 系统基础配置(最小化安装补充依赖)
最小化安装的 CentOS7 缺少很多基础工具,先安装必要依赖,避免后续 Docker 安装、命令执行失败:
bash
# 更新系统软件包(可选,建议执行,避免依赖版本过低)
yum update -y
# 安装基础工具(wget、vim、net-tools等,后续常用)
yum install -y wget vim net-tools epel-release
2. 安装 Docker CE(CentOS7 稳定版,步骤固定)
CentOS7 默认源没有 Docker,需要配置 Docker 官方源,同时解决依赖缺失问题(重点解决 container-selinux 依赖):
bash
# 1. 卸载旧版本Docker(如果之前装过,避免冲突,没装过可跳过)
yum remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
# 2. 安装Docker依赖(必做,否则安装失败)
yum install -y yum-utils device-mapper-persistent-data lvm2 container-selinux
# 3. 配置Docker官方源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 4. 安装Docker CE(稳定版)
yum install -y docker-ce docker-ce-cli containerd.io
# 5. 启动Docker服务,并设置开机自启(提前配置,后续不用再改)
systemctl start docker
systemctl enable docker
# 6. 验证Docker安装成功(输出版本号即成功)
docker --version
✅ 成功标识:Docker version 24.0.7, build afdd53b
3. 配置 Docker 镜像加速(国内必做,否则镜像拉取超时)
Docker 默认拉取官方镜像(国外源),国内访问极慢,甚至超时。这里用阿里云个人专属镜像加速(比公共源更稳定),步骤如下:
-
登录阿里云官网(https://www.aliyun.com/),搜索 "容器镜像服务",进入 "镜像加速器",复制自己的专属加速地址(示例:https://xxxxxx.mirror.aliyuncs.com,替换成自己的);

-
配置镜像加速,修改 Docker 守护进程配置文件:
bash
# 创建Docker配置目录(如果不存在)
mkdir -p /etc/docker
# 写入加速配置(替换成自己的阿里云专属加速地址)
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://xxxxxx.mirror.aliyuncs.com", "https://mirror.ccs.tencentyun.com"]
}
EOF
# 重新加载配置,重启Docker,让加速生效
systemctl daemon-reload
systemctl restart docker
# 验证加速配置是否生效(输出配置的加速地址即成功)
docker info | grep -A 2 "Registry Mirrors"
✅ 成功标识:输出中包含自己配置的阿里云加速地址。
4. 升级 Docker Compose(解决旧版配置兼容问题)
CentOS7 默认安装的 Docker Compose 是 1.x 版本,不支持新版 docker-compose.yml 中的配置(如 condition、start_period),升级到 V2 版本(官方推荐):
bash
# 1. 删除旧版docker-compose(如果之前装过)
rm -f /usr/local/bin/docker-compose
# 2. 安装Docker Compose V2(插件形式,稳定)
yum install -y docker-compose-plugin
# 3. 建立软链接,保持docker-compose命令可用(和旧版用法一致)
ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/local/bin/docker-compose
# 4. 验证升级成功(输出V2版本号即成功)
docker-compose --version
✅ 成功标识:Docker Compose version v2.27.1(版本号可不同)
5. 关闭防火墙(测试环境,避免端口访问失败)
个人测试用,直接关闭 CentOS7 的 FirewallD 防火墙,避免前端、后端、数据库端口被拦截,生产环境可按需开放端口:
bash
# 1. 立即停止防火墙服务
systemctl stop firewalld
# 2. 禁止防火墙开机自启(避免虚拟机重启后防火墙又开启)
systemctl disable firewalld
# 3. 验证防火墙状态(输出inactive即成功关闭)
systemctl status firewalld
三、项目目录结构整理(规范目录,避免后续混乱)
将本地准备好的所有文件,上传到 CentOS7 虚拟机的/root/rdif-docker目录(自定义目录,方便记忆),最终目录结构如下(重点:前后端仅保留打包 / 发布文件,无源码、无多余文件):

plaintext
rdif-docker/ # 项目根目录(所有文件都放在这里)
├── docker-compose.yml # 核心编排文件,管理所有容器
├── backend/ # 后端目录(仅保留VS发布的publish)
│ └── publish/ # .NET8发布文件(含RDIF.WebHost.dll、appsettings.json)
├── frontend/ # 前端目录(仅保留Vue3打包的dist)
│ └── dist/ # Vue3静态文件(index.html、css、js、assets)
├── nginx/ # Nginx配置目录
│ └── nginx.conf # Nginx配置文件(代理前端+后端接口)
├── mysql/ # MySQL配置目录
│ ├── my.cnf # MySQL配置(不区分大小写、字符集等)
│ └── init.sql # MySQL初始化SQL(创建库、表、初始化数据)
└── rdif-all-images.tar # 离线镜像包(含所有所需镜像,避免拉取超时)
上传文件方法(新手推荐,可视化操作)
用 MobaXterm或Xftp 或 WinSCP 工具,连接虚拟机(IP:192.168.1.100,账号:root,密码:Root@123456,虚拟密码),将本地的 publish、dist、配置文件、镜像 tar 包,拖到对应目录下即可。
四、编写 docker-compose.yml(核心配置,重中之重)
这是整个部署的核心,所有容器(MySQL、Redis、后端、Nginx)的联动、端口映射、目录挂载,都在这里配置。结合本次需求(前后端已打包 / 发布,无需编译构建),编写如下配置(注释详细,可直接复制修改,替换自己的对应信息,Redis 密码已替换为通用虚拟密码):
yaml
version: '3.8'
# 所有服务的集合
services:
# 1. MySQL8 服务(数据库,数据持久化)
mysql:
image: mysql:8.0 # 使用的镜像(本地已导入,无需拉取)
container_name: rdif-mysql # 自定义容器名,方便管理
restart: always # 容器异常退出/ Docker启动时,自动重启
environment:
MYSQL_ROOT_PASSWORD: Root@123456 # MySQL root密码(虚拟,替换成自己的)
MYSQL_USER: guosisoft # 项目访问MySQL的用户名(自定义)
MYSQL_PASSWORD: Mysql@123456 # 项目访问MySQL的密码(虚拟)
MYSQL_DATABASE: rdif_vue3 # 项目所用数据库名(自定义)
MYSQL_TZINFO_TO_SYS_TABLES: 1 # 初始化MySQL时区表,解决时差问题
TZ: Asia/Shanghai # 强制容器时区为东八区(核心,解决时差)
ports:
- "3306:3306" # 端口映射:宿主机3306 → 容器内3306(本地工具可连接)
volumes:
# 挂载MySQL配置文件,实现自定义配置
- ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
# 挂载初始化SQL,容器启动时自动执行,创建库表
- ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
# 挂载数据卷,持久化MySQL数据(docker-compose down不会删除数据)
- mysql-data:/var/lib/mysql
# 挂载宿主机时区文件,双重保障时区同步(只读,避免容器修改)
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
command: --lower_case_table_names=1 # MySQL不区分大小写(避免项目表名大小写问题)
networks:
- rdif-network # 加入自定义网络,实现容器间通信
# 健康检查:检测MySQL是否真正就绪,避免后端启动早于MySQL
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uguosisoft", "-pMysql@123456"]
interval: 5s # 每5秒检测一次
timeout: 30s # 超时时间30秒
retries: 10 # 重试10次,失败则认为容器未就绪
start_period: 20s # 容器启动后,延迟20秒开始检测
# 2. Redis 服务(缓存,轻量版,密码已替换为虚拟通用密码)
redis:
image: redis:7-alpine # 轻量版Redis,占用资源少
container_name: rdif-redis # 自定义容器名
restart: always # 自动重启
ports:
- "6379:6379" # 端口映射:宿主机6379 → 容器内6379
volumes:
- redis-data:/data # 数据卷持久化Redis数据
command: redis-server --requirepass "Redis@123456" # Redis虚拟密码,替换成自己的复杂密码
networks:
- rdif-network # 加入自定义网络
environment:
TZ: Asia/Shanghai # 同步东八区时区
# 3. .NET8 后端服务(已发布,直接挂载运行,Redis连接密码同步替换)
backend:
image: mcr.microsoft.com/dotnet/aspnet:8.0 # .NET8运行时镜像(无需构建)
container_name: rdif-backend # 自定义容器名
restart: always # 自动重启
ports:
- "58588:58588" # 端口映射:宿主机58588 → 容器内58588(后端接口端口)
depends_on:
mysql:
condition: service_healthy # 仅在MySQL健康检查通过(就绪)后,才启动后端
redis:
condition: service_started # Redis启动后,即可启动后端
volumes:
# 核心:挂载本地publish到容器的/app目录
- ./backend/publish:/app
# 宿主机:/wwwroot/Resources → 容器内:/wwwroot/Resources
- /wwwroot/Resources:/wwwroot/Resources
environment:
TZ: Asia/Shanghai # 同步东八区时区
ASPNETCORE_URLS: "http://*:58588" # 强制.NET8容器内监听58588端口(解决端口不通)
ASPNETCORE_ENVIRONMENT: Production # .NET环境(生产环境)
# MySQL连接字符串(替换成自己的用户名、密码、数据库名,server用容器名mysql)
ConnectionStrings__MySQL: "server=mysql;port=3306;database=rdif_vue3;user=guosisoft;password=Mysql@123456;charset=utf8mb4;AllowPublicKeyRetrieval=True;SslMode=None"
# Redis连接字符串(server用容器名redis,密码同步替换为虚拟密码)
ConnectionStrings__Redis: "redis:6379,password=Redis@123456,defaultDatabase=0,ssl=false,abortConnect=false"
working_dir: /app # 容器工作目录,指向挂载的publish目录
entrypoint: ["dotnet", "RDIF.WebHost.dll"] # 启动后端核心dll(替换成自己的dll名)
networks:
- rdif-network # 加入自定义网络
# 4. Nginx 服务(代理前端静态文件+后端接口)
nginx:
image: nginx:alpine # 轻量版Nginx
container_name: rdif-nginx # 自定义容器名
restart: always # 自动重启
ports:
- "6866:6866" # 端口映射:宿主机6866 → 容器内6866(前端访问端口)
volumes:
# 挂载Nginx配置文件,实现前端代理和接口转发
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
# 挂载前端dist目录,Nginx直接代理静态文件(核心)
- ./frontend/dist:/usr/share/nginx/html
depends_on:
- backend # 后端启动后,再启动Nginx
networks:
- rdif-network # 加入自定义网络
environment:
TZ: Asia/Shanghai # 同步东八区时区
# 数据卷:持久化MySQL和Redis数据(docker-compose down不会删除)
volumes:
mysql-data:
redis-data:
# 自定义网络:所有容器加入同一网络,实现容器间通信(用容器名即可访问)
networks:
rdif-network:
driver: bridge
关键配置说明(必看,避免踩坑)
- 所有容器都配置了
restart: always,配合 Docker 开机自启,实现虚拟机重启后所有服务自动启动; - 后端配置
ASPNETCORE_URLS: "http://*:58588",强制监听 58588 端口,和端口映射一致,解决.NET8 默认监听 8080 导致的端口不通; - MySQL 连接字符串中,
server=mysql(用容器名),而非虚拟机 IP,容器间通信必须这样配置;补充AllowPublicKeyRetrieval=True;SslMode=None,解决容器内连接 MySQL 失败; - 所有容器都配置
TZ: Asia/Shanghai,同步东八区时区,解决 MySQL 时差 8 小时问题; - 后端
depends_on配置了condition: service_healthy,确保 MySQL 完全就绪后再启动后端,避免后端启动时 MySQL 未初始化完成导致的连接失败; - 所有敏感密码(MySQL/Redis)均为虚拟示例,实际部署请替换为字母 + 数字 + 特殊符号的复杂密码,提升安全性。
五、本地拉取镜像并打包为 tar(离线方案前置步骤,关键!)
本次部署采用离线镜像导入 方案(解决服务器网络拉取超时问题),需在本地能正常联网的电脑上提前拉取所有所需镜像,再打包为 tar 文件,最后上传到 CentOS 服务器。
5.1 本地拉取镜像的前提条件
本地电脑(Windows/macOS)需安装Docker Desktop(Docker 桌面版),内置 Docker 引擎和镜像管理功能,是本地操作 Docker 的必备工具:
- 下载地址:https://www.docker.com/products/docker-desktop/
- 安装后验证:打开 Docker Desktop,启动后在本地终端(CMD/PowerShell/ 终端)执行
docker --version,输出版本号即安装成功; - 关键设置:Docker Desktop 中配置镜像加速(和服务器端一致,阿里云 / 网易云均可),避免本地拉取镜像超时。
5.2 本地终端拉取本次部署所有所需镜像
打开本地终端(Windows 用 PowerShell/CMD,macOS/Linux 用终端),执行以下docker pull命令,逐个拉取镜像(按本次部署的版本号拉取,确保版本一致):
bash
# 1. 拉取MySQL8.0镜像
docker pull mysql:8.0
# 2. 拉取Redis7轻量版镜像
docker pull redis:7-alpine
# 3. 拉取.NET8运行时镜像(后端运行依赖)
docker pull mcr.microsoft.com/dotnet/aspnet:8.0
# 4. 拉取Nginx轻量版镜像
docker pull nginx:alpine
✅ 拉取成功验证:本地终端执行docker images,能看到以上 4 个镜像,无报错即拉取完成。


5.3 本地将镜像打包为 tar 文件(单镜像 / 批量镜像两种方式)
打包镜像有单镜像打包 和批量镜像打包 两种方式,推荐批量打包为一个 tar 文件(方便上传和服务器导入),以下两种方式均提供代码示例。
方式 1:批量打包所有镜像为一个 tar 文件(推荐,一次导入所有)
将所有拉取的镜像打包为一个统一的 tar 文件(示例:rdif-all-images.tar),放在本地易找到的目录(如桌面),执行命令前先切换到目标目录(如 Windows 桌面目录):
bash
# Windows PowerShell切换到桌面目录(示例,可替换为自己的目录)
cd C:\Users\你的用户名\Desktop
# 批量打包镜像为rdif-all-images.tar(核心命令,包含所有所需镜像)
docker save -o rdif-all-images.tar mysql:8.0 redis:7-alpine mcr.microsoft.com/dotnet/aspnet:8.0 nginx:alpine
- 关键参数:
-o指定输出的 tar 文件名和路径;后面跟所有需要打包的镜像名:版本,用空格分隔。
方式 2:单个镜像单独打包(按需使用,适合单独更新镜像)
如果后续只需更新某个镜像(如仅更新 MySQL),可单独打包该镜像,命令如下:
bash
# 打包MySQL8.0为单独的tar文件
docker save -o mysql_8.0.tar mysql:8.0
# 打包Redis7-alpine为单独的tar文件
docker save -o redis_7_alpine.tar redis:7-alpine
# 打包.NET8运行时为单独的tar文件
docker save -o dotnet_aspnet_8.0.tar mcr.microsoft.com/dotnet/aspnet:8.0
# 打包Nginx-alpine为单独的tar文件
docker save -o nginx_alpine.tar nginx:alpine
5.4 验证本地打包成功
打包完成后,在本地目标目录(如桌面)能看到生成的 tar 文件(如 rdif-all-images.tar),文件大小约 2-3G(正常大小),即打包成功;后续用 MobaXterm/Xftp/WinSCP 将该 tar 文件上传到 CentOS 服务器的/root/rdif-docker目录即可。

六、服务器端核心部署步骤(离线导入镜像 + 启动服务)
6.1 离线导入镜像(解决网络拉取超时,核心步骤)
将本地打包好的镜像 tar 文件上传到服务器后,执行以下命令离线导入,无需再联网拉取:
bash
# 1. 进入项目根目录(确保镜像tar包在该目录下)
cd /root/rdif-docker
# 2. 查看镜像tar包是否存在(能看到rdif-all-images.tar即正常)
ls -l
# 3. 离线导入所有镜像(耐心等待,约1-2分钟,镜像包约2-3G)
docker load -i rdif-all-images.tar
# 4. 验证镜像导入成功(能看到4个所需镜像即正常)
docker images
✅ 成功标识:docker images输出中包含mysql:8.0、redis:7-alpine、nginx:alpine、mcr.microsoft.com/dotnet/aspnet:8.0等镜像。

6.2 启动所有服务(核心命令,一键启动)
所有配置和准备工作完成后,执行以下命令,一键启动所有容器,无需手动逐个启动:
bash
# 进入项目根目录(必须在docker-compose.yml所在目录执行)
cd /root/rdif-docker
# 后台启动所有服务(-d:后台运行,无需构建,因为前后端已打包)
docker-compose up -d
# 查看所有服务运行状态(所有服务State列显示Up即正常)
docker-compose ps
✅ 成功标识:docker-compose ps输出中,mysql、redis、backend、nginx 四个服务的 State 列均为Up (healthy)或Up。

6.3 验证服务启动成功(逐个验证,确保无问题)
启动后,逐个验证前端、后端、数据库、Redis 是否正常,避免后续使用时出现问题:
(1)验证后端服务(Swagger 访问,核心)
.NET8 后端默认集成 Swagger,访问地址:http://192.168.1.100:58588/swagger/index.html(替换成自己的虚拟机 IP 和后端端口)
-
✅ 成功标识:浏览器能正常打开 Swagger 页面,无报错,能看到所有接口;
-
❌ 失败排查:如果打不开,执行
docker-compose logs -f backend,查看后端实时日志,排查问题(如端口监听错误、MySQL 连接失败)。

(2)验证前端服务(静态页面访问)
前端由 Nginx 代理,访问地址:http://192.168.1.100:6866(替换成自己的虚拟机 IP 和前端端口)
-
✅ 成功标识:浏览器能正常打开 Vue3 前端页面,样式、图片正常显示;
-
❌ 失败排查:打不开则查看 Nginx 日志,命令:
docker-compose logs -f nginx,检查 Nginx 配置是否正确、dist 目录是否挂载成功。

(3)验证 MySQL 服务(本地工具连接)
用 Navicat 或 DBeaver 等本地数据库工具,连接虚拟机上的 MySQL:
- 主机:192.168.1.100(虚拟机 IP)
- 端口:3306
- 用户名:guosisoft(自定义的 MySQL 用户名)
- 密码:Mysql@123456(自定义的密码)
- 数据库:rdif_vue3(自定义的数据库名)
- ✅ 成功标识:能正常连接,能看到 init.sql 初始化的库、表和数据。
(4)验证 Redis 服务(可选,按需验证)
用 Redis 客户端工具(如 Another Redis Desktop Manager),连接虚拟机上的 Redis:
- 主机:192.168.1.100(虚拟机 IP)
- 端口:6379
- 密码:Redis@123456(自定义的 Redis 密码)
- ✅ 成功标识:能正常连接,能执行 set、get 等命令。
七、部署过程中遇到的坑及解决方案(重点,避坑指南)
这部分是核心,记录了我部署过程中遇到的所有问题,每个问题都有详细的排查过程和解决方案,亲测有效,帮你少走弯路。
坑 1:Docker 镜像拉取超时,提示 "dial tcp ... i/o timeout"
- 现象:执行
docker pull mysql:8.0或docker-compose up -d时,拉取镜像超时,提示访问registry-1.docker.io失败; - 排查:虚拟机能 ping 通百度(外网正常),镜像加速配置也生效,但仍拉取超时,推测是虚拟机网络有隐性限制(如内网拦截);
- 解决方案:采用离线导入镜像方案 (前文 5、6 小节已详细说明),在本地能联网的电脑上拉取镜像、打包成 tar 包,上传到服务器后用
docker load -i导入,彻底跳过网络拉取。
坑 2:后端服务启动成功,但访问http://192.168.1.100:58588打不开 Swagger
-
现象:
docker-compose ps显示 backend 服务是 Up 状态,但浏览器访问后端地址打不开,查看后端日志,显示Now listening on: http://[::]:8080; -
排查:后端容器默认监听 8080 端口,但 docker-compose.yml 中映射的是 58588:58588,端口不匹配,导致访问不通;
-
解决方案:在 backend 服务的 environment 中,添加
ASPNETCORE_URLS: "http://*:58588",强制.NET8 容器内监听 58588 端口,和端口映射一致,重启后端容器即可。

坑 3:后端日志提示 "Unable to connect to any of the specified MySQL hosts"
-
现象:后端启动后,日志持续报错,无法连接 MySQL,但本地 Navicat 能正常连接虚拟机上的 MySQL;
-
排查:本地能连接,说明 MySQL 端口映射正常;后端容器和 MySQL 容器在同一 Docker 网络,应该用容器名(mysql)访问,而非虚拟机 IP,排查后发现连接字符串正确,但后端启动早于 MySQL 初始化完成;
-
解决方案:
- 给 MySQL 服务添加健康检查(前文 docker-compose.yml 中的 healthcheck 配置),检测 MySQL 是否真正就绪;
- 后端服务的 depends_on 中,添加
condition: service_healthy,确保 MySQL 健康检查通过后,再启动后端; - 给 MySQL 连接字符串补充
AllowPublicKeyRetrieval=True;SslMode=None,解决容器内连接 MySQL 的公钥检索和 SSL 限制。
坑 4:执行docker-compose down提示配置无效,"Unsupported config option for services.backend: 'condition'"
- 现象:执行 docker-compose 命令时,提示配置无效,不支持
condition和start_period; - 排查:查看 docker-compose 版本,发现是 1.x 版本,不支持新版配置项;
- 解决方案:升级 Docker Compose 到 V2 版本(前文二、4 小节已详细说明),升级后即可支持所有配置。
坑 5:MySQL 容器时间比北京时间慢 8 小时
-
现象:MySQL 中查询当前时间(
select now();),比北京时间慢 8 小时,影响项目时间相关功能; -
排查:Docker 容器默认使用 UTC 世界标准时间,国内使用东八区(CST),两者相差 8 小时;
-
解决方案:
-
给 MySQL 服务添加环境变量
TZ: Asia/Shanghai,强制容器时区为东八区(核心); -
挂载宿主机的时区文件(
/etc/localtime:/etc/localtime:ro),双重保障时区同步; -
给后端、Nginx、Redis 容器也添加相同的
TZ环境变量,实现全服务时间统一; -
重建 MySQL 容器(环境变量修改后,单纯 restart 不生效,需
docker-compose stop mysql && docker-compose rm -f mysql && docker-compose up -d mysql)。
-

坑 6:虚拟机重启后,所有 Docker 服务都需要手动启动
- 现象:虚拟机重启后,Docker 服务和所有容器都停止了,需要手动执行
systemctl start docker和docker-compose up -d; - 排查:Docker 服务未设置开机自启,容器的
restart: always配置,需要 Docker 服务启动后才会生效; - 解决方案:设置 Docker 服务开机自启,命令:
systemctl enable docker,后续虚拟机重启后,Docker 会自动启动,所有容器也会自动后台启动。
坑 7:本地打包镜像后,服务器导入提示 "no such image"
- 现象:执行
docker load -i rdif-all-images.tar时,提示镜像不存在; - 排查:本地拉取的镜像版本和服务器 docker-compose.yml 中配置的版本不一致,或打包时镜像名拼写错误;
- 解决方案:本地执行
docker images确认镜像名和版本,确保和 docker-compose.yml 中完全一致,重新打包并上传。
坑 8:虚拟机重启后IP变更问题
首先查看本地网络配置,如下参考:

虚拟机网络适配器设置为桥接模式。

在linux中执行命令:
bash
vim /etc/sysconfig/network-scripts/ifcfg-ens33

保存后重启网络服务
bash
systemctl restart network
八、常用 Docker/Compose 管理命令(日常运维必备)
部署完成后,日常维护、排错,都会用到以下命令,整理好放在这里,方便后续使用(所有命令均亲测可用)。
(一)Docker 基础命令(全局通用)
bash
# 1. Docker服务管理(核心,开机自启/启停)
systemctl start docker # 启动Docker服务
systemctl stop docker # 停止Docker服务
systemctl restart docker # 重启Docker服务
systemctl enable docker # Docker开机自启(永久生效)
systemctl disable docker # 关闭Docker开机自启
systemctl status docker # 查看Docker服务状态
systemctl is-enabled docker # 检查Docker是否开机自启(输出enabled则是)
# 2. 容器管理(日常排错、启停常用)
docker ps # 查看**运行中**的容器
docker ps -a # 查看**所有**容器(运行+停止)
docker start <容器名/ID> # 启动指定容器(用容器名,更方便)
docker stop <容器名/ID> # 停止指定容器
docker restart <容器名/ID> # 重启指定容器(如后端容器:docker restart rdif-backend)
docker rm <容器名/ID> # 删除指定停止的容器
docker rm -f <容器名/ID> # 强制删除**运行中**的容器(谨慎使用)
docker exec -it <容器名/ID> /bin/bash # 进入容器交互终端(Alpine镜像用sh替代/bin/bash)
docker logs <容器名/ID> # 查看容器日志(最新)
docker logs -f <容器名/ID> # 实时查看容器日志(排错核心,重点)
docker logs --tail 100 <容器名/ID> # 查看容器最后100行日志
# 3. 镜像管理(离线导入/打包常用)
docker images # 查看本地所有镜像
docker rmi <镜像名/ID> # 删除指定镜像(无容器依赖时)
docker rmi -f <镜像名/ID> # 强制删除镜像(忽略容器依赖,谨慎,如:docker rmi -f mysql:8.0)
docker load -i <镜像tar包> # 离线导入镜像(如:docker load -i rdif-all-images.tar)
docker save -o <输出tar包> <镜像名> # 打包本地镜像为tar包(如:docker save -o mysql8.tar mysql:8.0)
docker pull <镜像名:版本> # 拉取远程镜像
docker system prune -f # 清理无用容器/镜像/缓存(无风险,推荐定期执行)
# 4. 数据卷/网络管理(本次自动创建,按需查看)
docker volume ls # 查看所有Docker数据卷(保存MySQL/Redis数据)
docker network ls # 查看所有Docker网络(本次为rdif-network)
(二)Docker Compose 命令(项目管理核心,需在项目根目录执行)
bash
# 1. 核心启停命令(最常用)
docker-compose up -d # 后台启动所有服务(无构建,本次核心用)
docker-compose up -d --build # 后台启动+构建镜像(有Dockerfile时用)
docker-compose down # 停止并删除所有容器(保留数据卷/镜像,安全)
docker-compose stop # 仅停止所有容器(不删除,容器仍存在)
docker-compose start # 启动已停止的所有容器
docker-compose restart # 重启所有服务(配置未改时用)
docker-compose restart backend # 重启指定服务(如后端,无需重启所有服务)
# 2. 状态/日志查看(排错常用)
docker-compose ps # 查看所有服务运行状态(看是否Up)
docker-compose logs -f # 实时查看所有服务日志
docker-compose logs -f <服务名> # 实时查看指定服务日志(如docker-compose logs -f backend,实时查看后端日志(排错重点)
# 3. 其他常用
docker-compose config # 验证docker-compose.yml配置语法是否正确(避免配置错误)
docker-compose rm <服务名> # 删除指定停止的服务容器
九、开机自启配置(无需手动干预,虚拟机重启自动恢复)
前面已经提到,这里再单独整理一遍,确保虚拟机重启后,所有服务自动启动,无需手动操作:
bash
# 1. 确保Docker服务开机自启(已配置,再次验证)
systemctl enable docker
systemctl is-enabled docker # 输出enabled即成功
# 2. 验证容器自动重启策略(docker-compose.yml已配置restart: always)
docker-compose config | grep -i restart # 输出4个restart: always即正常
# 3. 测试验证(可选,手动重启虚拟机)
reboot # 重启虚拟机,重新连接后,直接访问服务,确认能正常打开
✅ 成功标识:虚拟机重启后,无需执行任何命令,docker-compose ps显示所有服务都是 Up 状态,前端、后端、Swagger 都能正常访问。
十、前后端更新方法(日常维护,简单高效)
后续需要更新前端或后端代码时,无需重新部署整个环境,直接替换打包 / 发布文件,重启对应服务即可,步骤如下:
1. 后端更新(VS2022+ 重新发布后)
- 本地 VS2022+ 重新发布.NET8 项目,生成新的 publish 文件夹;
- 用 Xftp 工具,将新的 publish 文件夹,覆盖服务器
/root/rdif-docker/backend/下的旧 publish 文件夹; - 重启后端容器,更新生效:
bash
cd /root/rdif-docker
docker-compose restart backend
2. 前端更新(Vue3 重新打包后)
- 本地 Vue3 项目重新打包(yarn build/npm run build),生成新的 dist 文件夹;
- 用 Xftp 工具,将新的 dist 文件夹,覆盖服务器
/root/rdif-docker/frontend/下的旧 dist 文件夹; - 重启 Nginx 容器,更新生效:
bash
cd /root/rdif-docker
docker-compose restart nginx
十一、总结与注意事项(最后提醒,避坑收尾)
总结
本次部署基于Docker Compose实现了.NET8 后端(已发布)+Vue 前端(已打包)+MySQL8+Redis+Nginx 的整套多服务容器化部署,全程亲测可复现,核心亮点:
- 采用离线镜像导入方案,解决服务器网络拉取镜像超时问题,附本地拉取 + 打包完整步骤,新手也能上手;
- 前后端无需容器内编译构建,直接挂载本地打包 / 发布文件,启动更快、更新更方便,适合已完成本地开发的项目;
- 解决了端口不通、MySQL 连接失败、时区时差、容器启动顺序、配置兼容、开机自启等 6 大核心问题,附详细避坑方案;
- 所有配置集中在
docker-compose.yml,一键启停、易维护、易迁移,虚拟机重启后服务自动恢复,无需手动干预; - 全程采用轻量版镜像(alpine),降低服务器资源占用,适合个人测试 / 小型项目部署。
注意事项(重点避坑,生产环境必看)
-
版本强一致:本地拉取的镜像版本、docker-compose.yml 中的镜像版本、后端 / 前端的运行依赖版本,必须完全一致,避免兼容问题;
-
容器间通信规则 :同一 Docker 自定义网络内,服务间访问必须用容器名 / 服务名 (如后端连 MySQL 用
server=mysql),而非服务器 IP;本地工具(Navicat/Redis 客户端)连接才用服务器 IP; -
环境变量修改规则 :修改容器的环境变量(如 TZ、ASPNETCORE_URLS、密码)后,必须重建容器 (
stop+rm+up -d),单纯restart不会加载新配置; -
数据持久化 :MySQL/Redis 数据通过 Docker数据卷 持久化,
docker-compose down不会删除数据卷,但手动执行docker volume rm会丢失数据,谨慎操作; -
敏感信息保护 :本文中的 IP、密码、镜像加速地址均为虚拟示例,实际部署请替换为自己的真实信息,并设置复杂密码(字母 + 数字 + 特殊符号);
-
生产环境优化
:本文为个人测试环境
配置,生产环境需做以下优化:
- 开启防火墙,仅开放项目所需端口(6866/58588/3306),执行
firewall-cmd --add-port=端口/tcp --permanent && firewall-cmd --reload; - 禁用 root 账号直接登录,创建普通用户并赋予 Docker 操作权限;
- 配置 SSL 证书,实现 HTTPS 访问;
- 对 MySQL/Redis 做主从复制 / 集群,提升可用性;
- 开启防火墙,仅开放项目所需端口(6866/58588/3306),执行
-
排错核心思路 :任何服务启动失败、访问不通,优先查看容器日志 (
docker-compose logs -f 服务名),日志是定位问题的最直接依据;

- 镜像备份:服务器端的离线镜像 tar 包建议保留,后续服务器重装可直接导入,无需重新本地打包。
整个部署过程虽然踩了不少坑,但核心问题都是忽略了容器化的细节(如端口匹配、时区配置、容器通信规则、启动顺序),只要按步骤做好配置、逐一验证,就能顺利完成部署。Docker Compose 的编排能力让多服务部署变得简单,也是后端开发必备的技能之一。
希望这篇详细的部署记录,能帮到有同样需求的朋友,少走弯路,高效完成.NET8+Vue 项目的 Docker 容器化部署。如果大家在部署过程中遇到其他问题,欢迎在评论区留言,一起交流解决~
十二、结语
如果本文对你有一点点帮助,点个赞支持一下吧,你的每一个【赞】都是我创作的最大动力 _
更多技术文章请往:
http://www.guosisoft.com/article
http://www.rdiframework.net/article
大家一起共同交流和进步呀!!