<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Vue on WYF 的技术博客</title><link>https://saflyer.cn/tags/vue/</link><description>Recent content in Vue on WYF 的技术博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Wed, 16 Sep 2026 15:11:46 +0800</lastBuildDate><atom:link href="https://saflyer.cn/tags/vue/index.xml" rel="self" type="application/rss+xml"/><item><title>Linux运维 03：Jenkins 流水线部署前后端项目（MySQL/Redis 容器化）踩坑全记录</title><link>https://saflyer.cn/posts/jenkins-pipeline-deploy-fullstack/</link><pubDate>Wed, 16 Sep 2026 15:01:32 +0800</pubDate><guid>https://saflyer.cn/posts/jenkins-pipeline-deploy-fullstack/</guid><description>&lt;img src="https://saflyer.cn/posts/jenkins-pipeline-deploy-fullstack/cover.webp" alt="Featured image of post Linux运维 03：Jenkins 流水线部署前后端项目（MySQL/Redis 容器化）踩坑全记录" /&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;接上一篇《在一台已有 Jenkins 的服务器上，再搭一套完全隔离的 Jenkins》。&#10;本文记录把 myapp 项目（基于 ContiNew Admin 4.2.0 二开：Spring Boot 3.x 后端 + Vue3 前端）部署到测试环境（10.10.10.44）的全过程：MySQL/Redis 容器化、两条 Jenkins 流水线、后端/前端上线，以及一路踩到的 &lt;strong&gt;9 个坑&lt;/strong&gt;，每个都给出报错原文、根因和修复方法。&#10;（文中密码、密钥均已脱敏，请替换成自己的；脚本在真实环境验证通过。）&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一目标架构"&gt;&lt;a href="#%e4%b8%80%e7%9b%ae%e6%a0%87%e6%9e%b6%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;一、目标架构&#10;&lt;/h2&gt;&lt;p&gt;服务器 10.10.10.44 上已有 bid 项目的整套容器（后端 8080/8081、前端 80、snailjob 17888）和主机版 MySQL 5.7。myapp 项目全部新开端口，与 bid 完全隔离：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;组件&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;容器名&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;端口&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;镜像&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;说明&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Jenkins（CI）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;jenkins2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8082 / 50001&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;jenkins/jenkins:lts（2.568.3）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;上一篇已搭好&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MySQL&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;myapp-mysql&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;3307&lt;/strong&gt;→3306&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;mysql:8.0.42&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;库 &lt;code&gt;continew_admin&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Redis&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;myapp-redis&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;6379&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;redis:7.2.8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AOF 持久化，用 db 1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;后端&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;myapp-backend-server&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;18000（host 网络）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;自构建（Liberica JDK 17）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;continew-admin.jar&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;前端&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;myapp-frontend-server&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;8085&lt;/strong&gt;→80&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;自构建（nginx:1.27.0）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;dist + 反代后端&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;数据流：浏览器 → 8085（nginx）→ 静态页面；页面请求 &lt;code&gt;/api/*&lt;/code&gt; → nginx 反代 → 后端 18000 → MySQL 3307 / Redis 6379。&lt;/p&gt;&#10;&lt;p&gt;两条 Jenkins 流水线（复刻 bid 老任务的模式）：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;1_后端_myapp-admin_测试&lt;/code&gt;：git 拉代码 → Maven 构建 jar → scp 到服务器 → 远程构建镜像并重启容器&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;2_前端_myapp-admin-ui_测试&lt;/code&gt;：git 拉代码 → pnpm 构建 dist → scp 到服务器 → 远程构建镜像并重启容器&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="二mysql-容器化"&gt;&lt;a href="#%e4%ba%8cmysql-%e5%ae%b9%e5%99%a8%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;二、MySQL 容器化&#10;&lt;/h2&gt;&lt;h3 id="21-3306-之争为什么最终用了-3307"&gt;&lt;a href="#21-3306-%e4%b9%8b%e4%ba%89%e4%b8%ba%e4%bb%80%e4%b9%88%e6%9c%80%e7%bb%88%e7%94%a8%e4%ba%86-3307" class="header-anchor"&gt;&lt;/a&gt;2.1 3306 之争：为什么最终用了 3307&#10;&lt;/h3&gt;&lt;p&gt;最初的设想是直接用主机上已有的 MySQL（3306），但一查之下放弃了：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;主机 MySQL root 密码未知&lt;/strong&gt;：&lt;code&gt;mysql -uroot -p&amp;lt;候选密码&amp;gt;&lt;/code&gt; 试了两个候选密码，都是 &lt;code&gt;ERROR 1045 (28000): Access denied&lt;/code&gt;。它是 2026 年 1 月别人手工编译安装（/usr/local/mysql）的 MySQL &lt;strong&gt;5.7.38&lt;/strong&gt;。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;里面有个来路不明的业务库 &lt;code&gt;skb&lt;/code&gt;&lt;/strong&gt;：跑了 8 个多月，虽然当前没有活跃连接（&lt;code&gt;ss -tn state established | grep 3306&lt;/code&gt; 为空），但改密码、升级都有不可控风险。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;项目官方要求 MySQL 8.0+&lt;/strong&gt;（ContiNew Admin 文档），虽然扫过建表 SQL 没发现 8.0 专属语法，5.7&amp;quot;也许&amp;quot;能跑，但没必要赌。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;决定：&lt;strong&gt;主机 MySQL 一个字不动&lt;/strong&gt;，myapp 用独立容器跑 mysql:8。宿主 3306 被占，容器映射 &lt;strong&gt;3307:3306&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;h3 id="22-myapp-mysql-compose"&gt;&lt;a href="#22-myapp-mysql-compose" class="header-anchor"&gt;&lt;/a&gt;2.2 myapp-mysql compose&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;/opt/dockerfile/myapp-mysql/docker-compose.yml&lt;/code&gt;（最终版）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-0-1"&gt;&lt;a class="lnlinks" href="#hl-0-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-2"&gt;&lt;a class="lnlinks" href="#hl-0-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-3"&gt;&lt;a class="lnlinks" href="#hl-0-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-4"&gt;&lt;a class="lnlinks" href="#hl-0-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-5"&gt;&lt;a class="lnlinks" href="#hl-0-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-6"&gt;&lt;a class="lnlinks" href="#hl-0-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-7"&gt;&lt;a class="lnlinks" href="#hl-0-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-8"&gt;&lt;a class="lnlinks" href="#hl-0-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-9"&gt;&lt;a class="lnlinks" href="#hl-0-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-10"&gt;&lt;a class="lnlinks" href="#hl-0-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-11"&gt;&lt;a class="lnlinks" href="#hl-0-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-12"&gt;&lt;a class="lnlinks" href="#hl-0-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-13"&gt;&lt;a class="lnlinks" href="#hl-0-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-14"&gt;&lt;a class="lnlinks" href="#hl-0-14"&gt;14&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-15"&gt;&lt;a class="lnlinks" href="#hl-0-15"&gt;15&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-16"&gt;&lt;a class="lnlinks" href="#hl-0-16"&gt;16&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-17"&gt;&lt;a class="lnlinks" href="#hl-0-17"&gt;17&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-18"&gt;&lt;a class="lnlinks" href="#hl-0-18"&gt;18&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-19"&gt;&lt;a class="lnlinks" href="#hl-0-19"&gt;19&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-20"&gt;&lt;a class="lnlinks" href="#hl-0-20"&gt;20&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql:8.0.42&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myapp-mysql&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3307:3306&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/opt/docker/myapp-mysql/data/:/var/lib/mysql/&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TZ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Asia/Shanghai&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;MySQL密码&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 初始化数据库（表结构由后端 Liquibase 自动创建）&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;continew_admin&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="l"&gt;default-authentication-plugin=mysql_native_password&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="l"&gt;character-set-server=utf8mb4&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="l"&gt;collation-server=utf8mb4_general_ci&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="l"&gt;explicit_defaults_for_timestamp=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="l"&gt;lower_case_table_names=1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;数据库只需要建库：&lt;code&gt;MYSQL_DATABASE: continew_admin&lt;/code&gt;。&lt;strong&gt;表结构不用手工导&lt;/strong&gt;——后端首次启动时 Liquibase 会自动执行全部变更集建表。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h3 id="23-坑-1容器反复重启mysqld-报-permission-denied"&gt;&lt;a href="#23-%e5%9d%91-1%e5%ae%b9%e5%99%a8%e5%8f%8d%e5%a4%8d%e9%87%8d%e5%90%afmysqld-%e6%8a%a5-permission-denied" class="header-anchor"&gt;&lt;/a&gt;2.3 坑 1：容器反复重启，mysqld 报 Permission denied&#10;&lt;/h3&gt;&lt;p&gt;第一次 &lt;code&gt;docker-compose up -d&lt;/code&gt; 后容器状态一直是 Restarting，日志：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-1-1"&gt;&lt;a class="lnlinks" href="#hl-1-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-2"&gt;&lt;a class="lnlinks" href="#hl-1-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-3"&gt;&lt;a class="lnlinks" href="#hl-1-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mysqld: Can&amp;#39;t read dir of &amp;#39;/etc/mysql/conf.d/&amp;#39; (OS errno 13 - Permission denied)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mysqld: [ERROR] Stopped processing the &amp;#39;includedir&amp;#39; directive in file /etc/my.cnf at line 36.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mysqld: [ERROR] Fatal error in defaults handling. Program aborted!&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h4 id="报错在说什么"&gt;&lt;a href="#%e6%8a%a5%e9%94%99%e5%9c%a8%e8%af%b4%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;报错在说什么&#10;&lt;/h4&gt;&lt;p&gt;MySQL 的主配置文件 &lt;code&gt;/etc/my.cnf&lt;/code&gt; 第 36 行有一条 &lt;code&gt;!includedir /etc/mysql/conf.d/&lt;/code&gt;，意思是&amp;quot;启动时把 conf.d 目录下所有 .cnf 都读进来&amp;quot;。所以 mysqld 启动的&lt;strong&gt;第一步&lt;/strong&gt;就是去扫这个目录——&lt;strong&gt;哪怕它是空的，也必须能打开它&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;errno 13&lt;/code&gt; 就是 Linux 的 &lt;code&gt;EACCES&lt;/code&gt;（Permission denied）：mysqld 连目录都进不去，直接判定&amp;quot;配置加载失败&amp;quot;，放弃启动（Fatal error）。于是容器起来 → 挂掉 → 重启 → 又挂掉，状态一直是 Restarting。&lt;/p&gt;&#10;&lt;h4 id="根因umask-027--容器内外的人不一样"&gt;&lt;a href="#%e6%a0%b9%e5%9b%a0umask-027--%e5%ae%b9%e5%99%a8%e5%86%85%e5%a4%96%e7%9a%84%e4%ba%ba%e4%b8%8d%e4%b8%80%e6%a0%b7" class="header-anchor"&gt;&lt;/a&gt;根因：umask 027 + 容器内外的&amp;quot;人&amp;quot;不一样&#10;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;先说 umask&lt;/strong&gt;：它是新建文件/目录时默认权限的&amp;quot;减法模板&amp;quot;。&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;umask&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;新建目录权限&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;长这样&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;含义&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;022（普通服务器默认）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;755&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;drwxr-xr-x&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;所有人都能读、能进入&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;027（这台等保镜像）&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;750&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;drwxr-x---&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;属主全权、同组可读、&lt;strong&gt;其他人一律没门&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;code&gt;drwxr-x---&lt;/code&gt; 三个一组：&lt;code&gt;rwx&lt;/code&gt;（属主 root）/ &lt;code&gt;r-x&lt;/code&gt;（root 组）/ &lt;code&gt;---&lt;/code&gt;（其他人，什么都做不了）。&lt;/p&gt;&#10;&lt;p&gt;这台服务器 root 的 &lt;strong&gt;umask 是 027&lt;/strong&gt;（等保合规镜像的加固项），我用 root 跑 &lt;code&gt;mkdir -p&lt;/code&gt; 建出来的 &lt;code&gt;/opt/docker&lt;/code&gt;、&lt;code&gt;/opt/docker/myapp-mysql/conf&lt;/code&gt;、&lt;code&gt;/opt/docker/myapp-mysql/data&lt;/code&gt; 全是 &lt;strong&gt;750、属主 root&lt;/strong&gt;——我自己毫无感觉，因为 root 无视权限位。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;再说容器里是谁在访问&lt;/strong&gt;：bind mount（&lt;code&gt;- .../conf/:/etc/mysql/conf.d/&lt;/code&gt;）把宿主机目录&lt;strong&gt;原封不动&lt;/strong&gt;映射进容器，权限位和属主 uid 保持不变。而 mysql:8 官方镜像里，mysqld 故意&lt;strong&gt;不用 root 跑&lt;/strong&gt;（安全设计），它用的是 mysql 用户（uid 999）。&lt;/p&gt;&#10;&lt;p&gt;于是局面变成：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-2-1"&gt;&lt;a class="lnlinks" href="#hl-2-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-2"&gt;&lt;a class="lnlinks" href="#hl-2-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-3"&gt;&lt;a class="lnlinks" href="#hl-2-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-4"&gt;&lt;a class="lnlinks" href="#hl-2-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-5"&gt;&lt;a class="lnlinks" href="#hl-2-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-6"&gt;&lt;a class="lnlinks" href="#hl-2-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;目录: drwxr-x--- 属主 root(uid 0)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;进程: mysqld 身份 mysql(uid 999)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 既不是属主，也不是同组&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 落入其他人权限位 &amp;#34;---&amp;#34; → 什么都做不了 → errno 13&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;关键认知：&lt;strong&gt;Linux 权限看的是 uid 数字，不是用户名&lt;/strong&gt;。宿主机上 root 的私有目录挂进容器，对容器里的 uid 999 来说就是&amp;quot;别人的私有目录&amp;quot;。&lt;/p&gt;&#10;&lt;h4 id="修复两步都要各解决一个问题"&gt;&lt;a href="#%e4%bf%ae%e5%a4%8d%e4%b8%a4%e6%ad%a5%e9%83%bd%e8%a6%81%e5%90%84%e8%a7%a3%e5%86%b3%e4%b8%80%e4%b8%aa%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;修复（两步都要，各解决一个问题）&#10;&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-3-1"&gt;&lt;a class="lnlinks" href="#hl-3-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-2"&gt;&lt;a class="lnlinks" href="#hl-3-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-3"&gt;&lt;a class="lnlinks" href="#hl-3-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-4"&gt;&lt;a class="lnlinks" href="#hl-3-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-5"&gt;&lt;a class="lnlinks" href="#hl-3-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-6"&gt;&lt;a class="lnlinks" href="#hl-3-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-7"&gt;&lt;a class="lnlinks" href="#hl-3-7"&gt;7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-8"&gt;&lt;a class="lnlinks" href="#hl-3-8"&gt;8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-9"&gt;&lt;a class="lnlinks" href="#hl-3-9"&gt;9&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1) 初版 compose 里有一行 conf.d 挂载，既然没有自定义配置要放，直接删掉最干净&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sed -i &lt;span class="s2"&gt;&amp;#34;\|/etc/mysql/conf.d|d&amp;#34;&lt;/span&gt; /opt/dockerfile/myapp-mysql/docker-compose.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 2) 放开数据目录权限&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod &lt;span class="m"&gt;755&lt;/span&gt; /opt/docker /opt/docker/myapp-mysql /opt/docker/myapp-mysql/data&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker-compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; myapp-mysql mysql -uroot -p&amp;lt;MySQL密码&amp;gt; -e &lt;span class="s2"&gt;&amp;#34;SHOW DATABASES;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# → continew_admin 出现&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;第 1 步（删 conf.d 挂载）&lt;/strong&gt;：这行挂载本来是&amp;quot;以防以后要加自定义配置&amp;quot;预留的，实际一个配置文件都没放。空目录惹出致命错，&lt;strong&gt;不如不挂&lt;/strong&gt;——mysql 镜像自带的 /etc/mysql/conf.d 本来就是空的，删掉零损失。（另一条路是保留挂载 + chmod 755，也能修好，但留着一个没用的挂载点没意义。）&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;第 2 步（chmod 755 数据目录）&lt;/strong&gt;：conf.d 可以删，&lt;strong&gt;数据目录 &lt;code&gt;/var/lib/mysql&lt;/code&gt; 不能不挂&lt;/strong&gt;（不然数据全在容器层里，删容器就没）。它同样是 750 root:root，必须放开。755 的含义：属主可读可写可进入，其他人&lt;strong&gt;可读可进入、不可写&lt;/strong&gt;——够 mysql 用户用了，又不至于像 777 那样谁都能写。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="这个坑为什么还会再出现一次"&gt;&lt;a href="#%e8%bf%99%e4%b8%aa%e5%9d%91%e4%b8%ba%e4%bb%80%e4%b9%88%e8%bf%98%e4%bc%9a%e5%86%8d%e5%87%ba%e7%8e%b0%e4%b8%80%e6%ac%a1" class="header-anchor"&gt;&lt;/a&gt;这个坑为什么&amp;quot;还会再出现一次&amp;quot;&#10;&lt;/h4&gt;&lt;p&gt;同一个根因换了个马甲出现在前端（见坑 6）：Jenkins 容器 umask 也是 027 → &lt;code&gt;pnpm build&lt;/code&gt; 出来的 dist 文件是 640/750 root:root → scp 原样传到服务器 → nginx 容器里 worker 进程用 &lt;strong&gt;nginx 用户（非 root）&lt;/strong&gt; 去读 → 又是&amp;quot;其他人权限位 &lt;code&gt;---&lt;/code&gt; 什么都做不了&amp;quot; → 浏览器看到 403 Forbidden。&lt;/p&gt;&#10;&lt;p&gt;这两个坑是同一条规律：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;strong&gt;&amp;ldquo;文件属主是 root 且权限 750/640&amp;rdquo; + &amp;ldquo;消费它的进程是另一个 uid&amp;rdquo; = 必然 Permission denied。&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;经验：在等保加固过的服务器上（umask 027 是标配），凡是挂载进容器给非 root 进程用的目录/文件，建完顺手 &lt;code&gt;chmod 755&lt;/code&gt;（或 &lt;code&gt;chown&lt;/code&gt; 成容器里的 uid），可以一次避开这一类所有坑。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h2 id="三redis-容器化"&gt;&lt;a href="#%e4%b8%89redis-%e5%ae%b9%e5%99%a8%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;三、Redis 容器化&#10;&lt;/h2&gt;&lt;h3 id="31-先确认主机-redis-里没有重要数据"&gt;&lt;a href="#31-%e5%85%88%e7%a1%ae%e8%ae%a4%e4%b8%bb%e6%9c%ba-redis-%e9%87%8c%e6%b2%a1%e6%9c%89%e9%87%8d%e8%a6%81%e6%95%b0%e6%8d%ae" class="header-anchor"&gt;&lt;/a&gt;3.1 先确认主机 Redis 里没有重要数据&#10;&lt;/h3&gt;&lt;p&gt;主机上还有一个源码编译的 Redis（&lt;code&gt;/home/redis6/src/redis-server 0.0.0.0:6379&lt;/code&gt;），2026-01-07 编译安装，已经连续跑了 8 个多月。替换它之前必须确认没数据：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-4-1"&gt;&lt;a class="lnlinks" href="#hl-4-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-2"&gt;&lt;a class="lnlinks" href="#hl-4-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-3"&gt;&lt;a class="lnlinks" href="#hl-4-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-4"&gt;&lt;a class="lnlinks" href="#hl-4-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/home/redis6/src/redis-cli -a &amp;lt;Redis密码&amp;gt; info keyspace&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# # Keyspace ← 下面一行都没有，16 个逻辑库零 key&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ss -tn state established &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="m"&gt;6379&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 无任何活跃连接&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;结论：空库 + 无业务连接，可以安全下线。&lt;/p&gt;&#10;&lt;h3 id="32-备份并停用主机-redis"&gt;&lt;a href="#32-%e5%a4%87%e4%bb%bd%e5%b9%b6%e5%81%9c%e7%94%a8%e4%b8%bb%e6%9c%ba-redis" class="header-anchor"&gt;&lt;/a&gt;3.2 备份并停用主机 Redis&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-5-1"&gt;&lt;a class="lnlinks" href="#hl-5-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-2"&gt;&lt;a class="lnlinks" href="#hl-5-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-3"&gt;&lt;a class="lnlinks" href="#hl-5-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/home/redis6/src/redis-cli -a &amp;lt;Redis密码&amp;gt; shutdown nosave&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl disable redis redis-server 2&amp;gt;/dev/null &lt;span class="c1"&gt;# 没有 systemd 服务，纯防御&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mv /home/redis6 /home/redis6.bak.20260916 &lt;span class="c1"&gt;# 备份观察几天，没问题再删&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h3 id="33-myapp-redis-compose"&gt;&lt;a href="#33-myapp-redis-compose" class="header-anchor"&gt;&lt;/a&gt;3.3 myapp-redis compose&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;/opt/dockerfile/myapp-redis/docker-compose.yml&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-6-1"&gt;&lt;a class="lnlinks" href="#hl-6-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-2"&gt;&lt;a class="lnlinks" href="#hl-6-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-3"&gt;&lt;a class="lnlinks" href="#hl-6-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-4"&gt;&lt;a class="lnlinks" href="#hl-6-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-5"&gt;&lt;a class="lnlinks" href="#hl-6-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-6"&gt;&lt;a class="lnlinks" href="#hl-6-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-7"&gt;&lt;a class="lnlinks" href="#hl-6-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-8"&gt;&lt;a class="lnlinks" href="#hl-6-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-9"&gt;&lt;a class="lnlinks" href="#hl-6-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-10"&gt;&lt;a class="lnlinks" href="#hl-6-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-11"&gt;&lt;a class="lnlinks" href="#hl-6-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-12"&gt;&lt;a class="lnlinks" href="#hl-6-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;redis:7.2.8&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myapp-redis&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;6379:6379&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/opt/docker/myapp-redis/data/:/data/&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TZ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Asia/Shanghai&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;redis-server --appendonly yes --requirepass &amp;lt;Redis密码&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-7-1"&gt;&lt;a class="lnlinks" href="#hl-7-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-2"&gt;&lt;a class="lnlinks" href="#hl-7-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-3"&gt;&lt;a class="lnlinks" href="#hl-7-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-4"&gt;&lt;a class="lnlinks" href="#hl-7-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod &lt;span class="m"&gt;755&lt;/span&gt; /opt/docker/myapp-redis /opt/docker/myapp-redis/data &lt;span class="c1"&gt;# umask 坑，同上&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker-compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; myapp-redis redis-cli -a &amp;lt;Redis密码&amp;gt; &lt;span class="nb"&gt;set&lt;/span&gt; __probe ok&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; myapp-redis redis-cli -a &amp;lt;Redis密码&amp;gt; get __probe &lt;span class="c1"&gt;# → &amp;#34;ok&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;设计要点：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;端口沿用 6379、密码沿用主机 Redis 的密码&lt;/strong&gt;——后端配置一行不用改。&lt;/li&gt;&#10;&lt;li&gt;后端配置 &lt;code&gt;REDIS_DB: 1&lt;/code&gt;：同机 bid 项目习惯用 db 0，myapp 用 &lt;strong&gt;db 1&lt;/strong&gt; 做逻辑隔离，就算以后共用实例也不串数据。&lt;/li&gt;&#10;&lt;li&gt;开 AOF（&lt;code&gt;--appendonly yes&lt;/code&gt;）持久化。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="四部署目录与脚本"&gt;&lt;a href="#%e5%9b%9b%e9%83%a8%e7%bd%b2%e7%9b%ae%e5%bd%95%e4%b8%8e%e8%84%9a%e6%9c%ac" class="header-anchor"&gt;&lt;/a&gt;四、部署目录与脚本&#10;&lt;/h2&gt;&lt;p&gt;服务器上的部署文件统一放 &lt;code&gt;/opt/dockerfile/&lt;/code&gt;，数据放 &lt;code&gt;/opt/docker/&lt;/code&gt;（这是 bid 项目的既有约定，照用）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-8-1"&gt;&lt;a class="lnlinks" href="#hl-8-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-2"&gt;&lt;a class="lnlinks" href="#hl-8-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-3"&gt;&lt;a class="lnlinks" href="#hl-8-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-4"&gt;&lt;a class="lnlinks" href="#hl-8-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-5"&gt;&lt;a class="lnlinks" href="#hl-8-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-6"&gt;&lt;a class="lnlinks" href="#hl-8-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-7"&gt;&lt;a class="lnlinks" href="#hl-8-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-8"&gt;&lt;a class="lnlinks" href="#hl-8-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-9"&gt;&lt;a class="lnlinks" href="#hl-8-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-10"&gt;&lt;a class="lnlinks" href="#hl-8-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-11"&gt;&lt;a class="lnlinks" href="#hl-8-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-12"&gt;&lt;a class="lnlinks" href="#hl-8-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-13"&gt;&lt;a class="lnlinks" href="#hl-8-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-14"&gt;&lt;a class="lnlinks" href="#hl-8-14"&gt;14&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-15"&gt;&lt;a class="lnlinks" href="#hl-8-15"&gt;15&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/opt/dockerfile/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── myapp-backend/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── Dockerfile # 后端镜像&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── myapp-backend-compose.yml # 后端编排&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── myapp-backend.shell # 部署脚本（删旧→构建→启动）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── myapp-frontend/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── myapp-frontend.yml # 前端编排&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── images-pkg/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── Dockerfile # 前端镜像&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── nginx/default.conf # nginx 配置&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── build_image.sh # 构建镜像&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── deploy-image-compse.sh # 部署入口（构建+启动）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── dist/ # 流水线 scp 上来的构建产物&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── myapp-mysql/docker-compose.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── myapp-redis/docker-compose.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;后端 Dockerfile（&lt;code&gt;/opt/dockerfile/myapp-backend/Dockerfile&lt;/code&gt;）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-9-1"&gt;&lt;a class="lnlinks" href="#hl-9-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-2"&gt;&lt;a class="lnlinks" href="#hl-9-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-3"&gt;&lt;a class="lnlinks" href="#hl-9-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-4"&gt;&lt;a class="lnlinks" href="#hl-9-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-5"&gt;&lt;a class="lnlinks" href="#hl-9-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-6"&gt;&lt;a class="lnlinks" href="#hl-9-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-7"&gt;&lt;a class="lnlinks" href="#hl-9-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-8"&gt;&lt;a class="lnlinks" href="#hl-9-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-9"&gt;&lt;a class="lnlinks" href="#hl-9-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-10"&gt;&lt;a class="lnlinks" href="#hl-9-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-11"&gt;&lt;a class="lnlinks" href="#hl-9-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-12"&gt;&lt;a class="lnlinks" href="#hl-9-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-13"&gt;&lt;a class="lnlinks" href="#hl-9-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-14"&gt;&lt;a class="lnlinks" href="#hl-9-14"&gt;14&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-15"&gt;&lt;a class="lnlinks" href="#hl-9-15"&gt;15&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;bellsoft/liberica-openjdk-debian:17.0.14&lt;/span&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; continew-admin.jar /app/bin/app.jar&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app/bin&lt;/span&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; mkdir -p /app/logs&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;java&amp;#34;&lt;/span&gt;, &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;-XX:+UseZGC&amp;#34;&lt;/span&gt;, &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;-XX:MetaspaceSize=128m&amp;#34;&lt;/span&gt;, &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;-XX:MaxMetaspaceSize=256m&amp;#34;&lt;/span&gt;, &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;-XX:+HeapDumpOnOutOfMemoryError&amp;#34;&lt;/span&gt;, &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;-XX:HeapDumpPath=/app/logs/oom.hprof&amp;#34;&lt;/span&gt;, &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;-XX:ErrorFile=/app/logs/hs_err_pid%p.log&amp;#34;&lt;/span&gt;, &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;-Djava.security.egd=file:/dev/./urandom&amp;#34;&lt;/span&gt;, &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;-jar&amp;#34;&lt;/span&gt;, &lt;span class="s2"&gt;&amp;#34;app.jar&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;后端编排 &lt;code&gt;myapp-backend-compose.yml&lt;/code&gt;（&lt;strong&gt;全文都是要点&lt;/strong&gt;，后面详解几个环境变量）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-10-1"&gt;&lt;a class="lnlinks" href="#hl-10-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-2"&gt;&lt;a class="lnlinks" href="#hl-10-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-3"&gt;&lt;a class="lnlinks" href="#hl-10-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-4"&gt;&lt;a class="lnlinks" href="#hl-10-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-5"&gt;&lt;a class="lnlinks" href="#hl-10-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-6"&gt;&lt;a class="lnlinks" href="#hl-10-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-7"&gt;&lt;a class="lnlinks" href="#hl-10-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-8"&gt;&lt;a class="lnlinks" href="#hl-10-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-9"&gt;&lt;a class="lnlinks" href="#hl-10-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-10"&gt;&lt;a class="lnlinks" href="#hl-10-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-11"&gt;&lt;a class="lnlinks" href="#hl-10-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-12"&gt;&lt;a class="lnlinks" href="#hl-10-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-13"&gt;&lt;a class="lnlinks" href="#hl-10-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-14"&gt;&lt;a class="lnlinks" href="#hl-10-14"&gt;14&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-15"&gt;&lt;a class="lnlinks" href="#hl-10-15"&gt;15&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-16"&gt;&lt;a class="lnlinks" href="#hl-10-16"&gt;16&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-17"&gt;&lt;a class="lnlinks" href="#hl-10-17"&gt;17&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-18"&gt;&lt;a class="lnlinks" href="#hl-10-18"&gt;18&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-19"&gt;&lt;a class="lnlinks" href="#hl-10-19"&gt;19&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-20"&gt;&lt;a class="lnlinks" href="#hl-10-20"&gt;20&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-21"&gt;&lt;a class="lnlinks" href="#hl-10-21"&gt;21&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-22"&gt;&lt;a class="lnlinks" href="#hl-10-22"&gt;22&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-23"&gt;&lt;a class="lnlinks" href="#hl-10-23"&gt;23&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-24"&gt;&lt;a class="lnlinks" href="#hl-10-24"&gt;24&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-25"&gt;&lt;a class="lnlinks" href="#hl-10-25"&gt;25&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-26"&gt;&lt;a class="lnlinks" href="#hl-10-26"&gt;26&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-27"&gt;&lt;a class="lnlinks" href="#hl-10-27"&gt;27&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-28"&gt;&lt;a class="lnlinks" href="#hl-10-28"&gt;28&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-29"&gt;&lt;a class="lnlinks" href="#hl-10-29"&gt;29&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-30"&gt;&lt;a class="lnlinks" href="#hl-10-30"&gt;30&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-31"&gt;&lt;a class="lnlinks" href="#hl-10-31"&gt;31&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-32"&gt;&lt;a class="lnlinks" href="#hl-10-32"&gt;32&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-33"&gt;&lt;a class="lnlinks" href="#hl-10-33"&gt;33&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;myapp-backend-server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myapp-backend&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myapp-backend-server&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TZ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Asia/Shanghai&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;PROFILES_ACTIVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;prod&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 数据源：本机 myapp-mysql 容器（3307 端口；network_mode: host 所以用 127.0.0.1）&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;DB_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;127.0.0.1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;DB_PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3307&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;DB_USER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;root&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;DB_PWD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;MySQL密码&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;DB_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;continew_admin&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 缓存：本机 myapp-redis 容器 6379，用 db 1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;127.0.0.1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;REDIS_PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6379&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;REDIS_PWD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;Redis密码&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;REDIS_DB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 字段加密密钥（测试环境复用仓库 dev 示例值，正式环境必须换成独立生成的密钥对！）&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FIELD_ENCRYPT_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;AES密钥&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FIELD_ENCRYPT_PUBLIC_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;RSA公钥&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FIELD_ENCRYPT_PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;RSA私钥&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# snail-job 已关闭，但占位符仍需解析，填不会使用的占位值（见坑 6）&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SCHEDULE_USERNAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;admin&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SCHEDULE_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unused-disabled&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SCHEDULE_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unused-disabled&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 关闭 snail-job 调度；application.url 用于跨域放行前端地址&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SPRING_APPLICATION_JSON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;#34;snail-job&amp;#34;:{&amp;#34;enabled&amp;#34;:false},&amp;#34;application&amp;#34;:{&amp;#34;url&amp;#34;:&amp;#34;http://10.10.10.44:8085&amp;#34;}}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/opt/docker/myapp-backend/logs/:/app/logs/&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/opt/docker/myapp-backend/data/file/:/app/data/file/&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;network_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;host&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;部署脚本 &lt;code&gt;myapp-backend.shell&lt;/code&gt;（流水线最后一步远程执行它）：查询并删除旧容器 → 删除旧镜像 → &lt;code&gt;docker build -t myapp-backend .&lt;/code&gt; → &lt;code&gt;docker-compose -f myapp-backend-compose.yml up -d&lt;/code&gt;。&lt;/p&gt;&#10;&lt;h3 id="坑-2部署文件放错了服务器"&gt;&lt;a href="#%e5%9d%91-2%e9%83%a8%e7%bd%b2%e6%96%87%e4%bb%b6%e6%94%be%e9%94%99%e4%ba%86%e6%9c%8d%e5%8a%a1%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;坑 2：部署文件放错了服务器&#10;&lt;/h3&gt;&lt;p&gt;一个值得记录的事故：最初按旧方案的记忆，把整套 &lt;code&gt;/opt/dockerfile/myapp-*&lt;/code&gt; 推到了 &lt;strong&gt;10.10.10.43&lt;/strong&gt;（dev 机），还在它的主机 MySQL 里建了 &lt;code&gt;continew_admin&lt;/code&gt; 库、往 &lt;code&gt;authorized_keys&lt;/code&gt; 写了部署公钥。用户及时指出：应该是 .44。&lt;/p&gt;&#10;&lt;p&gt;清理（一条命令完成，&lt;strong&gt;误操作后及时、完整地回滚&lt;/strong&gt;很重要）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-11-1"&gt;&lt;a class="lnlinks" href="#hl-11-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-2"&gt;&lt;a class="lnlinks" href="#hl-11-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-3"&gt;&lt;a class="lnlinks" href="#hl-11-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-4"&gt;&lt;a class="lnlinks" href="#hl-11-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-5"&gt;&lt;a class="lnlinks" href="#hl-11-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 在 .43 上：&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm -rf /opt/dockerfile/myapp-backend /opt/dockerfile/myapp-frontend /opt/docker/myapp-backend&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/local/mysql/bin/mysql -uroot -p&amp;lt;密码&amp;gt; -e &lt;span class="s2"&gt;&amp;#34;DROP DATABASE IF EXISTS continew_admin;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sed -i.bak &lt;span class="s2"&gt;&amp;#34;/jenkins2-to-43-deploy/d&amp;#34;&lt;/span&gt; /root/.ssh/authorized_keys&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ls /opt/dockerfile/ &lt;span class="c1"&gt;# 确认只剩 bid 项目的目录&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;然后把文件推到 .44，重新生成/安装部署密钥（ed25519 密钥对，公钥追加到 .44 的 &lt;code&gt;/root/.ssh/authorized_keys&lt;/code&gt;），流水线里的 &lt;code&gt;DEPLOY_HOST&lt;/code&gt;、前端 &lt;code&gt;VITE_API_*&lt;/code&gt; 全部改成 .44。&lt;/p&gt;&#10;&lt;h2 id="五后端流水线"&gt;&lt;a href="#%e4%ba%94%e5%90%8e%e7%ab%af%e6%b5%81%e6%b0%b4%e7%ba%bf" class="header-anchor"&gt;&lt;/a&gt;五、后端流水线&#10;&lt;/h2&gt;&lt;p&gt;Jenkins2 新建 Pipeline 任务 &lt;code&gt;1_后端_myapp-admin_测试&lt;/code&gt;（Pipeline script 直接写在任务里）。核心配置：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-12-1"&gt;&lt;a class="lnlinks" href="#hl-12-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-2"&gt;&lt;a class="lnlinks" href="#hl-12-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-3"&gt;&lt;a class="lnlinks" href="#hl-12-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-4"&gt;&lt;a class="lnlinks" href="#hl-12-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-5"&gt;&lt;a class="lnlinks" href="#hl-12-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-6"&gt;&lt;a class="lnlinks" href="#hl-12-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-7"&gt;&lt;a class="lnlinks" href="#hl-12-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-8"&gt;&lt;a class="lnlinks" href="#hl-12-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-9"&gt;&lt;a class="lnlinks" href="#hl-12-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-10"&gt;&lt;a class="lnlinks" href="#hl-12-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-11"&gt;&lt;a class="lnlinks" href="#hl-12-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-groovy" data-lang="groovy"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;jdk&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jdk17&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;maven&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;maven3.9&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// 名字与全局工具配置一致&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;BRANCH_NAME&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;defaultValue:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dev&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;JAR_FILE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;continew-admin.jar&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;LOCAL_JAR_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;continew-server/target/${JAR_FILE_NAME}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DEPLOY_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/opt/dockerfile/myapp-backend&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DEPLOY_SCRIPT_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;myapp-backend.shell&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DEPLOY_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;10.10.10.44&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DEPLOY_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;root&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;GIT_CREDENTIALS_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;myapp-git&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;三个阶段：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;Checkout&lt;/strong&gt;：&lt;code&gt;git branch: &amp;quot;${BRANCH_NAME}&amp;quot;, credentialsId: 'myapp-git', url: 'https://git.example.com/myapp-safe/myapp-admin.git'&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Build&lt;/strong&gt;：&lt;code&gt;mvn clean package -P fat-jar -DskipTests --batch-mode&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Deploy&lt;/strong&gt;（ssh 免密，密钥方案见坑 4）：&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;ssh-keyscan 10.10.10.44 &amp;gt;&amp;gt; ~/.ssh/known_hosts&lt;/code&gt;（首次）&lt;/li&gt;&#10;&lt;li&gt;远程备份旧 jar：&lt;code&gt;mv continew-admin.jar continew-admin.jar.$TIMESTAMP&lt;/code&gt;（可回滚）&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;scp continew-server/target/continew-admin.jar root@10.10.10.44:/opt/dockerfile/myapp-backend/&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;ssh root@10.10.10.44 'cd /opt/dockerfile/myapp-backend &amp;amp;&amp;amp; ./myapp-backend.shell'&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;构建实况：11 个 Maven 模块，enforcer / spotless / checkstyle / flatten 四道质量门禁全过，全量构建约 1 分钟，产物 &lt;code&gt;continew-server/target/continew-admin.jar&lt;/code&gt;（Spring Boot repackage 后的 fat jar）。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;两个无害 WARNING，知道即可：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;maven-compiler-plugin:4.0.0-beta-x has unmet prerequisites&lt;/code&gt; —— 插件 4.x 需要 Maven 4，Maven 3.9 自动回退选了 3.16.0，不影响构建；&lt;/li&gt;&#10;&lt;li&gt;continew-extension 模块 pom 缺插件版本号声明，建议后续补上。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h3 id="坑-3ssh-agent-凭据三连败"&gt;&lt;a href="#%e5%9d%91-3ssh-agent-%e5%87%ad%e6%8d%ae%e4%b8%89%e8%bf%9e%e8%b4%a5" class="header-anchor"&gt;&lt;/a&gt;坑 3：ssh-agent 凭据三连败&#10;&lt;/h3&gt;&lt;p&gt;Deploy 阶段最初写的是 &lt;code&gt;sshagent([env.SSH_CREDENTIALS_ID]) { ... }&lt;/code&gt;，结果连续失败三次：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-13-1"&gt;&lt;a class="lnlinks" href="#hl-13-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ERROR: [ssh-agent] Could not find specified credentials: myapp-ssh-43&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;第 1 次&lt;/strong&gt;：脚本里凭据 ID 还是旧名字 &lt;code&gt;myapp-ssh-43&lt;/code&gt;，且这条 SSH 凭据压根没建（检查 &lt;code&gt;jenkins_home/credentials.xml&lt;/code&gt;，里面只有 &lt;code&gt;myapp-git&lt;/code&gt; 一条）。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;第 2 次&lt;/strong&gt;：直接把任务配置里的 ID 全局替换掉：&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-14-1"&gt;&lt;a class="lnlinks" href="#hl-14-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-2"&gt;&lt;a class="lnlinks" href="#hl-14-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-3"&gt;&lt;a class="lnlinks" href="#hl-14-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sed -i &lt;span class="s2"&gt;&amp;#34;s/myapp-ssh-43/myapp-ssh-44/g; s/10\.10\.10\.43/10.10.10.44/g&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /home/compose-jenkins2/jenkins_home/jobs/*/config.xml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker restart jenkins2&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;第 3 次&lt;/strong&gt;：手动去页面建了凭据再跑，还是 &lt;code&gt;Could not find specified credentials: myapp-ssh-44&lt;/code&gt;——credentials.xml 证据显示 SSH 凭据&lt;strong&gt;从未真正保存成功&lt;/strong&gt;。手动建凭据有 4 个常见失手点：Kind 选错（必须是 SSH Username with private key）、建到了用户级存储而不是 System → Global、ID 没手填变成 UUID、忘了点 Create。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;连续卡三次后换思路：不用 Jenkins 凭据了，用容器内密钥文件&lt;/strong&gt;——反正 jenkins_home 是绑定挂载，宿主机放好容器内就能用：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-15-1"&gt;&lt;a class="lnlinks" href="#hl-15-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-2"&gt;&lt;a class="lnlinks" href="#hl-15-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-3"&gt;&lt;a class="lnlinks" href="#hl-15-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-4"&gt;&lt;a class="lnlinks" href="#hl-15-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-5"&gt;&lt;a class="lnlinks" href="#hl-15-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-6"&gt;&lt;a class="lnlinks" href="#hl-15-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-7"&gt;&lt;a class="lnlinks" href="#hl-15-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-8"&gt;&lt;a class="lnlinks" href="#hl-15-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-9"&gt;&lt;a class="lnlinks" href="#hl-15-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-10"&gt;&lt;a class="lnlinks" href="#hl-15-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-11"&gt;&lt;a class="lnlinks" href="#hl-15-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-12"&gt;&lt;a class="lnlinks" href="#hl-15-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-13"&gt;&lt;a class="lnlinks" href="#hl-15-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-14"&gt;&lt;a class="lnlinks" href="#hl-15-14"&gt;14&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-15"&gt;&lt;a class="lnlinks" href="#hl-15-15"&gt;15&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-16"&gt;&lt;a class="lnlinks" href="#hl-15-16"&gt;16&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-17"&gt;&lt;a class="lnlinks" href="#hl-15-17"&gt;17&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-18"&gt;&lt;a class="lnlinks" href="#hl-15-18"&gt;18&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-19"&gt;&lt;a class="lnlinks" href="#hl-15-19"&gt;19&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 私钥放进 jenkins_home（容器内即 /var/jenkins_home/.ssh/）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p /home/compose-jenkins2/jenkins_home/.ssh&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp /home/compose-jenkins2/jenkins2_deploy_key /home/compose-jenkins2/jenkins_home/.ssh/deploy_key&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;%s\n&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Host 10.10.10.44&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39; IdentityFile /var/jenkins_home/.ssh/deploy_key&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39; IdentitiesOnly yes&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;gt; /home/compose-jenkins2/jenkins_home/.ssh/config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chown -R 1000:1000 /home/compose-jenkins2/jenkins_home/.ssh&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod &lt;span class="m"&gt;700&lt;/span&gt; /home/compose-jenkins2/jenkins_home/.ssh&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod &lt;span class="m"&gt;600&lt;/span&gt; /home/compose-jenkins2/jenkins_home/.ssh/&lt;span class="o"&gt;{&lt;/span&gt;deploy_key,config&lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 容器内验证免密通&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; jenkins2 ssh -o &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no -o &lt;span class="nv"&gt;BatchMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yes &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; root@10.10.10.44 &lt;span class="s2"&gt;&amp;#34;echo CONTAINER_SSH_OK&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# → CONTAINER_SSH_OK&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 把流水线里的 sshagent 步骤换成普通 script 块（直接改任务 config.xml 再重启）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sed -i &lt;span class="s2"&gt;&amp;#34;s/sshagent(\[env\.SSH_CREDENTIALS_ID\])/script/&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /home/compose-jenkins2/jenkins_home/jobs/*/config.xml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker restart jenkins2&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;要点：&lt;code&gt;IdentitiesOnly yes&lt;/code&gt; 防止 ssh-agent 里其它钥匙干扰；&lt;code&gt;BatchMode=yes&lt;/code&gt; 验证时确保不会卡在交互输入。改完之后&lt;strong&gt;不用再建任何凭据&lt;/strong&gt;，直接跑构建即通。&lt;/p&gt;&#10;&lt;h3 id="坑-4myapp-backendshell-permission-deniedexit-126"&gt;&lt;a href="#%e5%9d%91-4myapp-backendshell-permission-deniedexit-126" class="header-anchor"&gt;&lt;/a&gt;坑 4：&lt;code&gt;./myapp-backend.shell: Permission denied&lt;/code&gt;（exit 126）&#10;&lt;/h3&gt;&lt;p&gt;Maven 构建全绿，Deploy 传完 jar，最后一步远程执行部署脚本报：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-16-1"&gt;&lt;a class="lnlinks" href="#hl-16-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-16-2"&gt;&lt;a class="lnlinks" href="#hl-16-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-16-3"&gt;&lt;a class="lnlinks" href="#hl-16-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+ ssh root@10.10.10.44 &amp;#39;cd /opt/dockerfile/myapp-backend &amp;amp;&amp;amp; ./myapp-backend.shell&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bash: line 1: ./myapp-backend.shell: Permission denied&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ERROR: script returned exit code 126&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;根因&lt;/strong&gt;：部署文件是用 tar 包传到服务器的——本地 &lt;code&gt;tar -czf&lt;/code&gt; 打包时存的是 macOS 上写出来的 644 权限，&lt;strong&gt;tar 解包会用包内权限覆盖服务器上已有的文件&lt;/strong&gt;。第一次传完我在服务器上 &lt;code&gt;chmod +x&lt;/code&gt; 过，但第二次传包（改推 .44 那次）又把执行权限覆盖掉了。&lt;/p&gt;&#10;&lt;p&gt;修复（三个脚本一起处理，并记住：&lt;strong&gt;每次重新传 tar 包后都要补一遍&lt;/strong&gt;）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-17-1"&gt;&lt;a class="lnlinks" href="#hl-17-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-17-2"&gt;&lt;a class="lnlinks" href="#hl-17-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-17-3"&gt;&lt;a class="lnlinks" href="#hl-17-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod +x /opt/dockerfile/myapp-backend/myapp-backend.shell &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /opt/dockerfile/myapp-frontend/images-pkg/build_image.sh &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /opt/dockerfile/myapp-frontend/images-pkg/deploy-image-compse.sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;重新构建，部署通过。&lt;/p&gt;&#10;&lt;h3 id="坑-5snail-job-占位符导致后端起不来"&gt;&lt;a href="#%e5%9d%91-5snail-job-%e5%8d%a0%e4%bd%8d%e7%ac%a6%e5%af%bc%e8%87%b4%e5%90%8e%e7%ab%af%e8%b5%b7%e4%b8%8d%e6%9d%a5" class="header-anchor"&gt;&lt;/a&gt;坑 5：snail-job 占位符导致后端起不来&#10;&lt;/h3&gt;&lt;p&gt;容器反复重启，日志：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-18-1"&gt;&lt;a class="lnlinks" href="#hl-18-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-18-2"&gt;&lt;a class="lnlinks" href="#hl-18-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-18-3"&gt;&lt;a class="lnlinks" href="#hl-18-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-18-4"&gt;&lt;a class="lnlinks" href="#hl-18-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-18-5"&gt;&lt;a class="lnlinks" href="#hl-18-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Application run failed&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BeanCreationException: Error creating bean with name &amp;#39;feignConfiguration&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Caused by: org.springframework.util.PlaceholderResolutionException:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Could not resolve placeholder &amp;#39;SCHEDULE_PASSWORD&amp;#39; in value &amp;#34;${SCHEDULE_PASSWORD}&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;-- &amp;#34;${snail-job.server.api.password}&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;背景&lt;/strong&gt;：prod 配置里 &lt;code&gt;snail-job.enabled: true&lt;/code&gt; 写死，而 17888 端口被 bid 项目的 snailjob 占着，所以测试环境决定关掉调度——compose 里已经用 &lt;code&gt;SPRING_APPLICATION_JSON: '{&amp;quot;snail-job&amp;quot;:{&amp;quot;enabled&amp;quot;:false},...}'&lt;/code&gt; 关了。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;但坑在于&lt;/strong&gt;：即使 &lt;code&gt;enabled=false&lt;/code&gt;，snail-job 的 &lt;code&gt;feignConfiguration&lt;/code&gt; bean 创建时&lt;strong&gt;依然会解析 &lt;code&gt;${SCHEDULE_USERNAME/PASSWORD/TOKEN}&lt;/code&gt; 占位符&lt;/strong&gt;（&lt;code&gt;SCHEDULE_TOKEN&lt;/code&gt; 在 yml 里连默认值都没有）。占位符解析失败 = 启动直接崩。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;修复&lt;/strong&gt;：compose 里补三个永远不会被使用的占位值（反正调度已关闭）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-19-1"&gt;&lt;a class="lnlinks" href="#hl-19-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-2"&gt;&lt;a class="lnlinks" href="#hl-19-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-3"&gt;&lt;a class="lnlinks" href="#hl-19-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SCHEDULE_USERNAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;admin&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SCHEDULE_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unused-disabled&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SCHEDULE_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unused-disabled&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;重建容器：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-20-1"&gt;&lt;a class="lnlinks" href="#hl-20-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Started ContiNewAdminApplication in 17.987 seconds&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-21-1"&gt;&lt;a class="lnlinks" href="#hl-21-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-21-2"&gt;&lt;a class="lnlinks" href="#hl-21-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-21-3"&gt;&lt;a class="lnlinks" href="#hl-21-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s -o /dev/null -w &lt;span class="s1"&gt;&amp;#39;%{http_code}&amp;#39;&lt;/span&gt; http://localhost:18000/ &lt;span class="c1"&gt;# → 200&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; myapp-mysql mysql -uroot -p&amp;lt;密码&amp;gt; -e &lt;span class="s2"&gt;&amp;#34;SHOW TABLES;&amp;#34;&lt;/span&gt; continew_admin &lt;span class="p"&gt;|&lt;/span&gt; wc -l&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Liquibase 已自动建好全部表&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h2 id="六前端流水线"&gt;&lt;a href="#%e5%85%ad%e5%89%8d%e7%ab%af%e6%b5%81%e6%b0%b4%e7%ba%bf" class="header-anchor"&gt;&lt;/a&gt;六、前端流水线&#10;&lt;/h2&gt;&lt;p&gt;任务 &lt;code&gt;2_前端_myapp-admin-ui_测试&lt;/code&gt;，核心配置：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-22-1"&gt;&lt;a class="lnlinks" href="#hl-22-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-2"&gt;&lt;a class="lnlinks" href="#hl-22-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-3"&gt;&lt;a class="lnlinks" href="#hl-22-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-4"&gt;&lt;a class="lnlinks" href="#hl-22-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-5"&gt;&lt;a class="lnlinks" href="#hl-22-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-6"&gt;&lt;a class="lnlinks" href="#hl-22-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-7"&gt;&lt;a class="lnlinks" href="#hl-22-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-8"&gt;&lt;a class="lnlinks" href="#hl-22-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-9"&gt;&lt;a class="lnlinks" href="#hl-22-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-10"&gt;&lt;a class="lnlinks" href="#hl-22-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-11"&gt;&lt;a class="lnlinks" href="#hl-22-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-22-12"&gt;&lt;a class="lnlinks" href="#hl-22-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-groovy" data-lang="groovy"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nodejs&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;node22&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;BRANCH_NAME&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;defaultValue:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dev&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;BUILD_OUTPUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dist&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;REMOTE_DEPLOY_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/opt/dockerfile/myapp-frontend/images-pkg&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DEPLOY_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;10.10.10.44&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DEPLOY_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;root&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;GIT_CREDENTIALS_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;myapp-git&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// shell 环境变量优先级高于 .env 文件，覆盖 .env.production 里的上游演示地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;VITE_API_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://10.10.10.44:8085/api&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;VITE_API_WS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ws://10.10.10.44:8085/api&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;三个阶段：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;Checkout&lt;/strong&gt;：&lt;code&gt;https://git.example.com/myapp-safe/myapp-admin-ui.git&lt;/code&gt;，dev 分支&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Install &amp;amp; Build&lt;/strong&gt;：&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-23-1"&gt;&lt;a class="lnlinks" href="#hl-23-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-23-2"&gt;&lt;a class="lnlinks" href="#hl-23-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-23-3"&gt;&lt;a class="lnlinks" href="#hl-23-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-23-4"&gt;&lt;a class="lnlinks" href="#hl-23-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-23-5"&gt;&lt;a class="lnlinks" href="#hl-23-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm config &lt;span class="nb"&gt;set&lt;/span&gt; registry https://registry.npmmirror.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;command&lt;/span&gt; -v pnpm &lt;span class="o"&gt;||&lt;/span&gt; npm install -g pnpm@9&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pnpm config &lt;span class="nb"&gt;set&lt;/span&gt; registry https://registry.npmmirror.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pnpm install&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pnpm build &lt;span class="c1"&gt;# 读 .env.production，VITE_* 环境变量优先生效&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Deploy&lt;/strong&gt;：&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-24-1"&gt;&lt;a class="lnlinks" href="#hl-24-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-24-2"&gt;&lt;a class="lnlinks" href="#hl-24-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-24-3"&gt;&lt;a class="lnlinks" href="#hl-24-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh root@10.10.10.44 &lt;span class="s2"&gt;&amp;#34;cd /opt/dockerfile/myapp-frontend/images-pkg &amp;amp;&amp;amp; rm -rf dist &amp;amp;&amp;amp; mkdir -p dist&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;scp -r dist/. root@10.10.10.44:/opt/dockerfile/myapp-frontend/images-pkg/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh root@10.10.10.44 &lt;span class="s2"&gt;&amp;#34;cd /opt/dockerfile/myapp-frontend/images-pkg &amp;amp;&amp;amp; bash deploy-image-compse.sh&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;远程三件套：&lt;code&gt;deploy-image-compse.sh&lt;/code&gt;（入口：构建镜像 → compose 启动）→ &lt;code&gt;build_image.sh&lt;/code&gt;（删旧容器/镜像 → &lt;code&gt;docker build&lt;/code&gt;）→ &lt;code&gt;myapp-frontend.yml&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-25-1"&gt;&lt;a class="lnlinks" href="#hl-25-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-25-2"&gt;&lt;a class="lnlinks" href="#hl-25-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-25-3"&gt;&lt;a class="lnlinks" href="#hl-25-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-25-4"&gt;&lt;a class="lnlinks" href="#hl-25-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-25-5"&gt;&lt;a class="lnlinks" href="#hl-25-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-25-6"&gt;&lt;a class="lnlinks" href="#hl-25-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-25-7"&gt;&lt;a class="lnlinks" href="#hl-25-7"&gt;7&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;myapp-frontend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myapp-frontend&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myapp-frontend-server&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8085:80&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;前端 Dockerfile（&lt;strong&gt;最终版&lt;/strong&gt;，&lt;code&gt;chmod&lt;/code&gt; 那行是坑 6 的修复）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-26-1"&gt;&lt;a class="lnlinks" href="#hl-26-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-26-2"&gt;&lt;a class="lnlinks" href="#hl-26-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-26-3"&gt;&lt;a class="lnlinks" href="#hl-26-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-26-4"&gt;&lt;a class="lnlinks" href="#hl-26-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-26-5"&gt;&lt;a class="lnlinks" href="#hl-26-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-26-6"&gt;&lt;a class="lnlinks" href="#hl-26-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx:1.27.0&lt;/span&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; dist/ /usr/share/nginx/html/&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; nginx/default.conf /etc/nginx/conf.d/default.conf&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 构建产物可能带有 640/750 权限（CI umask），nginx 用户需要可读&lt;/span&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; chmod -R a+rX /usr/share/nginx/html&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;nginx &lt;code&gt;default.conf&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-27-1"&gt;&lt;a class="lnlinks" href="#hl-27-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-2"&gt;&lt;a class="lnlinks" href="#hl-27-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-3"&gt;&lt;a class="lnlinks" href="#hl-27-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-4"&gt;&lt;a class="lnlinks" href="#hl-27-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-5"&gt;&lt;a class="lnlinks" href="#hl-27-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-6"&gt;&lt;a class="lnlinks" href="#hl-27-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-7"&gt;&lt;a class="lnlinks" href="#hl-27-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-8"&gt;&lt;a class="lnlinks" href="#hl-27-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-9"&gt;&lt;a class="lnlinks" href="#hl-27-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-10"&gt;&lt;a class="lnlinks" href="#hl-27-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-11"&gt;&lt;a class="lnlinks" href="#hl-27-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-12"&gt;&lt;a class="lnlinks" href="#hl-27-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-13"&gt;&lt;a class="lnlinks" href="#hl-27-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-14"&gt;&lt;a class="lnlinks" href="#hl-27-14"&gt;14&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-15"&gt;&lt;a class="lnlinks" href="#hl-27-15"&gt;15&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-16"&gt;&lt;a class="lnlinks" href="#hl-27-16"&gt;16&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-17"&gt;&lt;a class="lnlinks" href="#hl-27-17"&gt;17&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-18"&gt;&lt;a class="lnlinks" href="#hl-27-18"&gt;18&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-19"&gt;&lt;a class="lnlinks" href="#hl-27-19"&gt;19&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-20"&gt;&lt;a class="lnlinks" href="#hl-27-20"&gt;20&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-21"&gt;&lt;a class="lnlinks" href="#hl-27-21"&gt;21&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-22"&gt;&lt;a class="lnlinks" href="#hl-27-22"&gt;22&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-23"&gt;&lt;a class="lnlinks" href="#hl-27-23"&gt;23&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-24"&gt;&lt;a class="lnlinks" href="#hl-27-24"&gt;24&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-25"&gt;&lt;a class="lnlinks" href="#hl-27-25"&gt;25&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-26"&gt;&lt;a class="lnlinks" href="#hl-27-26"&gt;26&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-27-27"&gt;&lt;a class="lnlinks" href="#hl-27-27"&gt;27&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;100m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 前端静态资源（history 路由回退到 index.html）&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri/&lt;/span&gt; &lt;span class="s"&gt;/index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# /api/ 代理到后端（172.17.0.1 为宿主机，后端端口 18000，代理时去掉 /api 前缀）&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/api/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://172.17.0.1:18000/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_ignore_client_abort&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# WebSocket 支持&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;Upgrade&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;两个设计点：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;172.17.0.1&lt;/code&gt; 是 docker0 网桥网关，即宿主机；后端用 host 网络跑在 18000，容器内通过这个地址访问。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;proxy_pass http://172.17.0.1:18000/;&lt;/code&gt; 末尾的 &lt;code&gt;/&lt;/code&gt; 会&lt;strong&gt;剥掉 &lt;code&gt;/api&lt;/code&gt; 前缀&lt;/strong&gt;：&lt;code&gt;/api/system/user&lt;/code&gt; → 后端收到 &lt;code&gt;/system/user&lt;/code&gt;。所以前端 &lt;code&gt;VITE_API_BASE_URL&lt;/code&gt; 统一带 &lt;code&gt;/api&lt;/code&gt;，同域访问，无跨域问题。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="坑-68085-打开直接-403-forbidden"&gt;&lt;a href="#%e5%9d%91-68085-%e6%89%93%e5%bc%80%e7%9b%b4%e6%8e%a5-403-forbidden" class="header-anchor"&gt;&lt;/a&gt;坑 6：8085 打开直接 403 Forbidden&#10;&lt;/h3&gt;&lt;p&gt;流水线全绿、容器在跑，浏览器打开 &lt;code&gt;http://10.10.10.44:8085/&lt;/code&gt; 却是 &lt;strong&gt;403 Forbidden&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;第一直觉是&amp;quot;dist 没传上去、目录为空&amp;quot;，上机检查抓到真凶——nginx 错误日志：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-28-1"&gt;&lt;a class="lnlinks" href="#hl-28-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;open() &amp;#34;/usr/share/nginx/html/index.html&amp;#34; failed (13: Permission denied)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;dist 文件权限是 &lt;code&gt;-rw-r-----&lt;/code&gt;（640）、目录 &lt;code&gt;drwxr-x---&lt;/code&gt;（750），属主 root；而 nginx 的 worker 进程用 &lt;code&gt;nginx&lt;/code&gt; 用户运行，&lt;strong&gt;读不了 root 的私有文件&lt;/strong&gt; → 403。&lt;/p&gt;&#10;&lt;p&gt;根源和 MySQL 那次是&lt;strong&gt;同一个 umask 坑&lt;/strong&gt;：Jenkins 容器 umask 027 → &lt;code&gt;pnpm build&lt;/code&gt; 产物 640/750 → scp 原样保留权限到服务器。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;修复做在 Dockerfile 里&lt;/strong&gt;（而不是手工 chmod），这样每次流水线部署都自动正确：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-29-1"&gt;&lt;a class="lnlinks" href="#hl-29-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; chmod -R a+rX /usr/share/nginx/html&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;code&gt;a+rX&lt;/code&gt; 大写 X：只对目录和已有执行位的文件加执行权限，不会把 js/css 误加执行位。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;重新跑前端流水线 → &lt;code&gt;curl http://localhost:8085/&lt;/code&gt; 返回 200，页面打开。&lt;/p&gt;&#10;&lt;h3 id="坑-7页面左上角-logo-不显示"&gt;&lt;a href="#%e5%9d%91-7%e9%a1%b5%e9%9d%a2%e5%b7%a6%e4%b8%8a%e8%a7%92-logo-%e4%b8%8d%e6%98%be%e7%a4%ba" class="header-anchor"&gt;&lt;/a&gt;坑 7：页面左上角 logo 不显示&#10;&lt;/h3&gt;&lt;p&gt;页面能打开了，但左上角 logo 裂开。排查链：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;code&gt;curl http://localhost:8085/logo.png&lt;/code&gt; → 200，&lt;strong&gt;文件本身存在&lt;/strong&gt;；&lt;/li&gt;&#10;&lt;li&gt;前端 &lt;code&gt;Logo.vue&lt;/code&gt; 的 logo 地址来自后端站点配置接口（&lt;code&gt;appStore.getLogo()&lt;/code&gt;），不是写死的；&lt;/li&gt;&#10;&lt;li&gt;查后端种子数据 &lt;code&gt;main_data.sql&lt;/code&gt;：&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-30-1"&gt;&lt;a class="lnlinks" href="#hl-30-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SITE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;系统LOGO&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SITE_LOGO&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/logo.svg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;strong&gt;默认值是 &lt;code&gt;/logo.svg&lt;/code&gt;，但项目 &lt;code&gt;public/&lt;/code&gt; 目录里只有 &lt;code&gt;logo.png&lt;/code&gt; 和 &lt;code&gt;favicon.ico&lt;/code&gt;&lt;/strong&gt;（二开时把 svg 换成了 png），浏览器请求 &lt;code&gt;/logo.svg&lt;/code&gt; → 404 → 裂开。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;&lt;strong&gt;修复&lt;/strong&gt;：改数据库里 &lt;code&gt;sys_option&lt;/code&gt; 表的当前值（改 &lt;code&gt;value&lt;/code&gt; 字段，种子只写 &lt;code&gt;default_value&lt;/code&gt;，重建表不覆盖）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-31-1"&gt;&lt;a class="lnlinks" href="#hl-31-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-31-2"&gt;&lt;a class="lnlinks" href="#hl-31-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; myapp-mysql mysql -uroot -p&amp;lt;密码&amp;gt; continew_admin &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e &lt;span class="s2"&gt;&amp;#34;UPDATE sys_option SET value=&amp;#39;/logo.png&amp;#39; WHERE code=&amp;#39;SITE_LOGO&amp;#39;;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;还有一层波折&lt;/strong&gt;：UPDATE 完、重启后端，接口返回的还是旧值——站点配置走了 &lt;strong&gt;JetCache 两级缓存&lt;/strong&gt;，重启只清了本地缓存，Redis（db 1）里还有。清缓存：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-32-1"&gt;&lt;a class="lnlinks" href="#hl-32-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-32-2"&gt;&lt;a class="lnlinks" href="#hl-32-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-32-3"&gt;&lt;a class="lnlinks" href="#hl-32-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; myapp-redis redis-cli -a &amp;lt;密码&amp;gt; -n &lt;span class="m"&gt;1&lt;/span&gt; keys &lt;span class="s2"&gt;&amp;#34;OPTION:*&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# OPTION:SITE / OPTION:TENANT / OPTION:LOGIN_CAPTCHA_ENABLED / ...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; myapp-redis redis-cli -a &amp;lt;密码&amp;gt; -n &lt;span class="m"&gt;1&lt;/span&gt; del OPTION:SITE ...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;接口验证返回 &lt;code&gt;&amp;quot;SITE_LOGO&amp;quot;,&amp;quot;value&amp;quot;:&amp;quot;/logo.png&amp;quot;&lt;/code&gt;，浏览器强刷（Cmd+Shift+R）后 logo 正常。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;顺带得到换 logo 的正规姿势：新 logo 放前端 &lt;code&gt;public/&lt;/code&gt; 重新构建；后台&amp;quot;系统管理 → 系统配置&amp;quot;里改 SITE_LOGO（界面改会自动失效缓存，不用手清 Redis）。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h3 id="坑-8右上角设置面板少了一大半"&gt;&lt;a href="#%e5%9d%91-8%e5%8f%b3%e4%b8%8a%e8%a7%92%e8%ae%be%e7%bd%ae%e9%9d%a2%e6%9d%bf%e5%b0%91%e4%ba%86%e4%b8%80%e5%a4%a7%e5%8d%8a" class="header-anchor"&gt;&lt;/a&gt;坑 8：右上角设置面板少了一大半&#10;&lt;/h3&gt;&lt;p&gt;最后一个隐蔽问题：点右上角设置图标，抽屉里&lt;strong&gt;只有&amp;quot;系统主题&amp;quot;和&amp;quot;色弱模式&amp;quot;&lt;/strong&gt;，而上游原版还有&amp;quot;系统布局 / 界面显示 / 哀悼模式 / 复制配置&amp;quot;等一整片。&lt;/p&gt;&#10;&lt;p&gt;第一怀疑是二开时删了功能——逐文件 diff 两个仓库，&lt;strong&gt;功能代码零改动&lt;/strong&gt;（差异只有 logo/favicon/应用名等替换）。真凶在源码里：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-33-1"&gt;&lt;a class="lnlinks" href="#hl-33-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-33-2"&gt;&lt;a class="lnlinks" href="#hl-33-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-33-3"&gt;&lt;a class="lnlinks" href="#hl-33-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-33-4"&gt;&lt;a class="lnlinks" href="#hl-33-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-33-5"&gt;&lt;a class="lnlinks" href="#hl-33-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vue" data-lang="vue"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="nx"&gt;SettingDrawer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vue&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settingOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_APP_SETTING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a-divider&lt;/span&gt; &lt;span class="nt"&gt;v-if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;settingOpen&amp;#34; orientation=&amp;#34;center&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;系统布局&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="nt"&gt;-divider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a-descriptions&lt;/span&gt; &lt;span class="nt"&gt;v-if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;settingOpen&amp;#34;&lt;/span&gt;&lt;span class="p"&gt; &lt;/span&gt;&lt;span class="na"&gt;...&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;界面显示&lt;/span&gt;&lt;span class="p"&gt;...&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a-descriptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;整个抽屉的板块都挂在 &lt;code&gt;v-if=&amp;quot;settingOpen&amp;quot;&lt;/code&gt; 上，而 &lt;code&gt;settingOpen&lt;/code&gt; 来自构建期环境变量 &lt;code&gt;VITE_APP_SETTING&lt;/code&gt;。仓库 &lt;code&gt;.env.production&lt;/code&gt; 里明明写的是 &lt;code&gt;true&lt;/code&gt;——但&lt;strong&gt;我在流水线的 environment 里加了一行 &lt;code&gt;VITE_APP_SETTING = 'false'&lt;/code&gt;&lt;/strong&gt;（当时误以为这是个演示开关），shell 环境变量优先级高于 .env 文件，构建产物里这些板块全被隐藏。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;修复&lt;/strong&gt;：删掉流水线任务里的那行变量（任务 → Configure → 删行 → Save），重新构建前端，浏览器强刷，完整面板回来。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;教训：&lt;strong&gt;Vite 项目里 shell 环境变量 &amp;gt; .env 文件&lt;/strong&gt;。流水线 environment 里写的每一个 &lt;code&gt;VITE_*&lt;/code&gt; 都是在覆盖仓库配置，加之前先想清楚。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h2 id="七上线验证清单"&gt;&lt;a href="#%e4%b8%83%e4%b8%8a%e7%ba%bf%e9%aa%8c%e8%af%81%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;七、上线验证清单&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;检查项&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;命令/方式&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;结果&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;jenkins2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;curl localhost:8082/login&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;200&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MySQL&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;SHOW DATABASES;&lt;/code&gt; 有 continew_admin&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Redis&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;set/get/del&lt;/code&gt; 读写探针&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;后端&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;curl localhost:18000/&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;200&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;后端表结构&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;SHOW TABLES;&lt;/code&gt;（Liquibase 自动建）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;前端&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;curl localhost:8085/&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;200&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;登录&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;浏览器 &lt;code&gt;http://10.10.10.44:8085&lt;/code&gt;，admin/123456&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;logo / 设置面板&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;浏览器强刷确认&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;旧 Jenkins（8080）及 bid 全部容器&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;docker ps&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;全程未受影响 ✅&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="八遗留事项上线后别忘"&gt;&lt;a href="#%e5%85%ab%e9%81%97%e7%95%99%e4%ba%8b%e9%a1%b9%e4%b8%8a%e7%ba%bf%e5%90%8e%e5%88%ab%e5%bf%98" class="header-anchor"&gt;&lt;/a&gt;八、遗留事项（上线后别忘）&#10;&lt;/h2&gt;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;凭据轮换&lt;/strong&gt;：搭建过程中 root 密码、数据库密码、部署私钥都经过多人/多窗口传递，环境跑通后统一轮换。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;FIELD_ENCRYPT_*&lt;/code&gt; 必须换&lt;/strong&gt;：测试环境复用了仓库 dev 示例密钥，正式上线前换成独立生成的 AES/RSA 密钥对。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;/home/redis6.bak.20260916&lt;/code&gt; 观察几天无异常后可删除。&lt;/li&gt;&#10;&lt;li&gt;snail-job 调度目前是关闭的；需要定时任务时，把项目的 schedule-server 单独容器化（注意 17888 与 bid 冲突，改端口映射）。&lt;/li&gt;&#10;&lt;li&gt;旧 Jenkins（8080）择机升级：&lt;code&gt;cd /home/compose &amp;amp;&amp;amp; docker-compose pull &amp;amp;&amp;amp; docker-compose up -d&lt;/code&gt;（先备份 jenkins_home）。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="九经验总结9-个坑一览"&gt;&lt;a href="#%e4%b9%9d%e7%bb%8f%e9%aa%8c%e6%80%bb%e7%bb%939-%e4%b8%aa%e5%9d%91%e4%b8%80%e8%a7%88" class="header-anchor"&gt;&lt;/a&gt;九、经验总结（9 个坑一览）&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;#&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;坑&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;一句话根因&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;一句话解法&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;mysqld 起不来 errno 13&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;服务器 umask 027，挂载目录 750 容器内用户进不去&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;删掉无用 conf.d 挂载 + &lt;code&gt;chmod 755&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;文件放错服务器&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;凭记忆操作没核对 IP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;完整回滚（文件/库/公钥）再重推&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ssh-agent 找不到凭据 ×3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;凭据 ID 变更 + 页面建凭据没保存成功&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;弃凭据，容器内放密钥 + ssh config&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;远程脚本 Permission denied&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;tar 解包覆盖掉 chmod +x&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;每次传包后补 chmod；或改用 bash 执行&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;SCHEDULE_PASSWORD 占位符崩溃&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;enabled=false 也会解析占位符&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;compose 补三个占位值&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;nginx 403&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;umask 027 → dist 640，nginx 用户读不了&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Dockerfile &lt;code&gt;chmod -R a+rX&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;logo 404&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;种子数据默认 /logo.svg，项目只有 png；还有 Redis 缓存&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;UPDATE sys_option + 清 OPTION:* 缓存&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;设置面板缺失&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;流水线 VITE_APP_SETTING=false 覆盖 .env&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;删环境变量重建&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;9&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;SSH 间歇被重置&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;高频连接触发云主机安全防护（HSS）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;合并命令减少连接次数，间隔重试&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;三条贯穿始终的主线经验：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;umask 027 是这台等保服务器最大的隐形规则&lt;/strong&gt;：所有&amp;quot;容器里读不到挂载目录/文件&amp;quot;的诡异问题，先查权限。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;每一处&amp;quot;覆盖&amp;quot;都要警惕&lt;/strong&gt;：tar 覆盖文件权限、shell 环境变量覆盖 .env、缓存覆盖数据库新值——改配置前先想清楚谁覆盖谁。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;CI 凭据不是只有页面一条路&lt;/strong&gt;：绑定挂载 + 密钥文件 + ssh config，比图形界面凭据更可控、可排错、可备份。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>