跳转至

生产部署

本指南介绍如何使用 Docker Compose、GHCR 容器镜像和 Caddy 反向代理将 Inklet 后端和门户部署到生产服务器。

服务器目录结构

生产部署使用简洁的目录布局:

~/deploy/
├── docker-compose.yml
└── .env

所有应用配置通过 .env 文件管理。Docker 镜像从 GitHub Container Registry (GHCR) 拉取。

Docker Compose

docker-compose.yml 文件定义了两个使用 GHCR 预构建镜像的服务:

services:
  backend:
    image: ghcr.io/inklet-2026/backend:latest
    restart: unless-stopped
    ports:
      - "4000:4000"
    env_file:
      - .env
    depends_on:
      - db

  portal-web:
    image: ghcr.io/inklet-2026/portal-web:latest
    restart: unless-stopped
    ports:
      - "3000:3000"
    env_file:
      - .env

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    ports:
      - "127.0.0.1:5432:5432"

volumes:
  pgdata:

数据库备份

PostgreSQL 数据存储在 Docker 卷中。请确保制定了备份策略。建议使用 pg_dump 配合 cron 定时任务,或在生产环境中使用托管数据库服务。

Caddy 反向代理

Caddy 作为反向代理,通过 Let's Encrypt 自动配置 HTTPS 证书。

/etc/caddy/Caddyfile

portal.iminklet.com {
    reverse_proxy localhost:3000
}

auth.iminklet.com {
    reverse_proxy localhost:4000
}

Caddy 自动完成以下工作:

  • 从 Let's Encrypt 获取并续期 TLS 证书
  • 将 HTTP 重定向到 HTTPS
  • 提供 HTTP/2 和 HTTP/3 支持

安装 Caddy

# Debian/Ubuntu
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

编辑 Caddyfile 后,重新加载 Caddy:

sudo systemctl reload caddy

CI/CD 流水线

Docker 镜像由 GitHub Actions 自动构建和发布。

构建触发条件

工作流在匹配 v* 模式的标签推送时触发:

on:
  push:
    tags:
      - 'v*'

构建流程

  1. GitHub Actions 在打标签的提交上检出代码
  2. 使用仓库中的 Dockerfile 构建 Docker 镜像
  3. 同时打上版本标签和 latest 标签:
    • ghcr.io/inklet-2026/backend:v1.2.0
    • ghcr.io/inklet-2026/backend:latest
  4. 将两个标签推送到 GHCR

创建发布

# 打标签
git tag v1.2.0
git push origin v1.2.0

CI 流水线自动构建并推送镜像。然后在服务器上部署:

sudo docker compose -f ~/deploy/docker-compose.yml pull
sudo docker compose -f ~/deploy/docker-compose.yml up -d

部署命令

拉取最新镜像

sudo docker compose -f ~/deploy/docker-compose.yml pull

启动/重启服务

sudo docker compose -f ~/deploy/docker-compose.yml up -d

查看日志

# 所有服务
sudo docker compose -f ~/deploy/docker-compose.yml logs -f

# 指定服务
sudo docker compose -f ~/deploy/docker-compose.yml logs -f backend

停止服务

sudo docker compose -f ~/deploy/docker-compose.yml down

回滚到指定版本

如果部署出现问题,将镜像固定到特定版本标签:

# 编辑 docker-compose.yml 使用指定版本
# image: ghcr.io/inklet-2026/backend:v1.1.0

sudo docker compose -f ~/deploy/docker-compose.yml pull
sudo docker compose -f ~/deploy/docker-compose.yml up -d

环境变量

.env 文件包含后端和门户的所有配置。在 ~/deploy/.env 创建该文件:

后端配置

变量 描述 示例
DATABASE_URL PostgreSQL 连接字符串 postgres://user:pass@db:5432/inklet?sslmode=disable
POSTGRES_USER PostgreSQL 用户名(用于数据库容器) inklet
POSTGRES_PASSWORD PostgreSQL 密码(用于数据库容器) strong-random-password
POSTGRES_DB PostgreSQL 数据库名(用于数据库容器) inklet
JWT_SECRET 用于签发 JWT 令牌的密钥 64-char-random-hex-string
PORT 后端 HTTP 端口 4000

AWS IoT Core

变量 描述 示例
AWS_REGION AWS 区域 us-east-1
AWS_ACCESS_KEY_ID 用于 IoT Core 的 IAM 访问密钥 AKIA...
AWS_SECRET_ACCESS_KEY IAM 密钥 wJal...
IOT_ENDPOINT AWS IoT Core 数据端点 xxxx-ats.iot.us-east-1.amazonaws.com
IOT_CERT_PATH 后端 MQTT 证书路径 /certs/backend.cert.pem
IOT_KEY_PATH 后端 MQTT 私钥路径 /certs/backend.private.key
IOT_ROOT_CA_PATH AWS IoT 根 CA 路径 /certs/root.pem
FACTORY_SECRET 用于 NFC 签名验证的 HMAC 密钥 32-byte-hex-string

OAuth

变量 描述 示例
GOOGLE_CLIENT_ID Google OAuth 客户端 ID 123456.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET Google OAuth 客户端密钥 GOCSPX-...
APPLE_CLIENT_ID Apple OAuth 服务 ID com.iminklet.auth
APPLE_TEAM_ID Apple Developer Team ID ABC123DEF4
APPLE_KEY_ID Apple Sign In 私钥 ID XYZ789
APPLE_PRIVATE_KEY Apple Sign In 私钥(PEM 格式) -----BEGIN PRIVATE KEY-----\n...

Stripe 计费

变量 描述 示例
STRIPE_SECRET_KEY Stripe API 密钥 sk_live_...
STRIPE_WEBHOOK_SECRET Stripe Webhook 签名密钥 whsec_...
STRIPE_PRICE_MONTHLY 月度计划的 Stripe Price ID price_...
STRIPE_PRICE_YEARLY 年度计划的 Stripe Price ID price_...

Portal Web

变量 描述 示例
VITE_AUTH_URL 后端 API URL(门户前端使用) https://auth.iminklet.com

URL 和 CORS

变量 描述 示例
FRONTEND_URL 门户 URL(用于 CORS 和 OAuth 重定向) https://portal.iminklet.com
BACKEND_URL 后端公开 URL https://auth.iminklet.com

密钥管理

切勿将 .env 文件提交到版本控制。使用安全的方式将密钥传输到生产服务器(例如 scp、密钥管理器或加密存储)。

DNS 配置

为你的域名配置 DNS 记录:

记录类型 名称
A portal.iminklet.com 服务器 IP 地址
A auth.iminklet.com 服务器 IP 地址

DNS 指向服务器后,Caddy 会自动配置 TLS 证书。

健康检查

验证部署是否正常:

# 后端健康检查
curl https://auth.iminklet.com/health

# 门户(返回 HTML)
curl -s -o /dev/null -w "%{http_code}" https://portal.iminklet.com

预期结果:后端返回 {"status":"ok"},门户返回 HTTP 200