【Docker】实战用例:前后端分离项目多容器Docker化设计

【Docker】实战用例:前后端分离项目多容器Docker化设计 实战用例多容器Docker化设计本文将通过一个具体的电商项目用例详细展示如何将 Spring Boot 后端、Vue 管理端、Uniapp 用户端H5以及 MySQL 数据库 进行容器化并使用 Docker Compose 进行统一管理。1. 项目背景与需求将四个部分分别容器化并通过一个命令启动整个系统同时保证开发与生产环境的一致性。Java 后端提供 RESTful API使用 Spring Boot 框架连接 MySQL 数据库Vue 管理端基于 Vue 3 的后台管理系统供管理员操作Uniapp 用户端基于 uni-app 开发的跨端应用H5 小程序用户通过浏览器访问 H5 版本MySql 数据库MySQL 8.0存储业务数据。2. 服务拆分与容器化设计根据项目需求将系统拆分为四个独立服务每个服务运行在独立的容器中通过 Docker Compose 统一编排。设计如下2.1 数据库独立有状态服务为什么独立数据库是有状态服务需要持久化存储不能随着容器的重启而丢失数据。实现方式使用官方 MySQL 8.0 镜像通过 Docker 卷volume挂载数据库文件到宿主机保证数据持久化。健康检查设置健康检查确保后端服务在数据库真正就绪后再启动避免因数据库未准备好导致后端启动失败。2.2 Java 后端无状态服务为什么无状态后端应用不保存用户会话等状态数据可以横向扩展容器重启不影响业务。实现方式将 Spring Boot 应用打包为JAR包运行在 JRE 环境中。采用多阶段构建第一阶段使用Maven镜像编译打包第二阶段使用轻量级JRE镜像运行显著减小最终镜像体积。通过环境变量注入数据库连接信息如数据库地址、用户名、密码实现配置与镜像分离镜像可在不同环境开发、测试、生产复用。注意事项Spring Boot 的配置文件如application.properties中应使用占位符${}引用环境变量这样容器运行时可以通过-e或 Compose 的environment注入实际值。2.3 前端静态服务Vue 管理端 Uniapp 用户端为什么用 Nginx 托管静态文件前端项目经过构建后生成的是纯静态文件HTML、CSS、JS使用高性能的Nginx作为 Web 服务器既能高效地处理静态资源请求又能方便地配置反向代理解决跨域问题。实现方式同样采用多阶段构建第一阶段使用Node镜像安装依赖并执行构建命令生成dist目录或类似输出目录根据实际项目配置调整第二阶段将构建产物复制到Nginx镜像的静态文件目录如 /usr/share/nginx/html自定义Nginx配置添加反向代理规则将前端发往 /api 的请求转发到后端服务从而绕过浏览器的同源策略限制。2.4 网络与通信容器网络所有服务置于同一个自定义Docker网络如 ecommerce-net中服务名可直接作为域名相互访问例如后端可以通过 mysql:3306 访问数据库前端 Nginx 可以通过 http://backend:8080 代理到后端。跨域解决方案前端 Nginx 配置反向代理将 /api 路径的请求转发到后端对浏览器来说请求的是同源地址自然不存在跨域问题。2.5 编排管理使用Docker Compose定义服务、网络和卷通过一个docker-compose.yml文件实现一键启动/停止。开发环境 vs 生产环境开发时可以挂载源码目录实现热更新如 Vue 项目可使用 npm run serve 配合端口映射生产环境则使用构建好的镜像移除挂载保证部署的稳定性。3. 项目目录结构合理的目录结构有助于维护和扩展。以下是一个推荐的项目组织方式4. 各服务的 Dockerfile 详解4.1 Java 后端 ( backend / Dockerfile )Spring Boot 应用默认支持从环境变量覆盖配置文件中的属性因此只需在 Dockerfile 中声明默认值可选并在运行时通过 -e 或 Compose 注入实际值。#Dockerfile文件 # 构建阶段使用Maven镜像编译打包(给这个阶段起名叫 builder后面可以引用)FROMmaven:3.8-openjdk-11ASbuilder # 设置工作目录为/appWORKDIR/app # 复制 pom.xmlCOPYpom.xml.# 预下载所有依赖到本地缓存(利用Docker缓存如果 pom.xml 没变这层缓存可以复用加快构建)RUNmvn dependency:go-offline # 复制源码目录COPYsrc./src # 编译打包生成JAR文件(-DskipTests跳过测试加快构建)RUNmvn cleanpackage-DskipTests# 运行阶段使用轻量级JRE镜像(只包含运行时不含Maven和编译器)FROMopenjdk:11-jre-slimWORKDIR/app # 设置默认环境变量可被运行时覆盖ENVSPRING_DATASOURCE_URLjdbc:mysql://mysql:3306/ecommerce \SPRING_DATASOURCE_USERNAMEroot \SPRING_DATASOURCE_PASSWORDsecret \SPRING_PROFILES_ACTIVEprod # 从构建 builder 阶段复制JAR包重命名为 app.jarCOPY--frombuilder/app/target/*.jar app.jar # 暴露应用端口 EXPOSE 8080 # 启动命令 ENTRYPOINT [java, -jar, app.jar]为什么要这样做多阶段构建第一阶段有Maven 和 JDK体积较大但编译完成后我们只需要 JRE 和最终的 JAR 包第二阶段使用jre-slim镜像最终镜像仅包含运行时所需体积可减小到 100MB 左右。dependency:go-offline提前下载所有依赖后续如果只修改源码而不改变 pom.xml构建时会直接使用缓存层加快构建速度。环境变量通过环境变量动态配置数据库连接镜像与配置分离符合 12-Factor 原则。环境变量说明SPRING_DATASOURCE_URL数据库 JDBC 连接串如 jdbc:mysql://mysql:3306/ecommerceSPRING_DATASOURCE_USERNAME数据库用户名SPRING_DATASOURCE_PASSWORD数据库密码SPRING_PROFILES_ACTIVE激活的 Spring Profile如 dev、prod在application.properties中只需使用变量占位符spring.datasource.url${SPRING_DATASOURCE_URL}spring.datasource.username${SPRING_DATASOURCE_USERNAME}spring.datasource.password${SPRING_DATASOURCE_PASSWORD}4.2 Vue 管理端 (web / Dockerfile)Vue 项目通常需要根据不同环境开发、测试、生产配置不同的 API 地址、CDN 路径等。由于前端构建后生成的是静态文件无法像后端那样在运行时直接读取操作系统环境变量因此需要通过构建时注入或运行时替换的方式来实现环境配置。下面提供两种主流方案。方案一构建时注入通过 Docker ARG这种方法在 Docker构建阶段将环境变量传递给前端构建工具如 Vite、Webpack在编译时直接替换代码中的变量。最终生成的镜像已包含特定环境的配置不同环境需要构建不同的镜像。# Dockerfile 文件 # 构建阶段FROMnode:16ASbuilderWORKDIR/app # 定义构建参数可设置默认值ARGVUE_APP_API_BASE_URL/apiARGVUE_APP_CDN_DOMAIN# 将构建参数写入环境变量供构建工具使用ENVVUE_APP_API_BASE_URL${VUE_APP_API_BASE_URL}\VUE_APP_CDN_DOMAIN${VUE_APP_CDN_DOMAIN}COPYpackage*.json./RUNnpm installCOPY..RUNnpm run build # 运行阶段FROMnginx:alpineCOPY--frombuilder/app/dist/usr/share/nginx/htmlCOPYnginx.conf/etc/nginx/nginx.confEXPOSE80前端项目中的使用以 Vue 3 Vite 为例在.env文件中定义变量名或直接在代码中通过import.meta.env.VITE_XXXVite或process.env.VUE_APP_XXXVue CLI引用。构建时会自动替换为传入的值。在docker-compose.yml 文件中传递构建参数web:build:context:./webargs:VUE_APP_API_BASE_URL:/apiVUE_APP_CDN_DOMAIN:https://cdn.example.com优点简单直接镜像构建完成后无需额外处理。缺点不同环境需要构建不同镜像难以实现“一次构建多处部署”。方案二运行时注入通过 envsubst Nginx此方案利用Nginx的envsubst命令在容器启动时动态替换前端静态文件中的占位符如{{API_BASE_URL}}从而实现同一个镜像在不同环境中通过环境变量配置。这种方法更适合生产环境的多环境部署如测试、预发、生产共用同一个镜像。# Dockerfile 文件 # 构建阶段FROMnode:16ASbuilderWORKDIR/appCOPYpackage*.json./RUNnpm installCOPY..# 构建时使用占位符例如将API基础地址替换为{{API_BASE_URL}}RUNnpm run build # 运行阶段FROMnginx:alpine # 安装 envsubstAlpine 镜像极简默认没有 envsubst 命令需要安装RUNapk add--no-cache gettext # 复制构建产物到临时目录启动时通过脚本替换COPY--frombuilder/app/dist/tmp/dist # 复制启动脚本COPYdocker-entrypoint.sh/docker-entrypoint.shRUNchmodx/docker-entrypoint.sh # 复制 Nginx 配置无需特殊处理COPYnginx.conf/etc/nginx/nginx.confEXPOSE80ENTRYPOINT[/docker-entrypoint.sh]配置docker-entrypoint.sh 启动脚本文件#!/bin/shset-e # 将环境变量中的值替换到JS文件中 # 假设构建时我们将API基础地址写成了{{API_BASE_URL}}占位符if[-n$API_BASE_URL];then find/tmp/dist-type f-name*.js-exec sed-is|{{API_BASE_URL}}|$API_BASE_URL|g{}\;fi # 复制替换后的文件到 Nginx 目录 cp-r/tmp/dist/* /usr/share/nginx/html/ # 启动 Nginx exec nginx -g daemon off;前端项目中需将动态配置的位置写成占位符例如// config.jsconstAPI_BASE_URL{{API_BASE_URL}};在docker-compose.yml 文件中传递 运行时环境变量web:build:./webenvironment:API_BASE_URL:/api # 或 http://backend:8080优点镜像与环境无关只需构建一次可在不同环境通过环境变量差异化配置。缺点需要额外编写启动脚本且替换逻辑需谨慎避免误替换。两种方案的选择建议在开发环境可使用方案一构建时注入配合 Docker Compose 的构建参数快速切换。在生产环境推荐方案二运行时注入实现 CI/CD 流水线中“构建一次到处运行”避免因环境差异导致的构建错误。无论采用哪种方案Nginx 的反向代理配置保持不变依然通过 location /api/ 将请求转发到后端。配套的nginx.conf位于 web/ 目录下events{worker_connections1024;}http{include/etc/nginx/mime.types;default_type application/octet-stream;server{listen80;server_name localhost;location/{root/usr/share/nginx/html;try_files $uri $uri//index.html;}location/api/{proxy_pass http://backend:8080/;proxy_set_header Host $host;proxy_set_headerX-Real-IP$remote_addr;proxy_set_headerX-Forwarded-For $proxy_add_x_forwarded_for;}}}关键点解释try_files $uri $uri/ /index.html解决 Vue 单页应用的路由问题当请求的资源不存在时返回index.html由前端路由处理/api/ 反向代理将前端对 /api 的请求转发到后端容器后端容器监听 8080 端口且服务名为 backend在 Docker 网络中可解析。4.3 Uniapp 用户端 (mobile/Dockerfile)Uniapp 构建 H5 的方式与 Vue 项目类似同样需要处理环境变量。可完全参考 Vue 管理端的两种方案只需根据实际项目调整构建命令和输出目录。常见差异点构建命令可能为 npm run build:h5输出目录可能为 dist/build/h5 或 dist/dev/h5需在 Dockerfile 中正确指定。5. Docker Compose 统一编排version:3.8# 注意推荐使用3.8版本支持较新的特性如 depends_on 的 conditionservices:mysql:image:mysql:8.0container_name:ecommerce-mysqlrestart:unless-stoppedenvironment:MYSQL_ROOT_PASSWORD:${MYSQL_ROOT_PASSWORD}MYSQL_DATABASE:ecommerceMYSQL_USER:${MYSQL_USER}MYSQL_PASSWORD:${MYSQL_PASSWORD}volumes:-mysql-data:/var/lib/mysql # 持久化数据卷-./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql # 初始化脚本可选ports:-3306:3306# 开发时映射端口便于客户端工具连接生产环境建议移除或仅绑定内网networks:-ecommerce-nethealthcheck:test:[CMD,mysqladmin,ping,-h,localhost]interval:10stimeout:5sretries:5backend:build:./backendcontainer_name:ecommerce-backendrestart:unless-stoppedenvironment:SPRING_DATASOURCE_URL:jdbc:mysql://mysql:3306/ecommerce?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueSPRING_DATASOURCE_USERNAME:${MYSQL_USER}SPRING_DATASOURCE_PASSWORD:${MYSQL_PASSWORD}SPRING_PROFILES_ACTIVE:proddepends_on:mysql:condition:service_healthy # 等待 MySQL 健康检查通过后再启动避免因数据库未就绪导致后端启动失败networks:-ecommerce-net # 开发环境可挂载源码实现热部署需配合 spring-boot-devtools # volumes:#-./backend/target:/appweb:build:context:./web # 方案一构建时注入使用 argsargs:VUE_APP_API_BASE_URL:/apiVUE_APP_CDN_DOMAIN:https://cdn.example.com # 方案二运行时注入使用 environmentenvironment:API_BASE_URL:/api # 启动脚本会读取此变量替换占位符container_name:ecommerce-webrestart:unless-stoppedports:-8081:80# 将宿主机8081端口映射到容器80端口供外部访问管理端networks:-ecommerce-netdepends_on:-backend # 仅控制启动顺序不等待后端就绪Nginx 可独立提供静态文件mobile:build:./mobilecontainer_name:ecommerce-mobilerestart:unless-stoppedports:-8082:80# 用户端H5映射到宿主机8082端口networks:-ecommerce-netdepends_on:-backendvolumes:mysql-data:driver:local # 使用本地驱动创建数据卷数据存储在宿主机/var/lib/docker/volumes/下networks:ecommerce-net:driver:bridge # 创建自定义桥接网络实现容器间通过服务名通信5.1 关键配置解释配置项作用/说明关键点/注意事项version: ‘3.8’指定Compose文件格式版本推荐3.8以上支持depends_on的condition特性确保Docker Engine版本兼容19.03.0services.mysql.image使用官方MySQL 8.0镜像无需构建直接拉取版本可固定如mysql:8.0.30避免意外升级services.mysql.container_name固定容器名称便于管理但注意同一宿主机下不能重名services.mysql.restart: unless-stopped容器退出时自动重启除非手动停止保证服务高可用适合生产环境services.mysql.environment通过环境变量初始化数据库使用${VAR}从.env读取敏感信息避免硬编码MYSQL_DATABASE自动创建库services.mysql.volumes数据持久化挂载mysql-data:/var/lib/mysql保证数据不丢失init.sql挂载到初始化目录首次启动自动执行services.mysql.ports映射端口到宿主机开发时方便客户端工具连接生产应移除或绑定内网IP如127.0.0.1:3306:3306services.mysql.networks加入自定义网络服务名mysql可供同一网络其他容器访问services.mysql.healthcheck健康检查命令通过mysqladmin ping检测服务真正就绪interval、timeout、retries控制检查行为services.backend.build指定构建上下文使用./backend目录下的Dockerfile构建镜像services.backend.environment传入后端环境变量覆盖application.properties中的占位符数据库连接串使用服务名mysqlservices.backend.depends_on控制启动顺序condition: service_healthy确保MySQL健康后才启动后端避免连接失败services.backend.networks加入同一网络通过服务名mysql访问数据库services.backend.volumes注释开发时挂载源码需配合spring-boot-devtools实现热部署生产环境必须移除services.web.buildVue管理端构建配置context指定路径args用于构建时注入方案一environment用于运行时注入方案二services.web.args构建参数传递对应Dockerfile中的ARG构建时替换前端变量不同环境需构建不同镜像services.web.environment运行时环境变量容器启动时由docker-entrypoint.sh读取替换静态文件中的占位符实现镜像复用services.web.ports端口映射将宿主机8081端口映射到容器80端口外部访问管理端services.web.depends_on依赖后端仅控制启动顺序不等待后端就绪Nginx可独立提供静态文件services.web.networks加入同一网络使Nginx能通过http://backend:8080代理后端APIservices.mobileUniapp用户端配置与web类似映射8082端口构建命令和输出目录需根据实际项目调整volumes.mysql-data定义数据卷driver: local使用本地存储数据保存在/var/lib/docker/volumes/下networks.ecommerce-net创建自定义网络driver: bridge实现单机容器通信服务名作为DNS解析解耦IP依赖.env文件集中管理环境变量存放敏感信息密码和环境差异配置必须加入.gitignore避免泄露5.2 配套的 .env 文件作用集中管理敏感信息和环境差异配置避免硬编码在 Compose 文件中。注意.env 文件不应提交到版本控制应加入 .gitignore生产环境需单独维护。MYSQL_ROOT_PASSWORDroot123MYSQL_USERecommerce_userMYSQL_PASSWORDecommerce_pass如果同时保留 args 和 environment则优先使用 environment 运行时注入前提是镜像支持。实际项目中可根据需要选择一种方式或通过 Compose 文件分离如 docker-compose.override.yml 用于开发使用 args生产使用 environment。5.3 实战注意事项版本兼容性确保 Docker Engine 版本支持 Compose file 3.8Docker 19.03.0。环境变量覆盖Compose 中的 environment 会覆盖镜像中 ENV 设置的默认值也覆盖 .env 文件中同名变量。构建参数 vs 环境变量args 仅在构建时有效不会传递到运行时的容器中environment 在容器运行时生效。前端两种方案的选择需根据实际需求。网络模式自定义桥接网络提供了服务发现和隔离生产环境如需跨主机通信可改用 overlay 网络需 Swarm 模式。健康检查的重要性特别是数据库直接依赖启动顺序可能不够因为容器启动后服务可能还在初始化。使用健康检查可确保服务真正就绪。日志管理生产环境建议配置日志驱动限制大小如logging:driver:json-fileoptions:max-size:10mmax-file:3资源限制可为每个服务设置 CPU、内存限制防止单个容器耗尽主机资源。注意deploy 仅用于 Swarm 模式Compose 单独使用时可用 mem_limit 等旧版字段。deploy:resources:limits:cpus:0.5memory:512M