5. 从代码到云端的部署实战

这是知识库中最核心的章节。覆盖从 SSH 连接到服务器、代码传输、Docker 构建与编排、一键启动、数据库运维到增量更新的完整流程。每条命令都附带原理说明,让你不仅会做,还懂为什么。


5.1 SSH:安全接管远程服务器

第一步:密码登录(首次连接)

# 在 Windows PowerShell 中执行
ssh root@211.159.xxx.xxx

原理说明

第二步:从密码到密钥(为什么密码登录危险?)

互联网上每天有大量机器人程序自动扫描服务器 22 端口,尝试各种密码组合入侵。密钥对相当于给服务器换了一把"指纹锁":

# 在 Windows PowerShell 中生成密钥对
ssh-keygen -t rsa -b 4096

原理说明

第三步:配置 SSH Config 实现秒连

# 在 C:\Users\你的用户名\.ssh\ 目录下创建 config 文件(无后缀)
# ~/.ssh/config
Host tao
    HostName 211.159.xxx.xxx
    User root
    IdentityFile ~/.ssh/id_rsa
    ServerAliveInterval 60
    ServerAliveCountMax 10

配置完成后,登录只需一行:

ssh tao

原理说明


5.2 代码传输:把代码送上服务器

方案 A:Git 克隆(标准,推荐)

# 在服务器上执行
mkdir -p ~/ing && cd ~/ing
git clone https://github.com/你的用户名/项目名.git
cd 项目名

更新代码时

# 本地提交
git add .
git commit -m "feat: 描述你的更新"
git push origin main

# 服务器拉取
ssh tao
cd ~/ing/项目名
git pull origin main

原理说明

方案 B:使用加速镜像(网络波动时)

# 当 GitHub 连接超时时使用代理
git clone https://ghproxy.net/https://github.com/Ing-la/openrise.git

原理说明

方案 C:SCP "空投"传输(终极方案)

当服务器彻底无法连接 GitHub 时,直接从本地推送代码:

# 在 Windows PowerShell 中执行

# 1. 本地打包(排除不需要的重型文件夹)
tar --exclude="node_modules" --exclude=".next" --exclude=".git" --exclude=".agents" -czvf openrise.tar.gz .

# 2. 使用 SSH 别名上传到服务器
scp openrise.tar.gz tao:~/ing/

# 3. SSH 登录服务器解压
ssh tao
cd ~/ing
mkdir -p openrise
tar -xzvf openrise.tar.gz -C openrise/

原理说明


5.3 环境变量配置

两份配置文件的用途

文件 用途 生效场景
根目录 .env Docker Compose 变量源 docker-compose up 时注入到容器
frontend/.env Next.js 本地变量 npm run dev 本地开发时

核心规则

Docker 部署:只需根目录 .env,docker-compose 自动读取注入到容器
本地开发:需要 frontend/.env,Next.js 直接读取

根目录 .env 配置(Docker 用)

# 从模板复制
cp .env.example .env

# 编辑配置
nano .env
# 数据库
POSTGRES_USER=openrise
POSTGRES_PASSWORD=你的强密码
POSTGRES_DB=openrise

# 认证
NEXTAUTH_SECRET=your-secret-here          # 用 openssl rand -base64 32 生成
NEXTAUTH_URL=http://localhost              # 按阶段修改为域名

# 邮件
RESEND_API_KEY=re_xxxxxxxxxxxx
RESEND_FROM=onboarding@resend.dev          # 开发环境;正式上线用 verified@你的域名

# Turnstile(人机验证)
NEXT_PUBLIC_TURNSTILE_SITE_KEY=1x...
TURNSTILE_SECRET_KEY=0x...
# 生成 NEXTAUTH_SECRET
openssl rand -base64 32

生产环境至少修改

变量 说明
POSTGRES_PASSWORD 设置强密码
NEXTAUTH_SECRET openssl rand -base64 32 生成
NEXTAUTH_URL 改为 https://你的域名
RESEND_API_KEY 正式 API Key

关键规则


5.4 Docker 构建与编排深度解析

核心前提:启动 Docker 引擎

# Windows 环境:双击打开 Docker Desktop
# 检查状态:右下角小鲸鱼图标变绿("Docker Desktop is running")

# 服务器环境:检查 Docker 运行状态
sudo systemctl status docker

原理说明

docker-compose up -d --build 的连环动作

这是部署中最核心的一行命令。按下回车后,后台经历了一个精密的流水线:

1. 解析蓝图     → 读取 docker-compose.yml
2. 执行构建     → 找到有 build 指令的服务,构建镜像
3. 拉取镜像     → 下载依赖的官方镜像(PostgreSQL、Nginx)
4. 建立网络     → 创建隔离网络 app-network
5. 顺序启动     → DB → Frontend(等待DB就绪)→ Nginx(最后启动)
6. 后台运行     → -d 参数让所有容器进入守护进程
# 一键启动所有服务
docker compose up -d --build

原理说明

什么时候需要 --build

开发阶段 → 每次都加 --build

原理说明

场景 命令 原因
改完代码 up -d --build 强制封装新代码
仅重启服务 up -d(不加 --build 代码没变,无需构建
下班/释放资源 down 干净利落,不留残留进程
仅重启数据库 restart db 不动代码,只重启动力系统

docker-compose down 的拆除流水线

docker compose down

原理说明

这不是简单的"关闭程序",而是有顺序的清除:

  1. SIGTERM:礼貌通知容器"请保存工作准备退出"
  2. SIGKILL:10 秒后没反应,强制切断电源
  3. 移除容器:这是 stopdown 的本质区别——down 把容器实体彻底抹除
  4. 拆除网络:销毁隔离通信通道
  5. 保留卷数据:除非加 -v 参数,否则 down 绝不删除数据,数据库资产稳如泰山

Dockerfile 多阶段构建原理

# frontend/Dockerfile

# 第一阶段:构建室(Builder)—— 有重型工具
FROM node:20-alpine AS builder
WORKDIR /app
# 【关键优化】先复制 package.json,利用缓存
COPY package.json ./
RUN npm install        # 这一步最耗时,但依赖不常变
COPY . .               # 后复制源码(经常变)
RUN npm run build      # 编译

# 第二阶段:运行室(Runner)—— 极简干净
FROM node:20-alpine AS runner
WORKDIR /app
# 【精髓】只从 builder 搬走成品,1GB 垃圾全部扔掉
COPY --from=builder /app/.next/standalone ./
USER nextjs            # 非 root 运行,安全第一
CMD ["node", "server.js"]

原理说明

为什么需要两个阶段?

分层缓存优化

COPY package.json ./     # ① 先复制依赖清单
RUN npm install           # ② 安装依赖(最耗时)
COPY . .                  # ③ 后复制源码(经常变化)

为什么需要 .dockerignore

# .dockerignore — 防止把本地垃圾塞进镜像
node_modules
.next
.git
*.md

原理说明


5.5 一键启动与验证

首次部署完整流程

# 在服务器上依次执行

# 1. 进入项目目录
cd ~/ing/openrise

# 2. 配置环境变量(从模板复制并编辑)
cp .env.example .env
nano .env
# ➜ 修改数据库密码、NEXTAUTH_SECRET、NEXTAUTH_URL 等

# 3. 一键启动(首次会下载镜像,约 3-5 分钟)
docker compose up -d --build

# 4. 检查容器状态(所有服务应为 Up)
docker compose ps

# 5. 查看启动日志(确认无错误)
docker compose logs -f frontend

验证清单

操作 预期结果
curl -I http://localhost 返回 200 OK
浏览器访问 http://服务器IP 打开网页
http://服务器IP/api/health 返回 {"status":"ok"}

常用命令速查

# 查看所有容器状态
docker compose ps

# 查看所有服务日志(实时滚动)
docker compose logs -f

# 查看特定服务日志
docker compose logs -f frontend

# 停止并移除容器(保留数据)
docker compose down

# 完全清理(删除数据卷)
docker compose down -v          # ⚠️ 会删除数据库数据!

# 仅停止(不删除容器)
docker compose stop

# 重新构建(无缓存)
docker compose build frontend --no-cache

5.6 生产环境配置修正与"名实同步"

在服务器上修改配置

# 用 vi 修改 .env
vi ~/ing/openrise/.env

vi 基本操作

按 i     → 进入编辑模式(底部显示 -- INSERT --)
编辑完成 → 按 Esc 退出编辑
:wq      → 保存并退出
:q!      → 不保存退出

修改配置后必须重启容器

核心原理(非常重要)

# ✅ 正确做法:修改 .env 后重启容器
docker compose up -d

# ❌ 错误认知:以为 restart 会重新读取 .env
docker compose restart          # 有时不会重新读取外部 .env

up -d 为什么不需要先 down

# ✅ 日常更新的标准命令
docker compose up -d --build

原理说明

直接 up 而不先 down,Docker 会执行**"边跑边换"**策略:

  1. 构建新镜像——此时旧容器依然正常运行,用户还能访问
  2. 计算变更——发现镜像有更新,标记受影响容器
  3. 瞬间替换——停止旧容器,启动新容器(通常 1-3 秒)

如果先 downup,会导致明显的服务中断。除非你要改网络架构或清理数据卷,否则日常更新直接用 up -d --build

命令 操作流程 适用场景 风险
up -d --build 边跑边换 日常更新 极低
down 先停止再拆除 深度维护(改网络、清数据) 高:服务完全下线
stop 只停止不删除 临时维护,省资源 中:再次启动快但文件不更新

5.7 数据库运维

开启 Prisma Studio 可视化管理

# 服务器上(通过 Docker 容器执行)
docker compose exec frontend npx prisma studio

原理说明

核心运维指令集

# 查看数据库日志(排查连接错误)
docker compose logs -f db

# 直接进入 SQL 命令行
docker compose exec db psql -U openrise -d openrise

# 数据备份(拍快照)
docker compose exec db pg_dump -U openrise > backup.sql

# 本地开发:同步数据库结构
npx prisma db push

# ⚠️ 慎用:重置数据库(清空所有表)
npx prisma migrate reset

数据库迁移自动化

# frontend/entrypoint.sh 中的关键环节
#!/bin/sh
set -e

# 先执行数据库迁移
echo "正在对比数据库版本并执行迁移..."
npx prisma migrate deploy

# 迁移成功后才启动应用
echo "数据库已就绪,正在启动 Next.js 应用..."
exec node server.js

原理说明

这就是**"原子化升级"**——要么全对,要么不跑:


5.8 增量更新:迭代的正确姿势

当你的项目进入迭代期(从 1 到 100),核心挑战是"如何高效更新"。

标准更新流程

# 1. 本地修改代码 → 提交 → 推送
git add .
git commit -m "feat: 描述更新内容"
git push origin main

# 2. SSH 登录服务器 → 拉取 → 重启
ssh tao
cd ~/ing/openrise
git pull origin main
docker compose up -d --build

SCP 增量更新(无 Git 时)

如果在无 GitHub 环境下使用 SCP 传输:

# 本地打包
tar -czf openrise.tar.gz . --exclude='node_modules' --exclude='.git' --exclude='.agents'

# 上传
scp openrise.tar.gz tao:~/ing/openrise/

# 服务器上:先洗地再解压(保留 .env)
ssh tao
cd ~/ing/openrise
find . -maxdepth 1 ! -name 'openrise.tar.gz' ! -name '.env' ! -name '.' -exec rm -rf {} +
tar -xzf openrise.tar.gz
docker compose up -d --build

原理说明

重要原则

  1. 不要在服务器上直接改代码——所有变动必须始于本地,经由 Git/SCP 投递
  2. 小步快跑——不要攒一大堆改动才提交,频繁提交+部署降低风险
  3. 配置安全——.env 不进入 Git,环境变量与代码彻底分离

5.9 常见问题排查

1. 端口被占用

# 修改 docker-compose.yml 中的端口映射
# 例如改为 "8080:80",然后访问 http://服务器IP:8080

2. Frontend 构建失败

# 常见原因:网络问题导致 npm install 失败
# 清除缓存重新构建
docker compose build frontend --no-cache

3. 容器反复重启

# 查看日志定位原因
docker compose logs -f frontend
# 常见错误:"Cannot find module" = 依赖缺失

4. 数据库连接失败

# 检查数据库容器状态
docker compose ps db
docker compose logs db
# 确认 db 容器健康后再启动 frontend

5. 注册/登录异常

# 检查 NEXTAUTH_URL 配置
# Docker 环境下应为 http://localhost(或通过 nginx 的端口)
# 生产环境应为 https://你的域名

6. IP 打不开网页?

90% 的原因是云平台安全组没有放通 80 端口
→ 登录腾讯云/阿里云控制台 → 安全组 → 添加入站规则
→ 放通 80(HTTP)和 443(HTTPS)端口

7. 镜像里包含环境变量吗?

不包含。 变量在容器启动时由 docker-compose.env 文件中注入。这确保了镜像本身是通用的、与环境无关的。


5.10 部署思维总结

Git 传递灵魂(逻辑),Docker 重塑肉身(环境),部署就是一场有序的更替。

本地开发 → Git/SCP → 服务器拉取 → Docker 构建 → 瞬间替换
  ↓           ↓           ↓           ↓           ↓
写代码     传源码     取到最新    打包镜像    1-3秒切换
                                         用户几乎无感

生产环境的敬畏之心


返回知识库 | 下一篇:CI/CD 自动化流水线