DaemonDaemonAI LAB
DAEMON / ARTICLE

MonoRepo前后端项目部署到阿里云 ECS

2026.09.16 · 17 MIN READ

1. 部署架构

系统由两个应用服务和一个数据库服务组成:

服务技术容器
WebNext.js + Node.jshahame-web-1
ServerFastAPI + Pythonhahame-server-1
DatabasePostgreSQL + pgvectorhahame-postgres-1

当前部署链路:

text
Local
  ↓ git push
Codeup
  ↓
云效 Flow
  ↓
源码制品 hahame.tgz
  ↓
Runner
  ↓
ECS
  ↓
Docker Compose Build
  ↓
PostgreSQL → Server → Web

各组件的职责:

组件职责
Codeup代码版本管理
云效 FlowCI/CD 流程编排
制品仓库保存源码发布包
Runner连接 ECS 并执行部署
Docker构建和运行容器
Docker Compose多服务编排

当前由云效负责 CI/CD 和源码传输,ECS 同时承担 Build 和 Runtime。

2. ECS 环境

ECS 宿主机主要准备:

Node.js、Python 和 PostgreSQL 都运行在容器中,宿主机不需要单独安装。

宿主机尽量只提供 Docker,业务运行环境由镜像决定。

Swap

当前 ECS 配置为 2 GiB 内存和 4 GiB Swap。

Next.js 和 Python 的 Docker Build 可能瞬时占用较多内存,2 GiB 机器容易发生 OOM。Swap 可以降低构建进程被系统杀掉的概率。

Swap 是低内存机器的安全垫,不是性能优化,也不能替代真实内存。

Docker 镜像加速

国内 ECS 拉取 Docker Hub 镜像时,可能遇到:

text
Client.Timeout exceeded while awaiting headers

检查 Registry Mirror:

bash
docker info | grep -A 5 "Registry Mirrors"

ECS 的 Docker Mirror 只影响 ECS,不会自动同步到云效构建环境。

3. 云效流水线

当前流水线的执行顺序:

  1. 拉取 main 分支。
  2. 打包源码。
  3. 上传 hahame.tgz 到制品仓库。
  4. Runner 将制品发送到 ECS。
  5. ECS 解压制品。
  6. 执行 deploy-production.sh
  7. Docker Compose 构建并启动服务。

源码制品需要排除:

这样可以确保制品只包含构建需要的文件,不携带本地依赖、缓存和生产 Secret。

部署脚本

云效主机部署阶段只负责下载和解压制品,然后进入项目目录执行:

bash
scripts/deploy-production.sh

具体部署逻辑放在 Repository Script 中,可以获得以下好处:

云效负责编排流程,Repository Script 负责具体部署逻辑。

4. 生产环境变量

生产配置通过云效变量注入,主要包括:

其中以下变量需要开启私密模式:

部署时生成 /opt/hahame/app/.env,并限制文件权限:

bash
chmod 600 /opt/hahame/app/.env

代码、配置和 Secret 必须分开管理。Secret 不进入 Git,也不进入源码制品。

5. Docker Compose

当前启动命令:

bash
docker compose \
  --project-name hahame \
  --env-file .env \
  -f compose.yml \
  up -d --build --remove-orphans

服务启动关系:

text
PostgreSQL
    ↓ healthy
FastAPI
    ↓ Alembic Migration
    ↓ healthy
Next.js

Docker Compose 负责容器、网络、Volume、环境变量、服务依赖和健康检查的统一编排。

数据持久化

PostgreSQL 数据保存在 Volume hahame_postgres_data 中:

应用 Container 可以重新构建,但数据库 Volume 必须保护好。

不要在生产环境随意执行 docker compose down -v。参数 -v 会删除 Volume,可能直接删除 PostgreSQL 数据。

6. 日常发布

完成本地开发后提交代码:

bash
git add .
git commit -m "<message>"
git push origin main

然后执行云效流水线:

text
Codeup 最新代码
  ↓
生成 hahame.tgz
  ↓
上传制品
  ↓
Runner → ECS
  ↓
执行部署脚本
  ↓
Docker Build
  ↓
Compose 重建服务

Docker 会复用没有变化的 Layer,因此后续构建通常比第一次更快。

Dockerfile 中变化少的内容放在前面,频繁变化的源码 COPY 放在后面,可以提高 Layer Cache 利用率。

7. 常用运维

查看服务

bash
docker compose \
  --project-name hahame \
  --env-file .env \
  -f compose.yml \
  ps

也可以按容器名称过滤:

bash
docker ps --filter name=hahame

查看日志

查看最近 100 行日志:

bash
docker logs --tail 100 hahame-server-1

持续跟踪日志:

bash
docker logs -f --tail 100 hahame-server-1

按下 Ctrl + C 只会退出日志跟踪,不会停止 Container。

重启服务

bash
docker compose restart server

restart 只会重启现有 Container,不会重新构建 Image。代码发生变化后仍然需要重新 Build。

查看资源

bash
free -h
df -h
docker stats
docker system df

这些命令分别用于查看:

命令作用
free -hMemory 和 Swap
df -h磁盘占用
docker stats容器 CPU 和内存
docker system dfDocker 磁盘占用

清理 Build Cache

清理前先检查 Docker 磁盘占用:

bash
docker system df

确认后清理 Build Cache:

bash
docker builder prune

清理时不要误删数据库 Volume hahame_postgres_data

8. Multi-stage Build 踩坑

后端曾在 Docker Multi-stage Build 和 uv Virtual Environment 组合下出现 Exit Code 127

原因是 uv 创建的虚拟环境引用了 Build Stage 中的 Python 路径,但该路径在最终 Runtime Image 中不存在。

处理方式:

dockerfile
UV_PYTHON=/usr/local/bin/python3
UV_PYTHON_DOWNLOADS=never

Multi-stage Build 不仅要确认文件已经复制到 Runtime Stage,还要确认文件引用的解释器、动态库和软链接等运行时依赖仍然存在。

9. 当前架构的问题

当前生产服务器同时负责构建和运行:

text
云效
  ↓
发送源码
  ↓
ECS
├── Pull Base Image
├── Build Business Image
└── Runtime

对于当前 2 GiB ECS,这会带来:

更合理的职责边界是:CI 负责 Build,ECS 专注 Runtime。

10. 优化方向一:ACR 镜像发布

推荐的目标架构:

text
Developer
   ↓
Codeup
   ↓
云效 Flow
   ↓
Build Web / Server Image
   ↓
ACR
   ↓
ECS Pull
   ↓
Docker Compose Run

各组件重新分工:

组件职责
CodeupSource
云效Build
ACRImage Artifact
ECSRuntime
ComposeService Orchestration

架构变化可以概括为:

text
当前:源码 → ECS → Build → Run
优化:源码 → CI Build → ACR → ECS Pull → Run

也就是把 Build 从生产服务器移到 CI。

镜像版本

生产镜像不要只使用 latest,推荐使用 Git Commit SHA 或 Pipeline Build Number,例如:

text
hahame-web:a81f29c
hahame-server:a81f29c

这样可以实现:

ECS 部署命令也会从本机构建:

bash
docker compose up -d --build

调整为拉取已经构建好的镜像:

bash
docker compose pull server web
docker compose up -d --remove-orphans

优化后 ECS 不再 Build,只负责 Pull 和 Run。

11. 优化方向二:蓝绿部署

ACR 解决的是构建、镜像版本、发布产物和 ECS 构建压力,但 Container 替换时仍然可能出现短暂中断。

如果需要进一步降低发布中断,可以引入 Blue/Green:

text
                   Nginx
                     │
             ┌───────┴───────┐
             │               │
           Blue            Green
        Web + Server     Web + Server
             │               │
             └───────┬───────┘
                     │
                 PostgreSQL

一次发布的大致流程:

  1. Blue 当前对外提供服务。
  2. Green 拉取并启动新镜像。
  3. 等待 Green Health Check 通过。
  4. Nginx 将流量从 Blue 切换到 Green。
  5. 验证新版本。
  6. 确认稳定后停止 Blue。

下一次发布再从 Green 切换回 Blue。

数据库迁移需要保持新旧版本兼容,一般采用:

  1. Expand:增加新字段或新表。
  2. Migrate:逐步迁移数据和应用逻辑。
  3. Contract:确认旧版本退出后删除旧结构。

12. 最终架构演进

text
阶段 1:当前

Codeup
  ↓
云效
  ↓
源码制品
  ↓
ECS
├── Build
└── Runtime


阶段 2:推荐

Codeup
  ↓
云效 Build
  ↓
ACR
  ↓
ECS Runtime


阶段 3:低中断发布

Codeup
  ↓
云效 Build
  ↓
ACR
  ↓
ECS
├── Blue
└── Green
      ↓
Nginx Traffic Switch

最后记忆

组件负责内容
CodeupSource
云效CI/CD
ACRImage Artifact
ECSRuntime
Docker ComposeService Orchestration
VolumePersistent Data
.env / CI VariableConfiguration 和 Secret
Nginx + Blue/GreenTraffic Switch 和 Low Downtime

CI 负责 Build,Registry 负责保存镜像,ECS 负责 Runtime,Compose 负责服务编排;需要进一步降低发布中断时,再引入 Blue/Green。