Vue+SpringBoot前后端分离项目Docker容器化部署实战

发布时间:2026/9/17 15:16:01
Vue+SpringBoot前后端分离项目Docker容器化部署实战 本地npm run dev一切正常IDEA 里点一下启动类接口全通可一旦要把这套 Vue SpringBoot 前后端分离项目交到一台干净的服务器上麻烦就来了Node 版本不对、JDK 版本不对、nginx 没装、路径对不上、后端还没起来前端就报 502。Docker 的价值就在这儿——把这一串环境不确定性固化成一个装着 nginx 和静态资源的镜像、一个装着 JDK 和 fat jar 的镜像再用一份 compose 文件把它们接起来。这套链路我自己反复搭过很多次也踩过不少坑下面从设计取舍到排错复盘一次讲清楚。适合已经能跑通本地开发、准备把项目容器化上线的同学也适合接手别人项目、需要读懂现有 Dockerfile 的读者。1. 容器化之前先搞明白这件事难在哪很多人一上来就写 Dockerfile写完发现改个后端地址要重新打包整个前端或者本地能跑服务器上 404根因都在于没弄清 Vue 和 SpringBoot 在配置什么时候生效这件事上完全是两套逻辑。1.1 Vue 的产物是构建期定型的改一个字都要重新打包Vue 项目不管是 Vue CLI 还是 Vitenpm run build出来的dist目录里axios的baseURL、process.env.VUE_APP_XXX或者import.meta.env.VITE_XXX这些写法在打包那一刻就全部被替换成了字面量字符串编译进assets/index-xxxx.js里。这意味着一个反直觉的结论前端镜像不是配置驱动的它是构建驱动的。你在服务器上改了环境变量前端根本不认因为那段字符串早在构建阶段就被写死了。由此推出两条很实用的规则前端代码里的接口前缀一律写相对路径/api绝对不要写http://192.168.1.100:8080。写死了 IP这份镜像就只有那台机器能用。一旦接口前缀是相对路径同一个前端镜像就能在所有环境复用——开发机、测试环境、生产环境全用同一份dist差异交给 nginx 去处理。我见过太多项目在src/utils/request.js里写着baseURL: process.env.VUE_APP_BASE_API然后在.env.production里填了个具体域名。结果换环境就得在服务器上装 Node、重新 build 一遍Docker 的意义直接少了七成。1.2 SpringBoot 是运行期定型一个镜像能跑多套环境和前端完全相反SpringBoot 的配置是有明确优先级的命令行参数 环境变量 application-{profile}.yml application.ymlapplication.yml里写的是默认值打包进 jar 里。数据库地址、Redis 地址、第三方密钥这类随环境变化的东西全部可以通过环境变量在容器启动时覆盖。SpringBoot 有一套很规整的宽松绑定规则spring.datasource.url对应环境变量SPRING_DATASOURCE_URL点变下划线、全大写。所以后端的正确姿势是一份镜像所有环境通用。构建一次推一次部署到哪套环境就用哪套环境变量。这就是为什么后端的 Dockerfile 里不应该出现任何环境相关的 COPY 或 ARG。1.3 浏览器只访问一个地址nginx 必须挡在最前面前后端分离项目在容器化之后网络拓扑其实有两种选法。一种是前端容器暴露 80后端容器也暴露 8080浏览器直接请求http://服务器IP:8080/api/xxx。这种做法的代价是必须配置 CORS而且后端端口直接对公网开放安全面变大。另一种是只暴露前端容器的 80 端口后端容器只在内网里待着前端容器里的 nginx 既负责发静态文件又负责把/api/开头的请求转发到后端容器。这种做法下浏览器眼里只有一个域名一个端口静态资源和接口完全同源CORS 这个问题从根上消失了。第二种是绝大多数场景下的正解后面所有的配置也都基于这个思路展开。记住一句话nginx 在这个架构里不是顺便装了个 web 服务器它是整个系统的统一入口是整个链路里最需要认真对待的一环。2. 目录结构、构建缓存与三份产物的边界2.1 一套我一直在用的仓库目录前后端放一个仓库还是两个仓库各有各的道理。小团队、发布节奏一致的项目我建议放一个仓库构建上下文和 compose 文件都好管理project/ ├── frontend/ │ ├── src/ │ ├── public/ │ ├── package.json │ ├── package-lock.json │ ├── vite.config.js │ ├── .dockerignore │ └── Dockerfile ├── backend/ │ ├── src/main/java/... │ ├── src/main/resources/application.yml │ ├── pom.xml │ ├── .dockerignore │ └── Dockerfile ├── deploy/ │ ├── nginx.conf │ ├── docker-compose.yml │ └── .env └── README.mddeploy目录单独拎出来的好处是换服务器时只需要拷这一个目录加上两个镜像部署动作和代码仓库解耦。nginx.conf放在这里而不是前端目录里是因为它属于部署配置不属于前端源码——前端开发同学改路由时不应该顺手改到它。2.2 构建产物、运行镜像、运行时配置三件事千万别混这是最容易乱的地方我用一张表把边界说清楚东西谁产生的什么时候会变最终去哪dist静态文件npm run build前端源码或依赖变化打进前端镜像app.jarmvn package后端代码或依赖变化打进后端镜像环境变量库地址、密钥部署时填写换环境时变只在 compose / .env不进镜像nginx.conf运维或后端同学路由规则变时打进前端镜像或挂载看这张表能得出一个判断标准任何换环境就要改的东西都不能进镜像。数据库密码进镜像等于把生产密码写进了可以随便docker save出来的文件里接口地址进前端镜像等于这份镜像只能服务一台机器。2.3 Docker 分层缓存为什么 COPY 的顺序这么值钱Docker 构建镜像时每条RUN、COPY、ADD指令都会生成一层。判断缓存是否命中的规则很简单如果某一层的输入变了这一层和它之后的所有层缓存全部失效。拿前端 Dockerfile 举例下面这两种写法的构建速度差距可能是 5 秒和 5 分钟的区别# 不推荐源码一改npm ci 全部重跑 COPY . . RUN npm ci RUN npm run build# 推荐源码改动不影响依赖层的缓存 COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build第二种写法里只要package.json和package-lock.json没动npm ci那一层永远命中缓存改一百行业务代码也不用重新下依赖。后端也是一样先COPY pom.xml跑mvn dependency:go-offline再COPY src。注意COPY package.json package-lock.json ./这种写法必须保证两个文件都存在缺一个会直接构建失败。用 yarn 的项目要换成yarn.lock用 pnpm 的换成pnpm-lock.yaml锁文件一定要跟着进镜像否则每次构建装到的依赖版本都可能不一样。3. Vue 前端镜像的写法多阶段构建加 nginx 托管3.1 Dockerfile 逐段拆解意图比语法重要前端最典型的写法是多阶段构建构建阶段用 Node 镜像运行阶段用 nginx 镜像Node 那一大坨东西不进最终镜像。# ---------- 构建阶段 ---------- FROM node:20-alpine AS builder WORKDIR /app # 单独复制锁文件最大化利用缓存 COPY package.json package-lock.json ./ RUN npm ci --registryhttps://registry.npmmirror.com # 再复制源码 COPY . . # 内存不足时给 Node 放宽堆上限 ENV NODE_OPTIONS--max-old-space-size4096 RUN npm run build # ---------- 运行阶段 ---------- FROM nginx:1.27-alpine COPY --frombuilder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [nginx, -g, daemon off;]几个细节值得单独说npm ci和npm install不是一回事。ci严格按package-lock.json安装装完不改锁文件也不会偷偷升级小版本。CI/CD 场景必须用ci否则本地跑得好好的、服务器上装出来的依赖版本不一样排查起来极其痛苦。CMD [nginx, -g, daemon off;]里的daemon off不能省。nginx 默认是以后台守护进程方式启动的主进程一 fork 就退出了Docker 会认为容器里的主进程结束了容器立刻停止。让 nginx 在前台跑容器才有活着的概念。node:20-alpine这个标签要跟着项目实际用的 Node 版本走。Node 17 之后 OpenSSL 升级到 3.0用 webpack 4 的老项目会报ERR_OSSL_EVP_UNSUPPORTED临时解法是加ENV NODE_OPTIONS--openssl-legacy-provider但更稳妥的做法还是把构建工具链升上去。3.2 .dockerignore 和构建慢的根因node_modules一定要写进.dockerignorenode_modules dist .git .vscode *.log npm-debug.log*原因有两个。一是COPY . .会把宿主机的node_modules复制进镜像覆盖掉容器里刚装好的那份。宿主机是 macOS 或 Windows 时node_modules里可能含平台相关的二进制复制进 Linux 容器直接跑不起来。二是构建上下文会变得巨大docker build光是把这个目录打发给 daemon 就要几十秒。顺带说一句构建速度。国内网络环境下npm ci卡住是很常见的两种解法换镜像源或者用构建参数控制ARG NPM_REGISTRYhttps://registry.npmmirror.com RUN npm ci --registry$NPM_REGISTRY个人经验是把镜像源做成ARG而不是写死这样在海外机器上构建时可以覆盖回官方源两个环境都不吃亏。3.3 nginx.conf 里真正决定成败的那几行server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; gzip on; gzip_types text/css application/javascript application/json image/svgxml; gzip_min_length 1k; # history 路由模式的兜底 location / { try_files $uri $uri/ /index.html; } # 静态资源长缓存 location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?)$ { expires 30d; add_header Cache-Control public, immutable; } # 接口转发 location /api/ { proxy_pass http://backend:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 10s; proxy_read_timeout 120s; client_max_body_size 50m; } }try_files $uri $uri/ /index.html是 history 路由模式的必需品。Vue Router 用 history 模式时/user/list这个路径在服务器上并没有对应的文件nginx 找不到就返回 404。这一行的意思是先找同名文件找不到就找同名目录还找不到就把index.html返回去交给前端路由自己处理。proxy_pass http://backend:8080/;结尾那个斜杠是整个配置文件里最容易出事的一个字符。规则是这样的nginx 配置浏览器请求转发到后端的路径proxy_pass http://backend:8080/;/api/user/list/user/listproxy_pass http://backend:8080;/api/user/list/api/user/list带斜杠等于把location匹配到的/api/前缀剥掉不带斜杠则原样透传。这两种写法没有对错只有配不配对但配错的结果一定是 404。backend这个名字不是随便起的它是 compose 里定义的服务名Docker 的内置 DNS 会把它解析成后端容器的 IP。这也是容器化比手写 IP 舒服的地方——换容器、重启容器IP 变了但服务名一直有效。4. SpringBoot 后端镜像基础镜像、分层 jar、JVM 参数4.1 基础镜像怎么选别再拿 openjdk:8 打天下镜像体积量级适用场景openjdk:8-jre偏大且已停更不建议新项目使用eclipse-temurin:17-jre-jammy中等Ubuntu 底座工具齐全通用首选排错方便eclipse-temurin:17-jre-alpine最小busybox 底座追求体积、且能接受缺 tzdataamazoncorretto:17-alpine小长期支持上了云、想要稳定更新的场景选 JRE 而不选 JDK是因为运行阶段不需要编译器javac、jmods这些能省下小一百兆。选 jammy 还是 alpine主要看你要不要省那几十兆——alpine 缺时区数据TZAsia/Shanghai是不生效的得额外apk add --no-cache tzdata很多日志时间差 8 小时的问题就是从这来的。Java 版本要和编译时的版本对齐。用 JDK 17 编译出来的 class 文件扔进 JRE 8 里跑会直接报UnsupportedClassVersionError。容器化之后这个错误往往发生在镜像构建成功、启动就崩的阶段看着吓人其实就是版本没对齐。4.2 用 layertools 拆包让增量发布只传改动的那一层SpringBoot 打出来的 fat jar 有个特点依赖占了九成体积业务代码只有几十 KB。如果不拆改一行代码重新构建整个几百兆的 jar 层缓存全废推镜像要重传几百兆。SpringBoot 官方提供的layertools能把这个 jar 按内容拆成四层# ---------- 拆包阶段 ---------- FROM eclipse-temurin:17-jre-jammy AS builder WORKDIR /build COPY target/*.jar app.jar RUN java -Djarmodelayertools -jar app.jar extract # ---------- 运行阶段 ---------- FROM eclipse-temurin:17-jre-jammy WORKDIR /app ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ /etc/timezone # 顺序不能乱变化频率低的层放前面 COPY --frombuilder /build/dependencies/ ./ COPY --frombuilder /build/spring-boot-loader/ ./ COPY --frombuilder /build/snapshot-dependencies/ ./ COPY --frombuilder /build/application/ ./ EXPOSE 8080 ENTRYPOINT [java, \ -XX:MaxRAMPercentage75.0, \ -Duser.timezoneAsia/Shanghai, \ org.springframework.boot.loader.launch.JarLauncher]拆出来的四层按照变化频率从低到高排列dependencies是稳定版本的第三方库snapshot-dependencies是快照依赖application才是你写的业务代码和配置文件。改一行 Service只有最后一层失效前面几层全部命中缓存构建和推送都是秒级的事。注意org.springframework.boot.loader.launch.JarLauncher这个类名是 Spring Boot 3.2 之后的新路径。3.2 之前要写成org.springframework.boot.loader.JarLauncher写错了会报Could not find or load main class。升级框架版本时这里是个隐藏的坑。4.3 容器里的 JVM 参数、时区、健康检查-XX:MaxRAMPercentage75.0替代老写法-Xmx512m理由是容器里 JVM 看到的物理内存是宿主机的内存不是容器限制。JDK 10 之后有了容器感知能力用百分比表示最多用容器限额的 75%最稳妥——限额 2G 就用 1.5G限额调到 4G 就自动变成 3G不用改任何配置。如果把-Xmx写死调大容器限额等于白调。时区要配两处系统层面的/etc/localtime和 JVM 层面的-Duser.timezone。少了前者date命令看到的时间不对少了后者new Date()打出来的日志时间不对。两处都配上才能保证日志时间、数据库存的时间、接口返回的时间三者一致。健康检查依赖spring-boot-starter-actuator加了依赖并放行/actuator/health之后可以在 Dockerfile 里写HEALTHCHECK --interval15s --timeout5s --start-period60s --retries10 \ CMD wget -qO- http://localhost:8080/actuator/health || exit 1这里有个小陷阱jammy 底座的镜像不一定自带wget或curl需要自己在 Dockerfile 里装。如果用 alpine 底座busybox 自带wget反而不用额外处理RUN apt-get update apt-get install -y --no-install-recommends curl \ rm -rf /var/lib/apt/lists/*--start-period60s这个参数经常被忽略。SpringBoot 启动要连数据库、加载缓存慢的时候三四十秒很正常这段时间里的健康检查失败不应该算数start-period就是给启动过程的宽限期。5. Compose 编排两个容器怎么接上5.1 docker-compose.yml 完整示例services: backend: build: ./backend image: myapp-backend:1.0.0 container_name: myapp-backend environment: - SPRING_PROFILES_ACTIVEprod - SPRING_DATASOURCE_URLjdbc:mysql://mysql:3306/app?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai - SPRING_DATASOURCE_USERNAMEapp - SPRING_DATASOURCE_PASSWORD${DB_PASSWORD} - SPRING_DATA_REDIS_HOSTredis - TZAsia/Shanghai volumes: - ./data/upload:/app/upload - ./logs/backend:/app/logs healthcheck: test: [CMD, wget, -qO-, http://localhost:8080/actuator/health] interval: 15s timeout: 5s retries: 10 start_period: 60s networks: - appnet restart: unless-stopped frontend: build: context: ./frontend image: myapp-frontend:1.0.0 container_name: myapp-frontend ports: - 80:80 depends_on: backend: condition: service_healthy networks: - appnet restart: unless-stopped networks: appnet: driver: bridgeports只出现在frontend上backend一条都没有。这不是偷懒是刻意的后端只需要在appnet这个网络里被 nginx 访问到没有任何理由让它对宿主机暴露端口。少开一个端口就少一份被扫描、被直接调接口的风险。SPRING_DATASOURCE_PASSWORD${DB_PASSWORD}用的是变量替换实际值放在同目录的.env文件里。.env必须写进.gitignore同时可以提供一个.env.example作为模板提交到仓库让新同事知道要填哪些项。5.2 同源反代和跨域直连的取舍回到第 1 节说的两种拓扑用表格对比一下真实差异维度nginx 同源反代浏览器直连后端CORS 配置不需要必须配且带 Cookie 时还要allowCredentials后端端口暴露不需要必须暴露前端镜像可复用性高只依赖相对路径低接口地址写死灰度/迁移改 nginx 配置即可要重新构建前端本地开发需要 devServer 代理直接就能调本地开发阶段用vite.config.js里的server.proxy模拟同样的转发关系这样本地和线上的请求路径完全一致不会出现本地能跑线上 404的情况export default { server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }5.3 环境变量到 SpringBoot 配置的映射表宽松绑定规则不难但对不上就是启动失败列一张常用对照表备查环境变量对应配置项SPRING_PROFILES_ACTIVEspring.profiles.activeSPRING_DATASOURCE_URLspring.datasource.urlSPRING_DATASOURCE_USERNAMEspring.datasource.usernameSPRING_DATA_REDIS_HOSTspring.data.redis.hostSERVER_PORTserver.portSERVER_SERVLET_CONTEXT_PATHserver.servlet.context-pathLOGGING_LEVEL_ROOTlogging.level.root有一类容易踩的坑YAML 里的数组或 Map 类型用环境变量表达比较别扭比如spring.datasource.hikari下面一堆参数。这种情况我一般直接挂一个application-prod.yml到容器里比堆十几个环境变量清爽。5.4 启动顺序、健康检查和数据卷depends_on只保证后端容器先启动不保证后端能用了。加上condition: service_healthy之后compose 会等后端健康检查通过再去启动前端避免刚部署完访问页面就吃 502。这个条件需要 Docker Compose v2 才支持老版本只能靠restart: unless-stopped兜底。数据卷那两行是必须的。./data/upload:/app/upload让上传的文件落在宿主机上容器重建不会丢./logs/backend:/app/logs让日志落地方便排查。如果应用把文件写在容器内部路径又不挂卷docker compose down之后这些文件就彻底消失了——这个坑我见过不止一次通常是在用户上传了半个月头像之后才被发现。6. 联调排错按现象反查原因容器化的排错有个好处现象和原因之间的对应关系比传统部署更清晰。下面这几个是我遇到频率最高的。6.1 页面能打开接口全 404先看浏览器网络面板里的请求路径。如果是/api/user/list到了后端变成/user/list而接口是/api/user/list那就是proxy_pass少了或多了一个斜杠。回过头看第 3.3 节的表。另一种情况后端配了server.servlet.context-path/api同时 nginx 的proxy_pass又剥掉了/api结果后端收到的路径变成了/user/list而它期望的是/api/user/list。这两个地方只能配一个配两个就是双重前缀。排查命令很简单进容器里手测一下docker compose exec frontend sh wget -qO- http://backend:8080/actuator/health能通说明网络没问题问题在路径规则上。6.2 刷新页面就 404点链接却正常这是 history 路由的经典表现。点击链接走的是前端路由不经过服务器按 F5 走的是服务器服务器找不到/user/list这个文件返回 404。修复方法就是 3.3 节里的try_files $uri $uri/ /index.html。如果用的是 hash 模式URL 里带#则不会有这个问题但代价是 URL 不好看、不利于 SEO。现在基本都是 history 模式加 nginx 兜底的组合。6.3 502 Bad Gateway 和 Connection refused 的三种成因502 是 nginx 找不到能连上的后端具体原因通常有三种后端容器根本没起来。docker compose ps看一下状态如果后端在不停重启docker compose logs backend里会直接告诉你原因最常见的是数据库连不上或者配置文件解析失败。服务名写错了。nginx 里写proxy_pass http://backend:8080的backend必须和 compose 里的服务名完全一致大小写敏感。两个容器不在同一个网络里。compose 默认会创建一个网络并把所有服务加进去但如果有一边写了network_mode: host或者手动指定了别的网络DNS 就解析不了。排查顺序建议是先docker compose ps看状态再docker compose logs看日志最后进容器wget手测。三步下来基本能定位。6.4 上传大文件报 413、日志时间差 8 小时413 是 nginx 的client_max_body_size默认值 1M 拦下来的改成 50m 即可。注意 SpringBoot 那边也有spring.servlet.multipart.max-file-size和max-request-size两处都要放开只改一处会出现nginx 放行了后端拒绝的现象。时间差 8 小时要看三个地方容器系统时区、JVM 时区、数据库连接串里的serverTimezone。三个都对上才不会有问题。docker compose exec backend date是最快的验证方式输出的时间不对就说明容器层面没配对。6.5 exec format error 与镜像架构不一致exec /usr/local/bin/java: exec format error这个报错的意思很直白镜像的 CPU 架构和当前机器对不上。在 M 系列芯片的 Mac 上构建出来的镜像是arm64推到 x86_64 的服务器上就跑不起来。反过来也一样。docker buildx build --platform linux/amd64 -t myapp-backend:1.0.0 ./backend docker inspect myapp-backend:1.0.0 | grep Architecture发布镜像前用docker inspect确认一下架构或者直接在 CI 里加--platform参数省得推到服务器上才发现。7. 上线之后发布、回滚、日志与瘦身7.1 发布和回滚靠的是 tag 而不是 latestlatest这个标签的坑在于它会被覆盖而且docker compose pull在本地有同名镜像时不一定拉最新的。生产环境的做法是每个版本打个明确 tagdocker build -t myapp-backend:1.0.3 ./backend docker tag myapp-backend:1.0.3 registry.example.com/myapp-backend:1.0.3 docker push registry.example.com/myapp-backend:1.0.3 # compose 里把 image 指到具体 tag docker compose up -d回滚就是改回上一个 tag 再up -d几十秒的事。这个能力在出事的时候非常值钱值得一开始就规划好。更新流程有个小细节docker compose up -d --build会先重建镜像再启动看着方便但它同时改了构建和运行两件事。我更习惯拆开先docker compose build确认构建成功再docker compose up -d。构建失败时至少现场还是好的不会出现镜像没建成容器先停了的尴尬局面。7.2 日志不落地等于没日志容器默认的日志驱动是json-file把所有标准输出写到宿主机磁盘上而且没有大小限制。一个疯狂打日志的应用跑一个月能把服务器磁盘写满。两种处理方式。第一种是限制单文件大小和数量services: backend: logging: driver: json-file options: max-size: 50m max-file: 5第二种是应用自己写文件到挂载出来的卷compose 里配./logs/backend:/app/logslogback 配置里输出路径指到/app/logs。这种方式的好处是日志格式完整、便于归档坏处是不能用docker compose logs直接看。我的习惯是两者结合docker compose logs用来快速看启动阶段的问题业务日志写文件用于长期排查。另外别忘了把logging.level.root在生产环境调到info或warndebug级别在生产环境打出来的量级非常可观。7.3 非 root 运行和镜像瘦身默认情况下容器里的进程是 root。这在大多数场景下能跑但不合规范一旦容器被突破攻击者拿到的是宿主机的 root 上下文虽然还有命名空间隔离但风险确实更高。后端镜像加一个普通用户RUN groupadd -r app useradd -r -g app -d /app -s /sbin/nologin app \ chown -R app:app /app USER app前端用的nginx:alpine镜像里已经有nginx用户但因为要监听 80 端口非 root 会起不来需要改成监听 8080 再映射出去。这一步略显麻烦很多项目就直接省了风险自己权衡。瘦身方面node:20-alpine换成node:20-alpine构建阶段已经是最优真正的空间都在构建阶段的临时层里——多阶段构建本身就把它们丢掉了。检查最终镜像大小用docker images如果前端镜像超过 100M、后端超过 400M就有优化的必要了。7.4 磁盘清理别让缓存把服务器撑爆Docker 的构建缓存、停止的容器、悬空镜像会持续占用磁盘。定期清理# 清理悬空镜像和停止的容器安全 docker system prune # 连未被使用的镜像一起清有风险正在用的不受影响 docker system prune -a # 只看占用 docker system df注意docker system prune -a会删掉所有没有被容器引用的镜像如果你的回滚方案依赖本地保留旧 tag 的镜像这条命令会把回滚能力一起删掉。生产环境慎用建议写成只清构建缓存docker builder prune。另外构建缓存本身也占空间如果服务器磁盘紧张可以在构建时加--no-cache换空间或者定期docker builder prune --filter until168h只清理一周前的缓存。8. 上线前跑一遍这份自检清单这套流程最后沉淀成一张清单每次部署新项目或者接手别人的容器化项目时对着过一遍能省下大量返工时间检查项期望结果前端接口前缀相对路径/api代码里搜不到具体 IP 或域名后端配置外部化镜像里没有明文密码库地址来自环境变量.dockerignore含node_modules、dist、.git、targetCOPY 顺序锁文件在前源码在后proxy_pass斜杠与后端 context-path 搭配不出现双重前缀history 路由nginx 有try_files $uri $uri/ /index.html上传限制nginxclient_max_body_size与 Spring 的 multipart 配置一致时区容器系统时区、JVM 时区、数据库连接串三处一致健康检查后端有/actuator/health前端depends_on用了service_healthy数据持久化上传目录、日志目录都挂到了宿主机镜像 tag用明确版本号不用latest日志上限配了max-size和max-file构建架构与服务器 CPU 架构一致我个人在这套东西上最大的体会是容器化的难点从来不在 Docker 本身Docker 的语法几天就能学完真正的成本在于想清楚哪些东西属于镜像、哪些属于环境以及把前端构建期、后端运行期这两套完全不同的配置逻辑摆正。这两件事想明白了Dockerfile 只是把它们翻译成几行指令而已。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询