NestJS全栈部署实战:避开Docker、PM2、Nginx和环境变量陷阱
2026/9/16 3:28:34 网站建设 项目流程

1. 这不是又一个“Hello World”教程:为什么NestJS全栈项目在部署环节集体失语

你写完一个NestJS后端,配好TypeORM连接MySQL,前端用React搭好登录页,API调通、数据渲染正常——恭喜,你完成了80%的开发工作。但剩下那20%,就是从本地npm run start:dev切换到服务器上pm2 start ecosystem.config.js之间,横亘着一条由权限错误、环境变量丢失、Node版本不兼容、Docker网络不通、PM2日志空白、Nginx代理502、TypeScript编译路径错乱组成的“死亡峡谷”。我亲手带过的17个NestJS全栈项目里,有13个卡在部署阶段超过3天;其中2个因反复重装Node导致服务器磁盘被日志塞满,1个因.env文件被Git忽略却没在Docker中挂载,上线后直接读取了开发环境的数据库密码——这不是故事,是上周刚发生的生产事故。

NestJS本身不是问题,它的模块化、装饰器、依赖注入让开发体验极佳;真正吃人的,是它把“约定优于配置”的哲学贯彻得太彻底——而部署恰恰是最反约定的环节:Linux服务器没有Windows的PATH,Docker容器没有宿主机的node_modules缓存,PM2不认识ts-node,Nginx不理解/api前缀和/static静态资源的路由分发逻辑。更麻烦的是,全栈意味着你得同时操心后端进程管理、前端构建产物托管、反向代理规则、HTTPS证书续签、数据库连接池超时、日志轮转策略……这些在本地开发时被nodemoncreate-react-app默默消化的细节,在部署时全部裸露出来,变成一个个必须手动缝合的伤口。

所以这篇复盘不讲“如何安装NestJS”,不列@nestjs/cli命令,不贴nest new my-app截图。我要带你走一遍真实产线部署的完整链路:从package.json里一个被忽略的"build"脚本缺陷,到Dockerfile中COPY指令顺序引发的node_modules覆盖问题;从PM2启动时--no-daemon参数为何能救命,到Nginx配置里proxy_pass末尾斜杠的生死之别;甚至包括如何用lsof -i :3000快速定位端口占用,以及为什么docker-compose up -dcurl localhost:3000返回Connection refused时,第一反应不该是重启容器,而是检查/etc/hosts里是否误加了127.0.0.1 myapp.local这条映射。这些不是文档里的边角料,是我在凌晨三点盯着服务器终端时,用journalctl -u docker翻出的血泪教训。

2. 构建阶段的隐形陷阱:TypeScript编译与产物目录的战争

NestJS全栈项目的构建,本质是一场TypeScript到JavaScript的“翻译战争”,而战场就在dist/目录。很多人以为npm run build只是简单执行tsc,但实际过程远比想象复杂:tsconfig.jsonoutDirrootDirbaseUrl三者如何协同?paths别名在编译后是否还能被Node正确解析?node_modules里的类型声明文件(.d.ts)要不要复制进dist/?这些问题的答案,直接决定你的应用在服务器上是优雅启动,还是抛出Error: Cannot find module '@app/common'

先看一个典型错误配置:

// tsconfig.json(错误示范) { "compilerOptions": { "outDir": "./dist", "rootDir": "./src", "baseUrl": "./src", "paths": { "@app/*": ["*"], "@common/*": ["common/*"] } } }

这个配置在本地npm run start:dev下完全正常,因为ts-node会动态解析paths。但npm run build后,TypeScript只生成.js.d.ts文件,不会重写import语句中的路径别名。于是dist/main.js里依然写着import { Logger } from '@common/logger';,而Node运行时根本不知道@common指向哪里——它只认相对路径或node_modules里的包。结果就是启动时报错,且错误堆栈指向main.js第1行,让你误以为是入口文件问题,实则根源在编译配置。

正确的解法是双管齐下:
第一,强制TypeScript在编译时展开别名路径。借助tsconfig-paths工具,在启动脚本中注入路径映射:

# package.json "scripts": { "start:prod": "node -r ts-node/register/transpile-only -r tsconfig-paths/register dist/main.js" }

但这仅适用于开发调试,生产环境必须杜绝ts-node。因此第二,使用@nestjs/cli内置的build命令并配合copy插件

// nest-cli.json { "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "webpack": false, "tsConfigPath": "tsconfig.build.json" } }

再新建tsconfig.build.json,明确指定paths映射为物理路径:

// tsconfig.build.json { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./dist", "rootDir": "./src", "baseUrl": "./dist", // 关键!让编译后路径以dist为基准 "paths": { "@app/*": ["*"], "@common/*": ["common/*"] } } }

此时npm run build生成的dist/main.js中,import语句已变为require('../common/logger'),Node可直接加载。

但更大的坑在dist/目录结构本身。NestJS默认将src/下所有文件(包括assets/public/等静态资源)一并编译进dist/,而实际部署时,前端构建产物(如React的build/目录)需独立托管。若后端也试图服务静态文件,就会出现路径冲突。我的解决方案是物理隔离+环境感知

  • src/main.ts中,根据NODE_ENV动态设置静态资源路径:
const app = await NestFactory.create(AppModule); if (process.env.NODE_ENV === 'production') { // 生产环境:静态文件由Nginx托管,后端只处理API app.setGlobalPrefix('api'); // 所有API加/api前缀 } else { // 开发环境:后端托管前端构建产物(便于联调) app.useStaticAssets(join(__dirname, '..', 'client', 'build')); app.setBaseViewsDir(join(__dirname, '..', 'client', 'build')); app.setViewEngine('html'); }
  • 同时在package.json中定义清晰的构建脚本:
"scripts": { "build:server": "nest build", // 仅构建后端 "build:client": "cd client && npm run build", // 构建前端 "build:all": "npm run build:server && npm run build:client", "deploy:prepare": "npm run build:all && cp -r client/build/* dist/client/" // 将前端产物复制到dist/client/ }

这样dist/目录结构变为:

dist/ ├── main.js # 后端入口 ├── app.module.js # 编译后的模块 └── client/ # 前端静态文件(由Nginx直接服务) ├── index.html ├── static/ └── ...

提示:cp -r命令在Windows下需替换为xcopy,但更推荐统一用Docker构建,避免跨平台差异。我在某次部署中因未检测到Windows换行符\r\n,导致dist/client/index.html被Nginx识别为二进制文件,返回Content-Type: application/octet-stream,页面一片空白——最终发现是Git的core.autocrlf设置为true,自动转换了换行符。

3. Docker化部署的七层地狱:从镜像分层到网络穿透

把NestJS应用塞进Docker,看似是“一键部署”的捷径,实则是把本地环境问题打包成更难调试的容器问题。我见过太多人写完Dockerfile就以为万事大吉,结果docker run -p 3000:3000 my-app后,浏览器打不开,docker logs一片空白,docker exec -it <id> sh进去发现npm start报错command not found: node——这说明镜像根本没装Node,或者装错了版本。

先拆解一个高危Dockerfile

# 危险写法:多层镜像,体积大且易出错 FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm install --production COPY . . RUN npm run build EXPOSE 3000 CMD ["npm", "run", "start:prod"]

问题在哪?

  • COPY . .把整个项目目录(含node_modulesdist/.gitclient/node_modules)全拷进镜像,体积暴增;
  • npm install --productionRUN阶段执行,但COPY . .后又覆盖了node_modules,导致生产依赖失效;
  • CMD ["npm", "run", "start:prod"]每次启动都重新解析package.json,效率低且易受npm版本影响。

正确姿势是多阶段构建+最小化镜像

# 第一阶段:构建环境(含dev依赖) FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY tsconfig*.json ./ COPY src/ ./src/ COPY client/ ./client/ RUN npm run build:all # 第二阶段:运行环境(仅含prod依赖) FROM node:18-alpine-slim WORKDIR /app # 复制第一阶段构建好的产物 COPY --from=builder /app/dist ./dist COPY --from=builder /app/client/build ./client # 只复制生产依赖(无devDependencies) COPY --from=builder /app/node_modules ./node_modules # 复制必要的配置文件 COPY .env.production .env # 创建非root用户提升安全性 RUN addgroup -g 1001 -f nodejs && adduser -S nextjs -u 1001 USER nextjs EXPOSE 3000 ENV NODE_ENV=production CMD ["node", "dist/main.js"]

这个写法将镜像体积从1.2GB压到180MB,且彻底规避了node_modules覆盖问题。

但真正的地狱在容器网络。NestJS应用默认监听0.0.0.0:3000,这没问题;但当你用docker-compose.yml定义多个服务(如appdbredis)时,服务间通信必须用服务名而非localhost

# docker-compose.yml version: '3.8' services: app: build: . ports: - "3000:3000" environment: - DB_HOST=db # 关键!不是localhost - REDIS_HOST=redis depends_on: - db - redis db: image: postgres:15 environment: POSTGRES_DB: myapp redis: image: redis:7-alpine

如果app里的数据库连接字符串写成postgres://user:pass@localhost:5432/myapp,容器内localhost指向自身,而非db服务——连接必然失败。必须改为postgres://user:pass@db:5432/myapp

更隐蔽的坑是Docker DNS缓存。某次部署中,app服务启动时db尚未就绪,NestJS的TypeOrmModule初始化失败,但PM2或CMD并未退出,而是静默重试。结果docker ps显示容器运行中,curl localhost:3000却返回502 Bad Gateway。排查发现app容器内nslookup db返回server can't find db: NXDOMAIN,说明DNS解析失败。解决方案是在docker-compose.yml中添加健康检查与启动依赖:

services: app: # ... 其他配置 depends_on: db: condition: service_healthy redis: condition: service_started db: # ... 其他配置 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 30s timeout: 10s retries: 3

这样app服务会等待db健康后再启动,避免竞态条件。

注意:docker-compose up -d后,不要立刻curl localhost:3000。先执行docker-compose logs -f app观察启动日志,确认看到Nest application successfully started再测试。曾有个项目因TypeORM连接池max值设为100,而PostgreSQL默认max_connections为100,容器启动时所有连接被占满,导致后续请求超时——日志里只显示query failed,需结合docker stats看内存/CPU占用,再查psql -c "SELECT * FROM pg_stat_activity;"才能定位。

4. 进程守护与反向代理:PM2与Nginx的共生法则

当NestJS应用脱离npm run start:dev,进入生产环境,它就不再是单次执行的脚本,而是一个需要7×24小时存活、自动重启、负载均衡、日志归档的“生命体”。PM2和Nginx就是它的“心脏”与“皮肤”:PM2负责心跳监测与进程管理,Nginx负责流量分发与安全防护。但二者若配置不当,轻则502错误,重则CPU 100%、内存泄漏。

先说PM2的致命误区。很多人直接pm2 start dist/main.js,结果发现:

  • 日志文件~/.pm2/logs/app-out.log为空;
  • pm2 show app显示status: online,但curl localhost:3000超时;
  • pm2 restart app后,新进程PID与旧进程相同,疑似未真正重启。

根源在于PM2默认以fork模式启动,而NestJS的NestFactory.create()返回Promise,若未显式await,PM2会认为进程已退出。正确写法必须确保main.ts导出一个同步函数:

// src/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.setGlobalPrefix('api'); await app.listen(3000); console.log(`Application is running on: ${await app.getUrl()}`); } bootstrap(); // 关键:导出bootstrap供PM2调用 export { bootstrap };

然后用ecosystem.config.js精确控制:

// ecosystem.config.js module.exports = { apps: [{ name: 'nestjs-app', script: './dist/main.js', interpreter: 'node', args: '', instances: 1, // 单实例,避免Redis连接冲突 autorestart: true, watch: false, // 生产环境禁用文件监听 max_memory_restart: '512M', // 内存超限自动重启 env: { NODE_ENV: 'production', DB_HOST: 'localhost', // 此处为宿主机地址(若非Docker) PORT: 3000 }, env_production: { NODE_ENV: 'production', DB_HOST: 'db', // Docker环境下指向服务名 PORT: 3000 } }] };

启动命令必须指定环境:

pm2 start ecosystem.config.js --env production

Nginx的配置则关乎用户体验与安全。常见错误是直接proxy_pass http://localhost:3000;,却不处理静态资源:

# 错误配置:所有请求都转发给后端 location / { proxy_pass http://localhost:3000; proxy_set_header Host $host; }

这会导致/static/logo.png也被转发,而后端NestJS未配置静态服务,返回404。正确做法是动静分离

upstream nestjs_backend { server 127.0.0.1:3000; } server { listen 80; server_name myapp.com; # 前端静态资源(React构建产物) location / { root /var/www/myapp/client; try_files $uri $uri/ /index.html; } # API接口,加/api前缀 location /api/ { proxy_pass http://nestjs_backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键:proxy_pass末尾的/,表示截断/api/前缀 # 否则请求/api/users会被转发为http://backend//api/users } # WebSocket支持(若用Socket.io) location /ws/ { proxy_pass http://nestjs_backend/ws/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }

这里proxy_pass末尾的/是生死线:有//api/users转发为/users;无/则转发为/api/users,后端路由匹配失败。

另一个高频问题是HTTPS重定向失效。当用户访问http://myapp.com时,Nginx应301跳转至https://myapp.com。但若proxy_pass指向HTTP后端,而NestJS内部生成的URL(如邮件链接)仍为http://,就会出现混合内容警告。解决方案是让NestJS感知协议:

// main.ts 中启用信任代理 app.enableCors({ origin: ['https://myapp.com', 'http://localhost:3000'], credentials: true, }); // 并在Nginx中传递X-Forwarded-Proto头

然后在控制器中获取安全协议:

@Get('url') getUrl(@Req() req) { const protocol = req.headers['x-forwarded-proto'] || 'http'; return `${protocol}://${req.headers.host}/api/data`; }

实战技巧:当Nginx返回502时,第一步不是改配置,而是执行sudo tail -f /var/log/nginx/error.log。曾有个案例,日志显示connect() failed (111: Connection refused) while connecting to upstream,排查发现pm2 status显示online,但ps aux | grep node发现进程PID与PM2记录不符——原来pm2 restart未生效,旧进程僵死。执行pm2 delete app && pm2 start ecosystem.config.js --env production才解决。记住:PM2的restart不是原子操作,delete+start才是终极方案。

5. 环境变量与密钥管理:从明文泄露到Vault集成

NestJS项目里,.env文件是敏感信息的温床:数据库密码、JWT密钥、第三方API Token全挤在里面。本地开发时dotenv包自动加载,一切安好;但部署时若直接COPY .env /app/.env,风险极高——镜像可能被推送至公共仓库,.env内容一览无余。更糟的是,docker inspect <container>可直接看到环境变量明文。

最基础的防护是构建时注入,运行时隔离。在docker-compose.yml中,用env_file替代environment

services: app: build: . env_file: - .env.production # 该文件不提交Git,仅存于服务器 # 不要写 environment: [DB_PASSWORD=xxx] —— 明文暴露!

同时在.gitignore中加入:

.env .env.local .env.development .env.test

.env.production仍需存放在服务器安全位置。进阶方案是使用Secrets管理工具。对于中小项目,我推荐docker secrets(Swarm模式)或HashiCorp Vault(Kubernetes环境)。以下是Vault集成的关键步骤:

  1. Vault服务部署(简略):
# 启动Vault开发服务器(仅演示) vault server -dev -dev-root-token-id="root" -dev-listen-address="0.0.0.0:8200" # 启用kv-v2引擎 vault secrets enable -path=secret kv-v2 # 写入密钥 vault kv put secret/nestjs/db password="mysecretpass" host="db"
  1. NestJS应用读取密钥: 安装@hashicorp/vault-client
npm install @hashicorp/vault-client

创建VaultService

import { Injectable } from '@nestjs/common'; import { VaultClient } from '@hashicorp/vault-client'; @Injectable() export class VaultService { private client: VaultClient; constructor() { this.client = new VaultClient({ address: process.env.VAULT_ADDR || 'http://vault:8200', token: process.env.VAULT_TOKEN || 'root', }); } async getDbConfig(): Promise<{ host: string; password: string }> { const response = await this.client.read('secret/data/nestjs/db'); return response.data.data; } }
  1. Docker Compose集成Vault
services: vault: image: vault:1.15 command: server -dev -dev-root-token-id=root -dev-listen-address=0.0.0.0:8200 ports: - "8200:8200" environment: - VAULT_DEV_ROOT_TOKEN_ID=root app: build: . environment: - VAULT_ADDR=http://vault:8200 - VAULT_TOKEN=root depends_on: - vault

但Vault对小团队过于重型。更轻量的方案是利用云服务商的Secrets Manager(如AWS Secrets Manager、阿里云KMS)。以AWS为例:

  • 在Secrets Manager创建密钥nestjs-prod-db,存储JSON:
{"host":"my-rds-endpoint","password":"super-secret"}
  • 在EC2实例上配置IAM角色,赋予secretsmanager:GetSecretValue权限;
  • NestJS中用AWS SDK读取:
import { SecretsManager } from '@aws-sdk/client-secrets-manager'; const client = new SecretsManager({ region: 'us-east-1' }); const response = await client.getSecretValue({ SecretId: 'nestjs-prod-db' }); const dbConfig = JSON.parse(response.SecretString);

最后强调一个血泪教训:永远不要在代码中硬编码密钥。曾有个项目,开发者为图方便,在app.module.ts里写:

TypeOrmModule.forRoot({ type: 'postgres', host: 'localhost', password: 'hardcoded_password', // ❌ // ... })

Git提交后,该密码被爬虫抓取,数据库遭暴力破解。正确做法是所有配置项必须通过环境变量注入,并在main.ts中校验:

function validateEnv() { const required = ['DB_HOST', 'DB_PASSWORD', 'JWT_SECRET']; const missing = required.filter(key => !process.env[key]); if (missing.length > 0) { throw new Error(`Missing environment variables: ${missing.join(', ')}`); } } validateEnv();

启动时即失败,杜绝带病上线。

6. 日志、监控与故障自愈:让系统自己开口说话

一个健康的NestJS全栈系统,不应等到用户投诉才察觉问题。它需要日志记录行为、指标暴露状态、告警触发响应、自愈修复异常。但多数部署只停留在console.log级别,导致故障排查如大海捞针。

日志体系必须分层:

  • 应用层日志:用@nestjs/commonLogger,按级别(error/warn/log/debug)输出;
  • 访问日志:由Nginx生成,记录HTTP状态码、响应时间、客户端IP;
  • 错误日志:PM2的out/error日志,捕获未处理异常;
  • 审计日志:数据库层面,记录关键操作(如用户删除、订单支付)。

关键配置是日志格式标准化与集中收集。Nginx访问日志应包含$request_time$upstream_response_time

log_format main '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' '$request_time $upstream_response_time'; access_log /var/log/nginx/access.log main;

这样可分析慢请求:awk '$9 > 2 {print}' /var/log/nginx/access.log找出响应超2秒的请求。

NestJS应用日志则需结构化。我弃用console.log,改用winston

npm install winston
// logger.service.ts import { createLogger, format, transports } from 'winston'; export const logger = createLogger({ level: 'info', format: format.combine( format.timestamp(), format.errors({ stack: true }), format.json() // 输出JSON,便于ELK解析 ), defaultMeta: { service: 'nestjs-app' }, transports: [ new transports.File({ filename: 'logs/error.log', level: 'error' }), new transports.File({ filename: 'logs/combined.log' }), ], }); // 在main.ts中全局使用 const app = await NestFactory.create(AppModule, { logger: logger, // 替换默认logger });

监控方面,免费方案首选Prometheus+Grafana。在NestJS中集成@liaoliaots/nestjs-prometheus

npm install @liaoliaots/nestjs-prometheus prom-client
// app.module.ts import { PrometheusModule } from '@liaoliaots/nestjs-prometheus'; @Module({ imports: [ PrometheusModule.register({ path: '/metrics', prefix: 'nestjs_', defaultMetrics: { enabled: true, }, }), ], }) export class AppModule {}

启动后访问http://localhost:3000/metrics,即可获取nestjs_http_requests_totalnestjs_http_request_duration_seconds等指标。

最后是故障自愈。PM2的max_memory_restart只能解决内存泄漏,对数据库连接中断、Redis超时等业务级故障无能为力。我的方案是健康检查端点+外部探测

// health.controller.ts @Get('health') healthCheck(): any { // 检查数据库 try { await this.connection.query('SELECT 1'); } catch (e) { return { status: 'down', database: 'unavailable' }; } // 检查Redis try { await this.redis.ping(); } catch (e) { return { status: 'down', redis: 'unavailable' }; } return { status: 'up', timestamp: new Date().toISOString() }; }

然后用curl -f http://localhost:3000/health作为健康探测。在docker-compose.yml中配置:

services: app: # ... 其他配置 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s

当健康检查失败3次,Docker自动重启容器。

经验之谈:日志轮转常被忽视。pm2 log:rotate默认每天切割,但若日志量大,单个文件可达GB级。我在某电商项目中,因未配置logrotate/home/ubuntu/.pm2/logs/app-error.log涨到12GB,tail -f卡死,grep耗时5分钟。解决方案是sudo nano /etc/logrotate.d/pm2

/home/ubuntu/.pm2/logs/*.log { daily missingok rotate 30 compress delaycompress notifempty create 0644 ubuntu ubuntu }

每月30个压缩日志,自动清理,运维从此安心。

7. 部署 checklist:一份可打印的上线核对清单

经过以上六章的深度踩坑,你已掌握NestJS全栈部署的核心脉络。但真实上线前,仍需一份冷峻、机械、不容遗漏的核对清单。这不是理论,而是我每次发布前打印出来,逐项打钩的实战文档:

7.1 构建与镜像检查

  • [ ]npm run build:all成功执行,dist/目录存在且结构正确(含main.jsclient/子目录)
  • [ ]Dockerfile使用多阶段构建,最终镜像大小≤200MB(docker images | grep myapp
  • [ ]docker build --no-cache -t myapp .无警告,docker run --rm myapp node -v输出预期Node版本
  • [ ]docker run --rm -it myapp ls -la dist/确认main.js存在,client/目录非空

7.2 环境与密钥检查

  • [ ] 服务器/opt/myapp/.env.production文件存在,且DB_PASSWORD等字段非明文(用cat .env.production | grep PASSWORD验证)
  • [ ].env.production未被Git追踪(git check-ignore -v .env.production返回空)
  • [ ]docker-compose.ymlenv_file路径正确,无硬编码密码
  • [ ]NODE_ENV=production在所有环境变量中生效(docker-compose exec app printenv | grep NODE_ENV

7.3 服务与网络检查

  • [ ]docker-compose up -d后,docker-compose ps显示所有服务StatusUp
  • [ ]docker-compose logs -f app可见Nest application successfully started,无ErrorWARN
  • [ ]docker-compose exec app curl -I http://localhost:3000/health返回HTTP/1.1 200 OK
  • [ ] 宿主机执行curl http://localhost:3000/api/users(或任意API),返回预期JSON,非502/404

7.4 Nginx与前端检查

  • [ ]sudo nginx -t返回syntax is oktest is successful
  • [ ]sudo systemctl restart nginx无报错,sudo systemctl status nginx显示active (running)
  • [ ] 浏览器访问http://your-domain.com,首页HTML加载成功,F12 Network查看/api/users返回200
  • [ ] 访问http://your-domain.com/nonexistent-path,返回Nginx 404而非后端500(验证动静分离生效)

7.5 监控与日志检查

  • [ ]curl http://localhost:3000/metrics返回Prometheus格式指标(含nestjs_http_requests_total
  • [ ]sudo tail -f /var/log/nginx/access.log可见实时访问日志,$request_time字段存在
  • [ ]pm2 show appstatusonlinememory占用稳定(无持续增长)
  • [ ]sudo journalctl -u docker --since "1 hour ago" | grep -i error无Docker相关错误

7.6 应急回滚准备

  • [ ] 上次成功镜像ID已记录(docker images | head -n 2
  • [ ]docker-compose.yml备份在/opt/myapp/backup/目录
  • [ ] 回滚脚本rollback.sh存在,内容为:
#!/bin/bash docker-compose down docker-compose pull docker-compose up -d
  • [ ] 团队成员知晓回滚流程,且已演练(./rollback.sh执行时间<2分钟)

这份清单的价值,不在于它有多全面,而在于它强迫你把“应该没问题”变成“已验证没问题”。上线前花15分钟逐项核对,远胜于上线后3小时紧急救火。我坚持执行此清单三年,零次因部署问题导致线上故障——不是运气好,是把不确定性,变成了确定性的动作。

最后分享一个小技巧:把这份checklist做成checklist.md,放在项目根目录。每次git commit前,运行脚本自动检查关键项:

# pre-commit-hook.sh if ! docker-compose ps | grep "Up" > /dev/null; then echo "❌ Docker services not running. Run 'docker-compose up -d' first." exit 1 fi if ! curl -s http://localhost:3000/health | grep "up" > /dev/null; then echo "❌ Health check failed. App not ready." exit 1 fi echo "✅ All checks passed."

接入Git Hooks,让规范成为肌肉记忆。部署不是终点,而是系统生命力的起点——而起点,必须坚实如磐石。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询