文章目录
-
- [1. 前置知识:两个 Compose 文件与核心概念](#1. 前置知识:两个 Compose 文件与核心概念)
- [2. 部署前准备(仅第一次需要关注)](#2. 部署前准备(仅第一次需要关注))
-
- [2.1 目录命名唯一性(必须严格遵守)](#2.1 目录命名唯一性(必须严格遵守))
- [2.2 本地源码准备](#2.2 本地源码准备)
- [3. 场景一:首次完整部署(后端 + 数据库 + 缓存等所有服务)](#3. 场景一:首次完整部署(后端 + 数据库 + 缓存等所有服务))
-
- [3.1 本地 WSL:构建全部镜像并验证](#3.1 本地 WSL:构建全部镜像并验证)
- [3.2 准备生产环境用的 Compose 文件](#3.2 准备生产环境用的 Compose 文件)
- [3.3 上传文件到生产服务器(通过 SSH/SCP)](#3.3 上传文件到生产服务器(通过 SSH/SCP))
- [3.4 生产服务器:加载镜像并启动所有服务](#3.4 生产服务器:加载镜像并启动所有服务)
- [4. 场景二:仅更新后端服务(日常发版)](#4. 场景二:仅更新后端服务(日常发版))
-
- [4.1 本地 WSL:构建新后端镜像并导出](#4.1 本地 WSL:构建新后端镜像并导出)
- [4.2 上传到服务器](#4.2 上传到服务器)
- [4.3 生产服务器:替换后端容器(一键完成)](#4.3 生产服务器:替换后端容器(一键完成))
- [5. 总结流程图](#5. 总结流程图)
1. 前置知识:两个 Compose 文件与核心概念
为了安全、稳定地部署,我们需要区分开发环境 和生产环境的 compose 文件。
| 文件 | 用途 | 核心字段 |
|---|---|---|
docker-compose.yml |
本地开发,放在项目根目录 | 包含 build,每次启动时从源码构建镜像 |
docker-compose.image.yml |
生产部署,需自行编写并上传至服务器 | 只有 image,禁止 build,直接使用已导出的镜像 |
build与image的区别
build:告诉 Docker "请根据这个目录下的 Dockerfile 现场编译一个镜像"。适合开发时频繁修改代码。image:告诉 Docker "直接使用这个已制作好的镜像,不要编译"。生产服务器没有源代码,必须用这种方式。
重要提醒:生产 compose 文件必须按实际项目编写本文档中出现的
docker-compose.image.yml内容仅为示例 ,你需要根据自己项目的服务名、端口、环境变量、卷挂载等进行修改。做法很简单:复制一份docker-compose.yml,删除所有build字段,并为每个服务补上image(镜像名从docker images获取)。
2. 部署前准备(仅第一次需要关注)
2.1 目录命名唯一性(必须严格遵守)
在生产服务器上,存放项目的父目录名称必须唯一 ,例如 /home/lasahub/AT133_QS_MV_safety_Check/backend_at133。
为什么? Docker Compose 会使用当前目录名作为"项目名",如果两个项目目录都叫 backend,它们的容器名、网络名就会冲突,导致一个项目启动时意外破坏另一个项目的容器。
✅ 正确:
/home/user/project_a_backend、/home/user/project_b_backend❌ 错误:
/home/user/backend(多个项目都用这个目录名会互相干扰)
2.2 本地源码准备
将你的后端项目文件夹从 Windows 复制到 WSL 用户目录下(例如 \\wsl.localhost\Ubuntu-22.04\home\xjm)。
复制后务必删除 venv、.idea 等非必要文件夹,避免影响镜像构建速度和体积。
3. 场景一:首次完整部署(后端 + 数据库 + 缓存等所有服务)
3.1 本地 WSL:构建全部镜像并验证
步骤 1:进入项目目录
bash
cd ~/ruoyi-fastapi-backend
(示例:实际目录名可能是 ruoyi-fastapi-backend 或其他,请根据你的项目修改)
步骤 2:构建并启动所有服务
bash
docker compose up -d --build
这个命令会读取 docker-compose.yml,根据其中的 build 指令构建后端、MySQL、Redis 等镜像,并以后台模式启动所有容器。
步骤 3:查看容器名称与日志
bash
docker ps
输出中 NAMES 列会显示当前运行的容器名,例如 banked-backend-AT133、banked-mysql-1 等。
查看后端日志,确认无报错并正常监听端口:
bash
docker logs -f banked-backend-AT133
(将容器名替换为你实际看到的名称)
看到类似 "Application startup complete" 或端口监听信息后按 Ctrl+C 退出。
步骤 4:导出所有需要的镜像
先列出本地构建出的镜像:
bash
docker images
你会看到类似如下镜像(命名规则为 项目目录名_服务名:latest):
ruoyi-fastapi-backend-banked:latestruoyi-fastapi-backend-mysql:latestruoyi-fastapi-backend-redis:latest
将所有业务镜像打包成一个压缩文件(文件名随意,这里以 all-images.tar.gz 为例):
bash
docker save ruoyi-fastapi-backend-banked:latest \
ruoyi-fastapi-backend-mysql:latest \
ruoyi-fastapi-backend-redis:latest \
| gzip > all-images.tar.gz
请用你实际 docker images 显示的镜像名替换。如果基础服务使用的是官方镜像(如 mysql:8.0)且未自定义,也可不导出,让服务器自行拉取;若服务器无外网,则需提前 docker pull mysql:8.0 后一并导出。
3.2 准备生产环境用的 Compose 文件
基于你自己的 docker-compose.yml,创建 docker-compose.image.yml,核心改动:
- 删除所有服务的
build配置; - 为每个服务添加
image,镜像名必须与刚才导出的完全一致。
示例(请根据你的实际服务名、端口、环境变量等修改!):
yaml
# docker-compose.image.yml (示例,请按需修改)
version: '3.8'
services:
banked:
image: ruoyi-fastapi-backend-banked:latest
container_name: banked-backend-AT133
ports:
- "8000:8000"
environment:
- DB_HOST=mysql
- REDIS_HOST=redis
depends_on:
- mysql
- redis
restart: unless-stopped
mysql:
image: ruoyi-fastapi-backend-mysql:latest
container_name: banked-mysql-AT133
environment:
MYSQL_ROOT_PASSWORD: yourpassword
MYSQL_DATABASE: yourdb
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
redis:
image: ruoyi-fastapi-backend-redis:latest
container_name: banked-redis-AT133
restart: unless-stopped
volumes:
mysql_data:
3.3 上传文件到生产服务器(通过 SSH/SCP)
将压缩包和 docker-compose.image.yml 上传到服务器的唯一项目目录 (例如 /home/lasahub/AT133_QS_MV_safety_Check/backend_at133):
bash
scp all-images.tar.gz lasahub@<服务器IP>:/home/lasahub/AT133_QS_MV_safety_Check/backend_at133/
scp docker-compose.image.yml lasahub@<服务器IP>:/home/lasahub/AT133_QS_MV_safety_Check/backend_at133/
3.4 生产服务器:加载镜像并启动所有服务
登录服务器并进入项目目录:
bash
cd /home/lasahub/AT133_QS_MV_safety_Check/backend_at133
加载镜像:
bash
gunzip -c all-images.tar.gz | sudo docker load
验证镜像是否加载成功:
bash
sudo docker images
启动所有服务(注意:这里绝对不要加 --build ,因为 compose 文件里已经全是 image 了):
bash
sudo docker compose -f docker-compose.image.yml up -d
检查后端日志,确认服务正常运行:
bash
sudo docker ps
sudo docker logs -f banked-backend-AT133
(容器名以实际为准)
4. 场景二:仅更新后端服务(日常发版)
当代码有修改,只需要更新后端时,数据库等其他服务不受任何影响。
4.1 本地 WSL:构建新后端镜像并导出
bash
cd ~/ruoyi-fastapi-backend
# 只重新构建后端镜像,不重新创建容器(banked 是 docker-compose.yml 中定义的后端服务名)
docker compose up -d --build banked
# 导出新镜像(压缩包名可任意,例如 backend-update.tar.gz)
docker save ruoyi-fastapi-backend-banked:latest | gzip > backend-update.tar.gz
关于服务名
banked:
banked来自于docker-compose.yml文件中services块下定义的服务名称,例如:
yamlservices: banked: build: . ... mysql: ...你的项目中服务名可能叫
backend、app等,请以docker-compose.yml中实际定义的名称为准,使用时替换即可。
4.2 上传到服务器
bash
scp backend-update.tar.gz lasahub@<服务器IP>:/home/lasahub/AT133_QS_MV_safety_Check/backend_at133/
4.3 生产服务器:替换后端容器(一键完成)
bash
cd /home/lasahub/AT133_QS_MV_safety_Check/backend_at133
# 加载新镜像
gunzip -c backend-update.tar.gz | sudo docker load
# 强制重建后端容器(自动停止旧容器、创建新容器)
sudo docker compose -f docker-compose.image.yml up -d --force-recreate banked
# 查看日志确认
sudo docker logs -f banked-backend-AT133
解释 :
--force-recreate参数会强制 Compose 重新创建容器,即使配置看起来没变。因为我们刚导入了新的latest镜像,这样就能确保新容器使用最新版本,且无需手动执行docker stop/rm。
5. 总结流程图
首次完整部署流程
#mermaid-svg-SNS5Cr95zFAdleT6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SNS5Cr95zFAdleT6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SNS5Cr95zFAdleT6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SNS5Cr95zFAdleT6 .error-icon{fill:#552222;}#mermaid-svg-SNS5Cr95zFAdleT6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SNS5Cr95zFAdleT6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SNS5Cr95zFAdleT6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SNS5Cr95zFAdleT6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SNS5Cr95zFAdleT6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SNS5Cr95zFAdleT6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SNS5Cr95zFAdleT6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SNS5Cr95zFAdleT6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SNS5Cr95zFAdleT6 .marker.cross{stroke:#333333;}#mermaid-svg-SNS5Cr95zFAdleT6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SNS5Cr95zFAdleT6 p{margin:0;}#mermaid-svg-SNS5Cr95zFAdleT6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SNS5Cr95zFAdleT6 .cluster-label text{fill:#333;}#mermaid-svg-SNS5Cr95zFAdleT6 .cluster-label span{color:#333;}#mermaid-svg-SNS5Cr95zFAdleT6 .cluster-label span p{background-color:transparent;}#mermaid-svg-SNS5Cr95zFAdleT6 .label text,#mermaid-svg-SNS5Cr95zFAdleT6 span{fill:#333;color:#333;}#mermaid-svg-SNS5Cr95zFAdleT6 .node rect,#mermaid-svg-SNS5Cr95zFAdleT6 .node circle,#mermaid-svg-SNS5Cr95zFAdleT6 .node ellipse,#mermaid-svg-SNS5Cr95zFAdleT6 .node polygon,#mermaid-svg-SNS5Cr95zFAdleT6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SNS5Cr95zFAdleT6 .rough-node .label text,#mermaid-svg-SNS5Cr95zFAdleT6 .node .label text,#mermaid-svg-SNS5Cr95zFAdleT6 .image-shape .label,#mermaid-svg-SNS5Cr95zFAdleT6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-SNS5Cr95zFAdleT6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SNS5Cr95zFAdleT6 .rough-node .label,#mermaid-svg-SNS5Cr95zFAdleT6 .node .label,#mermaid-svg-SNS5Cr95zFAdleT6 .image-shape .label,#mermaid-svg-SNS5Cr95zFAdleT6 .icon-shape .label{text-align:center;}#mermaid-svg-SNS5Cr95zFAdleT6 .node.clickable{cursor:pointer;}#mermaid-svg-SNS5Cr95zFAdleT6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SNS5Cr95zFAdleT6 .arrowheadPath{fill:#333333;}#mermaid-svg-SNS5Cr95zFAdleT6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SNS5Cr95zFAdleT6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SNS5Cr95zFAdleT6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SNS5Cr95zFAdleT6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SNS5Cr95zFAdleT6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SNS5Cr95zFAdleT6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SNS5Cr95zFAdleT6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SNS5Cr95zFAdleT6 .cluster text{fill:#333;}#mermaid-svg-SNS5Cr95zFAdleT6 .cluster span{color:#333;}#mermaid-svg-SNS5Cr95zFAdleT6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SNS5Cr95zFAdleT6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SNS5Cr95zFAdleT6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-SNS5Cr95zFAdleT6 .icon-shape,#mermaid-svg-SNS5Cr95zFAdleT6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SNS5Cr95zFAdleT6 .icon-shape p,#mermaid-svg-SNS5Cr95zFAdleT6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SNS5Cr95zFAdleT6 .icon-shape .label rect,#mermaid-svg-SNS5Cr95zFAdleT6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SNS5Cr95zFAdleT6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SNS5Cr95zFAdleT6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SNS5Cr95zFAdleT6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 复制源码到 WSL
删除 venv/.idea
进入项目目录
docker compose up -d --build
docker ps 查看容器名
docker logs -f 确认启动
docker images 查看镜像名
docker save 打包所有镜像
编写 docker-compose.image.yml
去掉 build,只用 image
scp 上传压缩包和 compose 文件
服务器解压加载镜像
docker compose -f docker-compose.image.yml up -d
查看日志验证部署
仅更新后端流程
#mermaid-svg-RbQXG2lETsJsuQCW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RbQXG2lETsJsuQCW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RbQXG2lETsJsuQCW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RbQXG2lETsJsuQCW .error-icon{fill:#552222;}#mermaid-svg-RbQXG2lETsJsuQCW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RbQXG2lETsJsuQCW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RbQXG2lETsJsuQCW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RbQXG2lETsJsuQCW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RbQXG2lETsJsuQCW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RbQXG2lETsJsuQCW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RbQXG2lETsJsuQCW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RbQXG2lETsJsuQCW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RbQXG2lETsJsuQCW .marker.cross{stroke:#333333;}#mermaid-svg-RbQXG2lETsJsuQCW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RbQXG2lETsJsuQCW p{margin:0;}#mermaid-svg-RbQXG2lETsJsuQCW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RbQXG2lETsJsuQCW .cluster-label text{fill:#333;}#mermaid-svg-RbQXG2lETsJsuQCW .cluster-label span{color:#333;}#mermaid-svg-RbQXG2lETsJsuQCW .cluster-label span p{background-color:transparent;}#mermaid-svg-RbQXG2lETsJsuQCW .label text,#mermaid-svg-RbQXG2lETsJsuQCW span{fill:#333;color:#333;}#mermaid-svg-RbQXG2lETsJsuQCW .node rect,#mermaid-svg-RbQXG2lETsJsuQCW .node circle,#mermaid-svg-RbQXG2lETsJsuQCW .node ellipse,#mermaid-svg-RbQXG2lETsJsuQCW .node polygon,#mermaid-svg-RbQXG2lETsJsuQCW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RbQXG2lETsJsuQCW .rough-node .label text,#mermaid-svg-RbQXG2lETsJsuQCW .node .label text,#mermaid-svg-RbQXG2lETsJsuQCW .image-shape .label,#mermaid-svg-RbQXG2lETsJsuQCW .icon-shape .label{text-anchor:middle;}#mermaid-svg-RbQXG2lETsJsuQCW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RbQXG2lETsJsuQCW .rough-node .label,#mermaid-svg-RbQXG2lETsJsuQCW .node .label,#mermaid-svg-RbQXG2lETsJsuQCW .image-shape .label,#mermaid-svg-RbQXG2lETsJsuQCW .icon-shape .label{text-align:center;}#mermaid-svg-RbQXG2lETsJsuQCW .node.clickable{cursor:pointer;}#mermaid-svg-RbQXG2lETsJsuQCW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RbQXG2lETsJsuQCW .arrowheadPath{fill:#333333;}#mermaid-svg-RbQXG2lETsJsuQCW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RbQXG2lETsJsuQCW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RbQXG2lETsJsuQCW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RbQXG2lETsJsuQCW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RbQXG2lETsJsuQCW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RbQXG2lETsJsuQCW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RbQXG2lETsJsuQCW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RbQXG2lETsJsuQCW .cluster text{fill:#333;}#mermaid-svg-RbQXG2lETsJsuQCW .cluster span{color:#333;}#mermaid-svg-RbQXG2lETsJsuQCW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RbQXG2lETsJsuQCW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RbQXG2lETsJsuQCW rect.text{fill:none;stroke-width:0;}#mermaid-svg-RbQXG2lETsJsuQCW .icon-shape,#mermaid-svg-RbQXG2lETsJsuQCW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RbQXG2lETsJsuQCW .icon-shape p,#mermaid-svg-RbQXG2lETsJsuQCW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RbQXG2lETsJsuQCW .icon-shape .label rect,#mermaid-svg-RbQXG2lETsJsuQCW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RbQXG2lETsJsuQCW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RbQXG2lETsJsuQCW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RbQXG2lETsJsuQCW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 修改代码
cd 项目目录
docker compose up -d --build banked
docker save 导出后端镜像
scp 上传到服务器
服务器加载新镜像
docker compose -f docker-compose.image.yml up -d --force-recreate banked
查看日志确认
遵循以上步骤,任何新手都能安全、规范地完成 Docker 部署。如有疑问,请参照各项目实际配置调整 compose 文件中的镜像名和服务名。