多实例本地开发环境生成器Skill gen-env

gen-env 技能是一个用于生成和管理多实例本地开发环境的工具。它通过创建唯一的实例身份,实现端口、数据、网络和浏览器状态的完全隔离,解决多个项目实例同时运行时产生的端口冲突、数据污染和身份验证混淆等问题。核心功能包括动态端口分配、基于子域名的浏览器隔离、Docker资源命名隔离以及精准的实例清理。适用于多分支并行开发、功能测试和版本对比等场景,提升开发效率和系统稳定性。关键词:本地开发环境,多实例隔离,Docker Compose,端口分配,浏览器隔离,开发工具,DevOps。

DevOps 0 次安装 0 次浏览 更新于 2/28/2026

名称: 生成环境 描述: 创建、更新或审查项目的 gen-env 命令,用于在本地主机上运行多个隔离实例。处理实例身份、端口分配、数据隔离、浏览器状态分离和清理。

gen-env 技能

生成或审查一个 gen-env 命令,该命令允许在本地主机上同时运行项目的多个隔离实例(例如,多个工作树、功能分支或版本)。

问题

没有隔离的情况下,同一项目的多个实例会:

  • 争夺硬编码端口(3000、5432、8080)
  • 共享 Docker 卷 → 数据损坏
  • 共享浏览器 cookie/localStorage → 身份验证混淆
  • 容器名称不明确 → 无法区分哪个是哪个
  • 存在灾难性清理风险 → docker down -v 会销毁一切

解决方案:实例身份

一切都源于一个工作空间名称

名称 = "功能-x"
         ↓
┌─────────────────────────────────────────────────────┐
│ COMPOSE_PROJECT_NAME = 本地网络-功能-x              │
│ DOCKER_NETWORK       = 本地网络-功能-x              │
│ VOLUME_PREFIX        = 本地网络-功能-x              │
│ CONTAINER_PREFIX     = 本地网络-功能-x-             │
│ TILT_HOST            = 功能-x.localhost             │
│ 端口                = 动态分配                      │
│ URL                 = 基于主机 + 端口派生          │
└─────────────────────────────────────────────────────┘

隔离维度

1. 端口隔离

每个实例从临时端口范围(49152-65535)获取唯一端口。

2. 数据隔离

Docker Compose 项目名称控制卷命名:

  • 实例 A: 本地网络-主分支_postgres_data
  • 实例 B: 本地网络-功能-x_postgres_data

无交叉污染。独立的数据库。

3. 网络隔离

每个实例有独立的 Docker 网络。容器通过服务名相互引用,不会冲突。

4. 浏览器状态隔离

关键点localhost 上的不同端口仍然共享 cookie!

http://localhost:3000  ─┐
                        ├─ 相同的 cookie、localStorage
http://localhost:3001  ─┘

解决方案:通过 *.localhost 进行子域隔离:

http://主分支.localhost:3000      ─ 独立的 cookie
http://功能-x.localhost:3001 ─ 独立的 cookie

Chrome/Edge 自动将 *.localhost 视为 127.0.0.1。无需修改 /etc/hosts

5. 身份验证隔离

每个实例可以有自己的身份验证领域/受众,防止令牌混淆。

6. 资源命名

容器、卷、Tilt 资源、日志上的清晰前缀 → 确切知道您正在查看哪个实例。

实施清单

创建或审查 gen-env 时:

身份与命名:

  • [ ] 需要 --name <工作空间> 参数
  • [ ] 验证名称(字母数字 + 短横线,DNS 最长 63 个字符)
  • [ ] 根据名称生成 COMPOSE_PROJECT_NAME
  • [ ] 生成 DOCKER_NETWORKVOLUME_PREFIXCONTAINER_PREFIX
  • [ ] 为浏览器隔离生成 *_HOST名称.localhost

端口分配:

  • [ ] 从临时范围(49152-65535)分配
  • [ ] 分配前检查端口可用性
  • [ ] 使用短超时(100 毫秒)以兼容 CI
  • [ ] 优雅处理 IPv6 禁用环境

持久化:

  • [ ] 锁文件存储名称 + 端口(.gen-env.lock
  • [ ] 锁文件存在且名称匹配时重用端口
  • [ ] --force 重新生成所有内容
  • [ ] --clean 删除生成的文件

输出:

  • [ ] 生成 .localnet.env(或项目特定名称)
  • [ ] 带有生成时间戳的清晰标题
  • [ ] 所有派生的 URL 使用正确的主机 + 端口

集成:

  • [ ] 通过 .envrc 将脚本添加到 PATH
  • [ ] .envrc 引用生成的 env 文件
  • [ ] 与 Docker Compose 配合使用(--env-file
  • [ ] 与 Tilt 配合使用(Starlark 读取 env 文件)

生成的环境结构

# .localnet.env - 由 gen-env 生成
# 实例: 功能-x
# 生成时间: 2024-01-15T10:30:00Z

# === 实例身份 ===
WORKSPACE_NAME=功能-x
COMPOSE_NAME=本地网络-功能-x
COMPOSE_PROJECT_NAME=本地网络-功能-x
DOCKER_NETWORK=本地网络-功能-x
VOLUME_PREFIX=本地网络-功能-x
CONTAINER_PREFIX=本地网络-功能-x-

# === 主机(用于浏览器隔离) ===
APP_HOST=功能-x.localhost
TILT_HOST=功能-x.localhost

# === 分配的端口 ===
POSTGRES_PORT=51234
REDIS_PORT=51235
API_PORT=51236
WEB_PORT=51237
# ... 更多端口

# === 派生的 URL ===
DATABASE_URL=postgres://user:pass@localhost:51234/dev
WEB_URL=http://功能-x.localhost:51237
API_URL=http://功能-x.localhost:51236

direnv 集成

# .envrc
PATH_add bin  # 或 scripts

dotenv_if_exists .localnet.env

参考实现(TypeScript/Bun)

有关完整实现,请参阅 @IMPLEMENTATION.md。

关键类型:

interface InstanceConfig {
  name: string;                    // 工作空间身份
  composeName: string;             // Docker Compose 项目名称
  dockerNetwork: string;           // Docker 网络名称
  volumePrefix: string;            // Docker 卷前缀
  containerPrefix: string;         // 容器名称前缀
  host: string;                    // 浏览器主机名(名称.localhost)
  ports: Record<string, number>;   // 分配的端口
  urls: Record<string, string>;    // 派生的 URL
}

interface LockfileData {
  version: 1;
  generatedAt: string;
  instance: InstanceConfig;
}

清理模式

按实例进行精准清理:

# 仅清理功能-x(容器 + 卷 + 网络)
docker compose -p 本地网络-功能-x down -v

# 或通过 gen-env
gen-env --clean  # 删除 .localnet.env 和 .gen-env.lock

# 列出所有本地网络实例
docker ps -a --filter "name=本地网络-" --format "table {{.Names}}\t{{.Status}}"

# 核选项(所有实例)- 危险
docker ps -a --filter "name=本地网络-" -q | xargs docker rm -f
docker volume ls --filter "name=本地网络-" -q | xargs docker volume rm

常见模式

模式 1:基于工作树的命名

# 从 git 工作树目录派生名称
WORKTREE_NAME=$(basename "$(git rev-parse --show-toplevel)")
gen-env --name "$WORKTREE_NAME"

模式 2:基于分支的命名

# 从分支派生名称
BRANCH=$(git branch --show-current | tr '/' '-')
gen-env --name "$BRANCH"

模式 3:显式命名

# 用户指定(建议为清晰起见)
gen-env --name bb-dev
gen-env --name testing-v2

审查清单

审查现有 gen-env 时:

  1. 它是否创建实例身份?(不仅仅是端口)
  2. 它是否设置 COMPOSE_PROJECT_NAME?(控制 Docker 命名)
  3. 它是否生成浏览器安全的主机?*.localhost
  4. URL 是否使用正确的主机派生?(不是硬编码的 localhost
  5. 清理是否精准?(可以删除一个实例而不影响其他实例)
  6. 锁文件是否存储名称?(用于跨运行的一致性)
  7. 它是否验证名称冲突?(如果锁文件有不同的名称则发出警告)

反模式

URL 中硬编码 localhost

WEB_URL=http://localhost:${WEB_PORT}  # 错误:共享 cookie

使用实例主机

WEB_URL=http://${APP_HOST}:${WEB_PORT}  # 正确:隔离的 cookie

没有 COMPOSE_PROJECT_NAME

# 错误:使用目录名,可能冲突
docker compose up

显式项目名称

COMPOSE_PROJECT_NAME=本地网络-功能-x
docker compose up  # 对所有资源使用项目名称

共享清理

docker compose down -v  # 错误:哪个实例?

实例特定清理

docker compose -p 本地网络-功能-x down -v  # 正确:明确

参考资料

  • @IMPLEMENTATION.md - 完整的 TypeScript 实现
  • @ADVANCED_PATTERNS.md - 复杂场景(单体仓库、CI、Tilt 集成)