一、 前言
简单介绍项目背景:这是一个前后端分离的社区项目,后端使用 Spring Boot,前端使用 Vue,数据库使用 MySQL,缓存使用 Redis。 记录将原本在本地运行的项目,迁移到阿里云 CentOS 服务器并通过 Docker Compose 一键编排的过程。
二、 项目目录结构
首先规划好服务器上的目录结构,清晰明了:
/usr/local/src/docker/aicommunity
├── backend
│ ├── Dockerfile
│ └── app.jar
├── frontend
│ ├── Dockerfile
│ ├── nginx.conf
│ └── dist/
├── database
│ ├── init/ (存放 .sql 初始化脚本)
│ └── data/ (存放数据库文件挂载)
└── docker-compose.yml
三、 核心配置文件 (经过千锤百炼的最终版)
1. docker-compose.yml
重点说明: 解决了服务依赖、端口映射冲突、以及 Redis 的加入。
version: '3.8'
services:
# 1. MySQL 服务
mysqldb:
image: mysql:8.0
container_name: mysqldb
restart: always
environment:
MYSQL_ROOT_PASSWORD: 你的强密码
MYSQL_DATABASE: community # 自动创建库名
ports:
- "3307:3306" # 宿主机3307,防止和本机MySQL冲突
volumes:
- ./database/data:/var/lib/mysql
- ./database/init:/docker-entrypoint-initdb.d # 初始化脚本挂载
# 2. Redis 服务 (验证码必备)
redis:
image: redis:alpine
container_name: redis-server
restart: always
ports:
- "6300:6379" # 宿主机用6300查看,容器内部还是6379
# 3. 后端服务
backend:
build: ./backend
container_name: backend-app
restart: always
ports:
- "8086:8080"
depends_on:
- mysqldb
- redis
environment:
SERVER_PORT: 8080 # 强制容器内端口为8080
# 数据库连接 (注意 host 写服务名 mysqldb)
SPRING_DATASOURCE_URL: jdbc:mysql://mysqldb:3306/community?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: 你的强密码
# Redis 连接 (注意 host 写服务名 redis,端口写内部端口 6379)
SPRING_REDIS_HOST: redis
SPRING_REDIS_PORT: 6379
# 4. 前端服务
frontend:
build: ./frontend
container_name: frontend-app
restart: always
ports:
- "86:80"
depends_on:
- backend
2. 后端 Dockerfile (解决验证码报错的关键)
重点说明: Alpine 镜像默认没有字体,会导致 EasyCaptcha 报 NullPointerException。
FROM openjdk:8-jdk-alpine
# ✅ 关键一步:安装字体库,解决验证码无法生成的问题
RUN apk add --no-cache fontconfig ttf-dejavu
WORKDIR /app
COPY *.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
3. 前端 Nginx 配置 (解决跨域与路径问题)
重点说明: /prod-api/ 的反向代理配置。
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# 反向代理后端接口
location /prod-api/ {
# 注意:这里 proxy_pass 结尾加 / 表示去掉前缀
proxy_pass http://backend:8080/;
}
}
四、 部署过程中遇到的那些"坑" (精华部分)
这部分是博客最有价值的地方,把您遇到的错误和解法列出来:
坑点 1:Docker 镜像拉取失败 (403 Forbidden)
-
现象:
openjdk:8-jdk-alpine拉不下来。 -
原因: 国内 Docker 源不稳定。
-
解决: 配置阿里云镜像加速器,修改
/etc/docker/daemon.json。
坑点 2:后端连不上数据库 (Host 误区)
-
现象: 报错
Communications link failure。 -
原因: 以前在本地写
localhost:3306,但在 Docker 里,localhost指的是容器自己。 -
解决: 将连接地址改为 Docker Compose 服务名
jdbc:mysql://mysqldb:3306/...。
坑点 3:缺少 Redis 导致 502 Bad Gateway
-
现象: 前端访问验证码报 502,后端日志显示连接拒绝。
-
原因: 忘记部署 Redis 容器,或者后端配置里没有指定 Redis 地址。
-
解决: 在 compose 文件中添加 Redis 服务,并配置
SPRING_REDIS_HOST: redis。
坑点 4:验证码报空指针异常 (NullPointerException)
-
现象: 后端报错
java.lang.NullPointerException at sun.awt.FontConfiguration。 -
原因: 使用了 Alpine 极简镜像,系统里没有字体库,Java 的 AWT 绘图功能无法使用。
-
解决: 修改 Dockerfile,添加
RUN apk add --no-cache fontconfig ttf-dejavu。
坑点 5:端口映射的认知误区
-
现象: Redis 映射为
6300:6379,后端去连6300连不上。 -
原因: Docker 容器间通信(Internal)使用的是原始端口
6379,映射端口6300是给宿主机外部用的。 -
解决: 后端配置文件里
SPRING_REDIS_PORT必须写6379。
坑点 6:数据库初始化脚本不执行
-
现象:
init.sql挂载了,但表没创建。 -
原因: MySQL 容器只有在数据目录为空时才会执行初始化脚本。如果之前启动过一次,已经有数据了,它就会忽略脚本。
-
解决:
docker compose down后,手动删除挂载的数据目录rm -rf ./database/data/*,再重新启动。
五、 最终启动命令
# 1. 一键构建并启动
docker compose up -d --build
# 2. 查看所有服务状态
docker compose ps
# 3. 查看后端日志 (排错必备)
docker compose logs -f backend
六、 总结
容器化部署虽然初期配置麻烦,通过 Docker Compose,我们成功实现了"一次配置,到处运行"。这次经历让我深刻理解了 Docker 网络通信机制、镜像构建细节以及 Linux 环境差异带来的影响。
Dockerfile是Docker Compose之间的区别
Dockerfile 是用来"造人"的(构建镜像),Docker Compose 是用来"排兵布阵"的(管理运行)。
1. 核心定义区别
| 特性 | Dockerfile | Docker Compose (docker-compose.yml) |
|---|---|---|
| 作用对象 | 单个镜像 (Image) | 多容器应用 (Multi-Container App) |
| 核心职责 | 构建 (Build):决定容器里装什么软件、什么代码、什么环境。 | 编排 (Orchestrate):决定怎么启动容器、端口怎么开、容器间怎么连。 |
| 执行时机 | 在容器运行之前(打包阶段)。 | 在容器运行之时(启动阶段)。 |
| 对应命令 | docker build |
docker compose up |
| 比喻 | 食谱:决定了一道菜里放什么料(盐、糖、肉)。 | 套餐菜单:决定了一桌饭包含哪些菜(汉堡+薯条+可乐),以及怎么摆盘。 |
2. 深度功能对比
🛠 Dockerfile:关注"内部"
它只关心容器里面有什么。
-
安装软件: 比如
RUN apk add fontconfig(你刚才加字体就是改这里)。 -
复制代码: 比如
COPY *.jar app.jar。 -
配置环境: 比如
ENV JAVA_HOME /usr/jdk。 -
启动命令: 比如
ENTRYPOINT ["java", "-jar", "app.jar"]。 -
特点: 一旦构建成镜像,里面内容就固定了,不可变。
🕹 Docker Compose:关注"外部"与"关系"
它关心容器之间 怎么配合,以及和宿主机的关系。
-
端口映射: 比如
ports: "8086:8080"(把内部的8080暴露给外部8086)。 -
依赖关系: 比如
depends_on: - redis(先启 Redis 再启后端)。 -
环境变量注入: 比如
SPRING_REDIS_HOST: redis(告诉后端 Redis 在哪)。 -
数据挂载: 比如
volumes: ./data:/var/lib/mysql(把数据存在外面)。 -
特点: 它可以随时修改,改完重启容器就生效,不需要重新编译代码。
3. 结合你刚才的实战案例
回想一下你刚刚遇到的两个经典报错,最能说明问题:
-
案例一:验证码报错(缺字体)
-
问题: 操作系统里没字体库。
-
修改位置: Dockerfile。
-
为什么? 因为这是镜像内部缺少了基础软件,必须重修"重新制造"这个镜像(Rebuild)。
-
-
案例二:连不上 Redis(502/500错误)
-
问题: 没启动 Redis 容器,或者后端不知道 Redis 的地址。
-
修改位置: docker-compose.yml。
-
为什么? 因为这是架构问题。你需要增加一个服务(Redis),并配置后端怎么去连接它。这不需要修改代码,也不需要重新安装 Linux 软件,只是调整"兵力部署"。
-
4. 它们的关系图
可以想象成一个建筑工程:
-
Dockerfile 是预制板工厂的图纸。
-
负责生产标准的"墙板"、"地板"、"窗户"(即镜像)。
-
工厂里只管把东西造好,不管这块板子将来装在哪。
-
-
Docker Compose 是施工现场的蓝图。
-
它指挥工人:"把这块墙板(后端镜像)立在这里,把那个地板(数据库镜像)铺在那里"。
-
它负责接通水电(网络),开好门窗(端口)。
-
5. 总结口诀(送给你的博客)
-
要改环境、装软件、换代码 ------ 找 Dockerfile。
-
要改端口、配网络、加数据库 ------ 找 Docker Compose。
-
Dockerfile 造出了"演员",Docker Compose 搭建了"舞台"并指挥"演出"。