简介这是一份关于 Docker 部署 mall 微服务商城系统的完整操作文档面向希望掌握容器化部署技能的运维人员、后端开发工程师及微服务学习者。mall 项目基于 SpringBootMyBatis 实现包含前台商城与后台管理系统文档从前端到后端覆盖部署全过程并提供了源码仓库与学习地址。包体为单个 PDF 文件压缩包大小约 1.37MB内容以部署步骤、命令示例与配置说明为主便于随时查阅。资源目前已有 556 人学习下载适合按步骤实操也适合作为企业级微服务容器化部署的参考。文档详细介绍了 Docker 基础环境安装、Harbor 镜像仓库配置以及 MySQL、Redis、Nginx、RabbitMQ、Elasticsearch、Logstash、Kibana、MongoDB、Minio 等基础组件部署并覆盖 mall-admin、mall-search、mall-port 等 SpringBoot 应用的镜像构建与部署同时包含 mall-tiny-docker 示例和前端 npm 构建、Nginx 发布过程可帮助读者快速搭建一套可运行的 mall 微服务商城环境。1. 为什么“docker部署mall微服务商城”一听就熟一部署就翻车在本地把 mall 微服务商城跑起来并不难难的是把它搬上服务器或者让同事在半天内复现你的环境。这个标题说到底要解决的问题是把微服务拆分出来的注册中心、网关、后台、门户、搜索连同它们依赖的 MySQL、Redis、ES、RabbitMQ 和对象存储全部固化成镜像与编排文件让部署从一个谁都说不清步骤的玄学变成一条可重复执行的命令。适合正在从单体转微服务、需要搭建演示环境或最小可用生产环境的开发者。反直觉的一点是比起 docker 命令本身部署过程中的大多数翻车都发生在服务发现、网络配置和数据持久化这几个参数上。2. 先起中间件再看服务用 Compose 编排 MySQL、Redis、ES 与 RabbitMQ2.1 部署前先画微服务架构图mall 到底依赖哪些中间件动手敲命令之前先把依赖盘清楚。mall 这类微服务商城服务层再怎么拆分最终都要落到数据层。常见依赖大体是这样中间件在商城里的用途端口约定部署优先级MySQL商品、订单、会员、库存等核心业务数据3306最高Redis缓存、验证码、分布式锁、购物车临时数据6379高Elasticsearch商品搜索、推荐、日志检索9200搜索功能必需RabbitMQ订单超时、延迟队列、异步通知5672/15672订单链路建议启用MinIO商品图片、用户头像等文件存储9000/9001有上传场景才需要很多新手上来就写服务的 Dockerfile中间件随手docker run一个 MySQL 就开始调试。结果服务起来后连不上库因为容器 IP 每次重建都在变。我一般会把中间件和服务放进同一个 Compose 工程里让它们通过服务名互通而不是依赖宿主机 IP。先搭建中间件的另一层考虑是启动顺序。微服务在启动时会尝试连接数据库、注册到 Nacos如果数据层没就绪服务启动会报一堆连接超时。虽然应用端有重试机制但把中间件先跑起来后面服务的日志会干净很多。2.2 MySQL 8.0 与 Redis 编排认证插件、持久化、端口映射的三个关键参数中间件部分用docker-compose.yml管理是最常见也最省心的做法。下面这段是我常用的一组基础配置services: mysql: image: mysql:8.0 container_name: mall-mysql restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: Mall123456 MYSQL_DATABASE: mall TZ: Asia/Shanghai command: - --default-authentication-pluginmysql_native_password - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci volumes: - /opt/mall/mysql/data:/var/lib/mysql - /opt/mall/mysql/config:/etc/mysql/conf.d networks: - mall-net redis: image: redis:7-alpine container_name: mall-redis restart: always ports: - 6379:6379 command: [redis-server, --appendonly, yes] volumes: - /opt/mall/redis/data:/data networks: - mall-net networks: mall-net: driver: bridge这段配置里有三个关键点值得说明。第一个是 MySQL 8.0 的认证插件。默认的caching_sha2_password对老版本 JDBC 驱动不友好Spring Boot 2.x 时代的 mall 项目经常出现Public Key Retrieval is not allowed报错。加上--default-authentication-pluginmysql_native_password能省掉很多兼容性问题如果用的是最新版本驱动这项也可以去掉。第二个是持久化。Redis 挂载了/data并开启appendonlyMySQL 挂载了/var/lib/mysql。容器重建后数据还在宿主机磁盘上这是生产可用和一次演示环境的分水岭。第三个是TZ环境变量。容器默认是 UTC 时区不设置的话业务表里写入的创建时间会比北京时间早 8 小时排查问题时会以为数据被改过。MySQL 镜像没有内嵌 tzdata显式指定TZ是最短的解决方案。启动中间件只需要一条命令docker compose -f docker-compose.yml up -d mysql redis这里没有加--build因为中间件用的是现成镜像只需要拉取和启动。拉取镜像较慢时可以用docker pull mysql:8.0先单独拉看下载进度。2.3 ES、RabbitMQ、MinIO 的容量与内存陷阱Elasticsearch 是这几个中间件里最容易出问题的。它默认尝试锁定内存并依赖系统物理内存在容器里跑需要显式关掉 bootstrap 检查。elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0 container_name: mall-es environment: - discovery.typesingle-node - ES_JAVA_OPTS-Xms512m -Xmx512m - TZAsia/Shanghai ports: - 9200:9200 volumes: - /opt/mall/es/data:/usr/share/elasticsearch/data networks: - mall-netdiscovery.typesingle-node是单机部署必备参数否则镜像会尝试启动集群发现在只有一台机器时反复报错。ES_JAVA_OPTS里的Xms和Xmx必须一致避免堆在运行中被动态伸缩ES 对堆大小异常敏感。宿主机内存只有 4G 的话512M 是安全的不要贪大。宿主机目录挂给 ES 时大概率会遇到AccessDeniedException。原因是镜像内elasticsearch用户 uid 是 1000而宿主机新创建的/opt/mall/es/data属主是 rootmkdir -p /opt/mall/es/data chown -R 1000:1000 /opt/mall/es/dataRabbitMQ 的坑相对少选带管理页面的rabbitmq:3-management镜像默认开放 15672 端口rabbitmq: image: rabbitmq:3-management container_name: mall-rabbitmq environment: RABBITMQ_DEFAULT_USER: mall RABBITMQ_DEFAULT_PASS: mall123 ports: - 5672:5672 - 15672:15672 volumes: - /opt/mall/rabbitmq:/var/lib/rabbitmq networks: - mall-netMinIO 属于按需部署的组件mall 项目的文件上传模块用到对象存储时再启动即可。常见的初始化参数是MINIO_ROOT_USER和MINIO_ROOT_PASSWORD数据目录挂到宿主机。文件服务一旦启用图片链接就走 MinIO 的地址因此它的网络别名和端口在服务配置里也是固定项。3. 服务镜像怎么做从 jar 到 image 的 Dockerfile 与构建参数3.1 一个可用的 Java 服务 Dockerfile分层拷贝与时区处理中间件就绪后进入 mall 服务本身的镜像构建。这是把本地编译好的 jar 包变成可分发镜像的过程。mall 系列服务基于 Spring Boot单体 jar 内部已经打包了依赖库Dockerfile 不需要运行 Maven 构建只需要把 jar 拷进基础镜像并执行启动命令。FROM openjdk:8-jre-alpine RUN apk add --no-cache tzdata \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone ENV JAVA_OPTS-Xms256m -Xmx256m COPY mall-admin.jar /app.jar EXPOSE 8080 ENTRYPOINT [sh, -c, java $JAVA_OPTS -jar /app.jar]这里有几个值得展开的设计细节。第一基础镜像选openjdk:8-jre-alpine而不是完整 JDK。mall 的老版本服务大多基于 Spring Boot 2.x 与 JDK 8运行时只需要 JRE镜像体积差一倍以上。如果项目已经升级到 Spring Boot 3.x对应换eclipse-temurin:17-jre即可。第二时区处理写在 Dockerfile 里而不是 Compose 环境变量里。基础镜像的 Linux 发行版各有差异alpine 的 cpython 时区数据缺失不装 tzdata 的话即使设置了TZ也不会生效。第三ENTRYPOINT用sh -c包裹 Java 命令是为了让JAVA_OPTS环境变量在运行时能被替换。Compose 里给某个服务加-e JAVA_OPTS-Xms512m -Xmx512m容器启动时就会用新值覆盖 Dockerfile 里的默认值这在分配多服务堆内存时特别有用。构建镜像的命令如下docker build -t mall/mall-admin:1.0.0 .注意末尾的点指当前目录里面的 Dockerfile。构建日志最下方看到Successfully tagged mall/mall-admin:1.0.0说明镜像已经就位。3.2 镜像构建的两种常见姿势命令行 build 与 IDEA 打包构建不一定要在命令行里做。IDEA 装了 Docker 插件后可以在Services面板直接右键 Dockerfile 执行 build效果和命令行一致只是把上下文目录、镜像标签放到了可视化界面里。但对微服务商城这种十几个服务的项目我仍然推荐写一个一键构建脚本。一个实用的脚本思路是先全量mvn package再按服务名逐个 build#!/bin/bash SERVICES(mall-admin mall-portal mall-search mall-auth mall-gateway) for svc in ${SERVICES[]}; do echo building $svc ... mvn -pl $svc -am package -DskipTests -q docker build -t mall/$svc:latest -f docker/$svc/Dockerfile . done脚本里用了-pl指定模块-am让 Maven 一并构建依赖模块。mall 这类多模块工程里mall-portal依赖mall-mbg或公共模块不写-am会直接报找不到依赖包。还有一种常见姿势是直接在服务器上以源码方式构建。把项目 clone 到服务器用 Maven 打包后构建镜像。这样做的好处是避免本地 jar 包和服务器环境不一致但服务器上必须有 JDK 和 Maven或者用多阶段构建把编译过程也装进镜像里。多阶段构建会更彻底但对小内存服务器并不友好我一般不在单机 2G 内存的机器上硬撸。3.3 镜像下载慢怎么办配置 registry mirror 减少等待构建时最影响体验的往往不是本地编译而是拉取基础镜像。openjdk:8-jre-alpine要拉mysql:8.0要拉elasticsearch几个 GB 的镜像更考验耐心。给 Docker 配置镜像加速源是部署的第一步。sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json EOF { registry-mirrors: [https://docker.m.daocloud.io] } EOF sudo systemctl daemon-reload sudo systemctl restart docker配置文件里的registry-mirrors可以填写多个地址Docker 会按顺序尝试。改完配置后用docker info能看到Registry Mirrors一节列出了生效的镜像源说明配置已经生效。此时再拉基础镜像速度会正常很多。注意daemon.json的修改只对之后拉取的镜像生效已经存在的镜像不会重新下载。如果怀疑镜像缓存在本地有损坏可以docker system prune -a清理后重新拉取但执行前先想清楚本地还有没有需要保留的容器和镜像。4. 把微服务接进 Compose内网名称、服务发现与启动顺序4.1 服务拆到了什么程度一张表看清 gateway、admin、portal、search 的边界服务层的拆分可以参考 mall 系列项目中常见的微服务拆分方式。虽然不同版本的 mall 服务命名有差异但业务边界基本一致服务名端口职责依赖mall-gateway8200统一入口、路由分发、鉴权前置Nacosmall-auth8300登录、令牌颁发与校验Nacosmall-admin8080商城后台管理接口MySQL、Redis、Nacosmall-portal8081商城前台门户接口MySQL、Redis、RabbitMQ、Nacosmall-search8082商品搜索与聚合查询ES、MySQL、Nacos这些服务既可以拆分成多个容器也可以合并部署。拆分的判断标准是数据边界和访问压力后台管理接口访问量低但数据模型复杂门户接口面向 C 端查询量大但数据模型相对简单搜索服务独立因为它的索引逻辑和业务库解耦。两张表写下来微服务拆分就不再是抽象概念而是端口、数据依赖和镜像的集合。4.2 把服务接进 Compose网络与 depends_on 的正确用法服务接入 Compose 时最核心的是网络配置。所有服务必须和中间件在同一个自定义网络mall-net里才能用服务名互相访问。这样微服务里的数据库地址就可以写jdbc:mysql://mysql:3306/mall注册中心地址写http://nacos:8848而不是写死某个容器 IP。nacos: image: nacos/nacos-server:v2.2.0 container_name: mall-nacos environment: - MODEstandalone ports: - 8848:8848 - 9848:9848 networks: - mall-net mall-admin: build: context: ./mall-admin image: mall/mall-admin:latest container_name: mall-admin depends_on: - nacos - mysql - redis environment: JAVA_OPTS: -Xms256m -Xmx512m MYSQL_HOST: mysql REDIS_HOST: redis NACOS_ADDR: nacos:8848 ports: - 8080:8080 networks: - mall-net这段配置里有两个高频踩坑点需要注意。第一个是depends_on的语义。它只控制容器启动的先后顺序不等待服务真正就绪。服务起来时 MySQL 可能还在做初始化应用连接数据库会报Connection refused。这不是配置错了而是启动顺序问题。Spring Boot 的 HikariCP 默认有重试机制很多情况下应用会先启动失败几次然后自动连上多等一会儿再看日志即可。第二个是服务名作为网络主机名。Compose 会为每个服务创建一个 DNS 记录容器之间用服务名互相访问。上面配置里MYSQL_HOST: mysql指的是 compose 服务名mysql不是容器名mall-mysql也不是宿主机 IP。三者都能通但服务名最稳妥不随容器重建变化。4.3 Nacos 地址填不对怎么办定位微服务黑匣子的三条命令服务启动后最怕日志刷屏却不知道问题在哪。微服务架构图在网络里画得很清楚但容器一跑起来就是黑匣子。下面三条命令能定位大部分服务注册问题。docker compose logs -f mall-admin docker logs mall-nacos --tail 200 docker exec -it mall-admin sh -c nslookup nacos第一条看应用日志重点找包含nacos和Connection refused的行。第二条看 Nacos 日志确认服务是否成功注册到了注册中心。第三条最实用在 mall-admin 容器内部执行nslookup nacos能直接确认容器是否能解析到 Nacos 服务名。如果 nslookup 返回的 IP 和 Nacos 容器 IP 不一致说明网络配置有问题需要在 Compose 网络里检查服务是否挂在同一个mall-net。还有一类情况是 Nacos 控制台看得到服务但服务之间调用仍然超时。这时检查网关转发配置里写的是不是服务名。mall-gateway 的 route 配置里,lb://mall-portal才走 Nacos 负载均衡直接写http://mall-portal:8081也可以但失去了动态发现能力。5. 部署避坑指南docker 权限、网络不通与数据丢失5.1 连不上 docker api 的权限报错从 permission denied 到修复现象是执行docker ps直接报错permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock。原因是当前用户不在 docker 用户组里。Docker 守护进程的 socket 文件只有 root 和 docker 组可访问普通用户直接用不了。很多教程只在 root 下演示换普通用户就翻车。解决方法是把当前用户加进 docker 组然后重新登录会话sudo usermod -aG docker $USER newgrp dockernewgrp docker让当前会话立即生效不用退出重连服务器。之后再用 docker 命令就不会报错了。这个坑几乎每个 Linux 服务器部署都会遇到一次添加用户组本身是安全的但注意这等同于把 docker 权限交给了该用户sudo 权限的授予要按团队规范来。5.2 容器内通、宿主机不通端口映射与防火墙的排查顺序现象是容器里用curl http://localhost:3306正常但宿主机去连127.0.0.1:3306就是不通。原因通常有两个。第一个是没配端口映射。Compose 文件里 ports 段没有写3306:3306容器内部的端口只在 docker 网络里生效宿主机访问不到。用docker ps看 PORTS 列如果是3306/tcp而不是0.0.0.0:3306-3306/tcp说明没映射成功。第二个是防火墙拦截了宿主机到容器的访问即使端口映射配置正确防火墙不放行 3306 也一样连不上。排错的顺序也很简单。先用docker ps确认端口映射再在宿主机执行curl -v telnet://127.0.0.1:3306 firewall-cmd --list-portstelnet通说明端口在监听再去检查防火墙。很多云服务器还有安全组规则端口映射正确但安全组没放行一切操作都白费。这一层排查完成后宿主机才能访问到容器里的 MySQL、Redis 这类中间件。5.3 服务名解析失败微服务之间为什么找不对地址现象是 mall-admin 日志里不断出现UnknownHostException: mysql或者Connection refused: /127.0.0.1:3306。前者说明容器无法解析mysql这个主机名后者说明应用根本没走服务名而是用了容器自身的回环地址。原因极可能是服务没有和 MySQL 挂在同一个 docker 网络里。Compose 默认会创建一个以目录名命名的网络如果手写docker run启动的服务没加--network mall-net它们之间完全没有连接。解决方式是在 Compose 里明确给每个服务指定networks: [mall-net]而不是指望默认网络把大家都连在一起。检查时用docker network inspect mall-net输出里Containers一节会列出所有挂在这个网络下的容器名和 IP。如果发现某个服务不在列表里就回到 compose 文件把网络配置补上然后docker compose up -d重建对应服务。5.4 Windows Docker Desktop 虚拟化报错确认设置项而不是重装现象是 Docker Desktop 启动就提示virtualization support not detected或者直接失败无法启动控制和 WLS 相关。这是因为 Windows 上 Docker Desktop 依赖 Hyper-V 或 WSL2 后端BIOS 里虚拟化被禁用或者 WSL2 没有启用就会报这类错误。解决步骤比重装更优先进 BIOS 开启Intel Virtualization TechnologyAMD 对应SVM Mode然后在 Windows 功能里启用虚拟机平台和适用于 Linux 的 Windows 子系统执行wsl --update更新内核。bcdedit /set hypervisorlaunchtype auto管理员终端执行上面的命令把 Hyper-V 自启动打开重启后 Docker Desktop 一般就能正常启动。服务器生产环境不推荐跑 Docker Desktop直接装 Linux 服务器版更省心这个报错在 Linux 上不存在。5.5 一重建容器数据就没了volume 必须落盘现象是容器还在时数据都正常但docker compose down之后再up -dMySQL 里的商品表空了Redis 缓存也没了。原因是数据只写在容器可写层容器一删就烟消云散。这是 Docker 设计如此不是 bug。第一次部署教学里最常见也最影响后续使用。解决方法是把有状态服务的数据目录挂载到宿主机。MySQL 挂/var/lib/mysqlRedis 挂/dataES 挂/usr/share/elasticsearch/data。挂载之后不管容器被删掉重建多少次数据始终在宿主机目录里。执行docker compose down -v会连命名卷一起删掉这个命令要谨慎生产环境没有备份不要随便带-v。想保留数据时只执行docker compose down卷和挂载目录都留在原地。6. 部署完成后的第一件事验证整条链路再做镜像瘦身6.1 用健康检查和登录接口验证微服务链路所有容器起来后先别急着调优把业务主链路走通一次验证的是网络、注册中心、数据源、Redis 缓存这一整套链路是否真正打通。我习惯按这个顺序验证docker compose ps curl http://localhost:8200/api/mall-admin/health curl -X POST http://localhost:8200/api/mall-admin/admin/login \ -H Content-Type: application/json \ -d {username:admin,password:123456}第一条看容器状态退出码和 Up 时间一目了然有容器反复重启说明启动阶段就有问题。健康检查接口能从网关入口探到真实响应注册中心通不通、网关路由对不对一个请求就能暴露出来。mall-gateway 没有健康端点的话可以直接请求 Nacos 的 API 看服务列表curl http://localhost:8848/nacos/v1/ns/service/list?pageNo1pageSize10返回的服务列表里出现 admin、portal、search、auth说明所有服务都已经在注册中心完成注册服务发现链路没有问题。这一步过去微服务黑匣子才真正打开。6.2 镜像瘦身到几百 MBalpine 基础镜像与安全更新链路验证通过后再动镜像不迟。服务镜像瘦身的空间主要来自基础镜像和分层。openjdk:8-jre-alpine已经算小了但如果嫌镜像过多可以统一用同一个基础镜像而不是每个服务各拉一个 tag。多阶段构建把编译期单独拆一层产物里只有 jar 和 JRE能压缩到 200M 上下。FROM maven:3.6-jdk-8 AS builder WORKDIR /build COPY pom.xml . COPY . . RUN mvn package -DskipTests FROM openjdk:8-jre-alpine COPY --frombuilder /build/target/mall-admin.jar /app.jar ENTRYPOINT [java, -jar, /app.jar]多阶段构建适合服务器磁盘紧张或镜像需要推送到私有仓库的场景。本地开发调试还是直接打 jar 再 build 灵活毕竟每次改一行代码就要全量 Maven 构建时间成本不划算。镜像瘦完身回到服务器上跑一次完全重建docker compose down docker compose build --no-cache docker compose up -d--no-cache强制镜像层全部重建用来排除旧缓存层导致的配置残留。这一套流程走完mall 的部署才真正从能跑变成可持续维护。我在部署这类微服务商城时最大的教训是把验证放到最后。每次都先容量规划再调参数结果花两小时调好 Redis 内存一启动才发现网关路由配错。现在一律先拉起全部服务跑通登录下单搜索主链路再回来精调资源限制。这样即便出问题面对的是干净的全量日志而不是被内存参数绕晕的模糊线索。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?