部署方案选择
| 平台 | 适合场景 | 定价 | 特点 |
|---|---|---|---|
| Fly.io | 全功能后端服务 | 按用量计费,有免费层 | 全球边缘 VM、原生持久化、内置 PostgreSQL |
| Railway | 快速原型/全栈 | $5/月起 | 一键部署、内置数据库、超简单 |
| Render | 传统后端服务 | 有免费层(会休眠) | 类似 Heroku,操作简单 |
| Cloudflare Workers | 轻量 API/边缘逻辑 | 免费 10万请求/天 | 全球 300+ 节点,冷启动 <5ms |
| 自托管 VPS | 成本敏感/特殊需求 | 最低 | 完全控制,运维复杂度高 |
Dockerfile 最佳实践
多阶段构建(Multi-stage Build)
多阶段构建将"构建环境"和"运行环境"分离,最终镜像不包含 node_modules 的开发依赖、源代码等,体积大幅减小:
# Dockerfile — Bun 项目(多阶段构建)
# ── 阶段1:安装依赖 ──
FROM oven/bun:1 AS installer
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
# ── 阶段2:构建 ──
FROM oven/bun:1 AS builder
WORKDIR /app
COPY --from=installer /app/node_modules ./node_modules
COPY . .
# 生成 Prisma Client
RUN bunx prisma generate
# 可选:编译 TypeScript
RUN bun build ./src/index.ts --outdir ./dist --target bun
# ── 阶段3:运行(最小镜像)──
FROM oven/bun:1-slim AS runner
WORKDIR /app
# 非 root 用户运行(安全最佳实践)
USER bun
# 只复制运行时需要的文件
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/prisma ./prisma
COPY package.json ./
# 暴露端口
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
# 启动命令
CMD ["bun", "run", "./dist/index.js"]
# Dockerfile — Node.js 22 项目
FROM node:22-alpine AS installer
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # tsc 编译
RUN npx prisma generate
FROM node:22-alpine AS runner
WORKDIR /app
USER node
COPY --from=installer /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/prisma ./prisma
COPY package.json ./
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
.dockerignore
# .dockerignore — 排除不需要的文件
node_modules
dist
.env
.env.local
.git
*.test.ts
*.spec.ts
README.md
.DS_Store
健康检查端点
// src/routes/health.ts
import { Hono } from 'hono';
import { prisma } from '../lib/prisma';
const health = new Hono();
// 简单健康检查(负载均衡器用)
health.get('/health', (c) => c.json({ status: 'ok' }));
// 深度健康检查(包含依赖项状态)
health.get('/health/ready', async (c) => {
const checks = {
database: false,
uptime: process.uptime(),
memory: process.memoryUsage(),
timestamp: new Date().toISOString(),
};
try {
await prisma.$queryRaw`SELECT 1`;
checks.database = true;
} catch {}
const allHealthy = checks.database;
return c.json({ status: allHealthy ? 'ok' : 'degraded', ...checks },
allHealthy ? 200 : 503);
});
export { health as healthRouter };
Fly.io 部署
# 安装 flyctl
curl -L https://fly.io/install.sh | sh
# 登录
fly auth login
# 初始化(在项目目录中)
fly launch
# 会自动检测到 Dockerfile,生成 fly.toml 配置文件
# 设置环境变量(Secrets)
fly secrets set DATABASE_URL="postgresql://..."
fly secrets set JWT_ACCESS_SECRET="your-super-secret-key"
fly secrets set JWT_REFRESH_SECRET="another-secret-key"
# 部署
fly deploy
# 查看日志
fly logs
# 扩容(2个实例)
fly scale count 2
# 查看状态
fly status
# fly.toml — Fly.io 配置文件
app = "my-backend"
primary_region = "nrt" # 日本东京(亚洲用户选这里)
[build]
[env]
PORT = "3000"
NODE_ENV = "production"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true # 无流量时自动停机(省钱)
auto_start_machines = true
min_machines_running = 0
[http_service.concurrency]
type = "connections"
hard_limit = 1000
soft_limit = 800
[[vm]]
size = "shared-cpu-1x" # 最小规格,按需升级
memory = "256mb"
数据库迁移(部署时自动运行)
# fly.toml — 添加 release 命令(部署前自动运行)
[deploy]
release_command = "bunx prisma migrate deploy"
Cloudflare Workers — 边缘部署
Cloudflare Workers 运行在 CF 的 300+ 个全球节点上,代码离用户最近的地方执行。限制:不能使用 Node.js 特定 API,不能访问文件系统。Hono 完美支持 Workers:
npm create hono@latest my-worker -- --template cloudflare-workers
// src/index.ts — Cloudflare Workers 版本
import { Hono } from 'hono';
// 定义环境变量类型(绑定 CF 的 KV、D1 等)
type Bindings = {
DB: D1Database; // Cloudflare D1(SQLite)
KV: KVNamespace; // Cloudflare KV 存储
JWT_SECRET: string; // Workers Secrets
};
const app = new Hono<{ Bindings: Bindings }>();
app.get('/api/users', async (c) => {
// 查询 D1 数据库(全球复制的 SQLite)
const { results } = await c.env.DB
.prepare('SELECT id, name, email FROM users LIMIT 20')
.all();
return c.json(results);
});
app.get('/api/cache/:key', async (c) => {
// 读取 KV 缓存
const cached = await c.env.KV.get(c.req.param('key'), 'json');
if (cached) return c.json({ source: 'cache', data: cached });
return c.json({ source: 'miss' }, 404);
});
export default app; // Workers 使用 default export
# 部署到 Cloudflare Workers
npx wrangler deploy
# 本地开发(模拟 CF 环境)
npx wrangler dev
# 设置 Secret
npx wrangler secret put JWT_SECRET
环境变量管理
# 推荐目录结构
.env # 提交到 git:公开的默认值
.env.example # 提交到 git:模板文档
.env.local # 不提交:本地覆盖
.env.test # 不提交:测试环境
.env.production # 不提交:生产环境(或使用平台 Secrets)
// lib/env.ts — 使用 Zod 验证环境变量
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
JWT_ACCESS_SECRET: z.string().min(32),
JWT_REFRESH_SECRET: z.string().min(32),
REDIS_URL: z.string().url().optional(),
GITHUB_CLIENT_ID: z.string().optional(),
GITHUB_CLIENT_SECRET: z.string().optional(),
});
// 启动时验证所有环境变量,缺失则立即失败
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('❌ 环境变量配置错误:', result.error.flatten().fieldErrors);
process.exit(1);
}
export const env = result.data;
GitHub Actions CI/CD
# .github/workflows/deploy.yml
name: Test and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
ports: ['5432:5432']
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with: { bun-version: latest }
- run: bun install --frozen-lockfile
- run: bun run typecheck # tsc --noEmit
- run: bun test --coverage
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: fly deploy --remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
零停机滚动更新
# Fly.io 默认支持滚动更新
# 新版本实例启动并通过健康检查后,旧实例才会停止
fly deploy
# 手动回滚到上一个版本
fly releases list
fly deploy --image registry.fly.io/my-app:v123
# 蓝绿部署:保留 n 个旧实例直到新实例就绪
fly deploy --strategy bluegreen
生产清单:① 健康检查端点就绪;② 优雅关机(SIGTERM 处理);③ 日志结构化(JSON 格式);④ 错误监控(Sentry);⑤ 性能监控(OpenTelemetry);⑥ 数据库连接池配置合理;⑦ 速率限制防止滥用;⑧ HTTPS 强制;⑨ 安全头部(helmet)。
优雅关机(Graceful Shutdown)
// 处理 SIGTERM(容器停止信号)
const server = Bun.serve({ port: 3000, fetch: app.fetch });
const shutdown = async (signal: string) => {
console.log(`收到 ${signal},开始优雅关机...`);
// 停止接受新请求
server.stop(true); // true = 等待现有请求完成
// 等待队列任务完成(给30秒)
await emailWorker.close();
// 断开数据库连接
await prisma.$disconnect();
console.log('关机完成');
process.exit(0);
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
恭喜完成本教程! 你已经掌握了从运行时选择到生产部署的完整后端开发知识链:Node.js 事件循环 → Bun 工具链 → Hono API → tRPC 类型安全 → Prisma 数据库 → 认证系统 → WebSocket → 任务队列 → 自动化测试 → 生产部署。现在去构建真实的项目吧!