ECC Docker Patterns 实战:Compose 本地开发编排、容器安全加固与 CLI 安装器测试 Harness

ECC Docker Patterns 实战:Compose 本地开发编排、容器安全加固与 CLI 安装器测试 Harness ECC Docker Patterns 实战Compose 本地开发编排、容器安全加固与 CLI 安装器测试 Harness【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC本文基于 ECC 仓库中的 docker-patterns 技能文档系统讲解 Docker 与 Docker Compose 在本地开发环境中的编排模式标准 Web 应用栈、多阶段 Dockerfile、网络与卷策略、容器安全加固与调试手段并结合仓库内 docker/plugin-setup/ 的真实参考实现展示 ECC 如何用最小权限 只读挂载 网络隔离的加固 harness 在容器中测试自己的 CLI 安装器。读完你可以直接复制一套可运行的 Compose 配置与生产级 Dockerfile并能理解每一处安全约束背后的动机。何时启用这些模式原文明确列出了五类典型场景为本地开发搭建 Docker Compose 环境设计多容器架构排查容器网络或卷volume问题审查 Dockerfile 的安全性与体积从本地裸机开发迁移到容器化工作流。这些场景覆盖了一个容器化项目从起服务到安全上线的完整链路下文按此脉络展开。Docker Compose 本地开发标准栈原文给出了一个四服务应用、Postgres、Redis、Mailpit的标准 Web 应用栈这里完整保留并解释关键设计# docker-compose.yml services: app: build: context: . target: dev # Use dev stage of multi-stage Dockerfile ports: - 3000:3000 volumes: - .:/app # Bind mount for hot reload - /app/node_modules # Anonymous volume -- preserves container deps environment: - DATABASE_URLpostgres://postgres:postgresdb:5432/app_dev - REDIS_URLredis://redis:6379/0 - NODE_ENVdevelopment depends_on: db: condition: service_healthy redis: condition: service_started command: npm run dev db: image: postgres:16-alpine ports: - 5432:5432 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: app_dev volumes: - pgdata:/var/lib/postgresql/data - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: [CMD-SHELL, pg_isready -U postgres] interval: 5s timeout: 3s retries: 5 redis: image: redis:7-alpine ports: - 6379:6379 volumes: - redisdata:/data mailpit: # Local email testing image: axllent/mailpit ports: - 8025:8025 # Web UI - 1025:1025 # SMTP volumes: pgdata: redisdata:这份配置里有几个容易被忽略但很关键的点target: dev引用多阶段 Dockerfile 中的dev阶段而不是每次都使用最终生产镜像避免把生产镜像里被裁剪过的依赖和工具带进开发环境depends_on的condition区分使用对db要求service_healthy配合下方pg_isready健康检查确保数据库真正可连接后再启动应用对redis只要求service_started——因为 Redis 没有等价的健康检查配置用健康条件反而会一直等待。这种按依赖强度选条件的写法值得照抄/app/node_modules匿名卷源码 bind mount.:/app会覆盖容器内预装的node_modules。单独声明/app/node_modules匿名卷后宿主机目录不会遮蔽容器内的依赖目录保证npm ci安装的依赖与镜像内版本一致/docker-entrypoint-initdb.d/init.sqlPostgres 官方镜像在首次初始化数据目录时自动执行该目录下的脚本这是无迁移框架时做种子数据的标准手法Mailpit本地邮件测试服务1025是 SMTP 收信端口8025是 Web UI应用把 SMTP 指向mailpit:1025即可在浏览器里查看发出的邮件。多阶段 Dockerfile开发、构建与生产分离原文用一个四阶段 Dockerfile 演示一份 Dockerfile 服务多种环境的完整写法# Stage: dependencies FROM node:22-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci # Stage: dev (hot reload, debug tools) FROM node:22-alpine AS dev WORKDIR /app COPY --fromdeps /app/node_modules ./node_modules COPY . . EXPOSE 3000 CMD [npm, run, dev] # Stage: build FROM node:22-alpine AS build WORKDIR /app COPY --fromdeps /app/node_modules ./node_modules COPY . . RUN npm run build npm prune --production # Stage: production (minimal image) FROM node:22-alpine AS production WORKDIR /app RUN addgroup -g 1001 -S appgroup adduser -S appuser -u 1001 USER appuser COPY --frombuild --chownappuser:appgroup /app/dist ./dist COPY --frombuild --chownappuser:appgroup /app/node_modules ./node_modules COPY --frombuild --chownappuser:appgroup /app/package.json ./ ENV NODE_ENVproduction EXPOSE 3000 HEALTHCHECK --interval30s --timeout3s CMD wget -qO- http://localhost:3000/health || exit 1 CMD [node, dist/server.js]各阶段的职责划分清晰阶段用途关键点deps独立安装依赖只COPY锁文件再npm ci让依赖层在代码变更时命中构建缓存dev热重载开发复用deps的node_modules再覆盖源码配合 Compose bind mountbuild执行构建npm run build后npm prune --production裁掉开发依赖production最小运行镜像只从build拷贝dist、生产依赖与package.json非 root 用户自带HEALTHCHECK生产阶段三个细节值得注意addgroup/adduser显式指定 UID/GID1001创建非 root 用户COPY --frombuild --chownappuser:appgroup保证拷贝文件的所有者与运行用户匹配避免权限问题HEALTHCHECK使用wget -qO-探测/health端点为编排系统Swarm/K8s提供存活依据。Override 文件开发与生产配置分离Compose 对docker-compose.override.yml自动合并加载是放开发专用配置的标准位置生产则用显式的-f组合# docker-compose.override.yml (auto-loaded, dev-only settings) services: app: environment: - DEBUGapp:* - LOG_LEVELdebug ports: - 9229:9229 # Node.js debugger # docker-compose.prod.yml (explicit for production) services: app: build: target: production restart: always deploy: resources: limits: cpus: 1.0 memory: 512M# Development (auto-loads override) docker compose up # Production docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d这套约定的收益是调试端口如 Node inspector 的 9229、debug 日志等敏感或无用的开发配置永远不会进入生产镜像配置而deploy.resources.limits在 Compose 中声明资源上限在与 Swarm 或受管平台交互时可直接生效。容器网络服务发现、自定义网络与最小暴露按服务名解析的服务发现同一 Compose 网络内的服务以服务名互相解析因此连接串里直接写服务名即可# From app container: postgres://postgres:postgresdb:5432/app_dev # db resolves to the db container redis://redis:6379/0 # redis resolves to the redis container用自定义网络做服务隔离把前端、API、数据库挂到不同网络可以让数据库只对 API 可达services: frontend: networks: - frontend-net api: networks: - frontend-net - backend-net db: networks: - backend-net # Only reachable from api, not frontend networks: frontend-net: backend-net:一个服务可以挂在多个网络上api因此同时服务对前端与对数据库两个方向而frontend无法直接触及backend-net。只暴露必要的端口services: db: ports: - 127.0.0.1:5432:5432 # Only accessible from host, not network # Omit ports entirely in production -- accessible only within Docker network绑定127.0.0.1使数据库端口仅宿主机本地可达生产环境则干脆省略ports服务间流量完全走 Docker 内部网络。卷策略三类卷各司其职volumes: # Named volume: persists across container restarts, managed by Docker pgdata: # Bind mount: maps host directory into container (for development) # - ./src:/app/src # Anonymous volume: preserves container-generated content from bind mount override # - /app/node_modules命名卷Named volumeDocker 托管、跨容器重启持久化用于数据库数据目录Bind mount把宿主机目录映射进容器用于开发热重载匿名卷Anonymous volume不指定宿主机路径的卷声明专门防止 bind mount 覆盖容器内生成物。常见组合模式services: app: volumes: - .:/app # Source code (bind mount for hot reload) - /app/node_modules # Protect containers node_modules from host - /app/.next # Protect build cache db: volumes: - pgdata:/var/lib/postgresql/data # Persistent data - ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql # Init scripts即源码 bind mount 容器生成物匿名卷 数据命名卷的三层结构。容器安全加固Dockerfile 层面的加固# 1. Use specific tags (never :latest) FROM node:22.12-alpine3.20 # 2. Run as non-root RUN addgroup -g 1001 -S app adduser -S app -u 1001 USER app # 3. Drop capabilities (in compose) # 4. Read-only root filesystem where possible # 5. No secrets in image layers固定具体版本标签保证可复现构建非 root 用户把容器逃逸的破坏面降到最低密钥绝不出现在任何镜像层镜像层可被逐层读取写进ENV等于公开。Compose 层面的运行时限制services: app: security_opt: - no-new-privileges:true read_only: true tmpfs: - /tmp - /app/.cache cap_drop: - ALL cap_add: - NET_BIND_SERVICE # Only if binding to ports 1024read_only: true让根文件系统只读需要写盘的路径用tmpfs显式放行cap_drop: ALL丢弃全部 Linux capabilities仅当需要绑定 1024 以下端口时才按最小原则加回NET_BIND_SERVICEno-new-privileges禁止进程通过setuid位提权。密钥管理# GOOD: Use environment variables (injected at runtime) services: app: env_file: - .env # Never commit .env to git environment: - API_KEY # Inherits from host environment # GOOD: Docker secrets (Swarm mode) secrets: db_password: file: ./secrets/db_password.txt services: db: secrets: - db_password # BAD: Hardcoded in image # ENV API_KEYsk-proj-xxxxx # NEVER DO THISenv_file与继承宿主环境变量都发生在运行时注入不落盘进镜像Swarm 的secrets则提供文件级密钥分发。反面教材只有一条任何写死在镜像里的密钥都必须杜绝。ECC 的真实参考实现加固的 CLI 安装器测试 HarnessECC 仓库中同主题的 skills/docker-patterns/SKILL.md 在上述通用模式之外多了一节Hardened CLI Installer Harnesses——用容器测试安装器在一次性项目副本上的行为同时不允许测试污染源 checkout。仓库中 docker/plugin-setup/ 就是这套模式的落地实现也是原文档所有安全约束的最佳注脚。尊重平台边界Linux 发行版Debian、Ubuntu跑真实容器macOS 无法作为 Docker 容器运行Docker 共享 Linux 内核macOS 上应原生运行同一套无 shell 测试入口Windows 容器需要 Windows Docker 引擎平台无关逻辑放在原生 Windows CI runner 上保留原生的 Ubuntu/macOS/Windows CI 矩阵来覆盖宿主特定的路径、shim、引号与文件系统行为绝不能用 Linux 容器的结果宣称验证了 macOS/Windows 行为。隔离契约在 compose.yaml 中的落地compose.yaml 通过 YAML 锚点把安全配置集中定义一次各服务复用基础镜像用不可变摘要钉死compose.yaml 的x-node-image锚点固定为node:22-bookworm-slimsha256:6c7479...real-cli-ubuntu则固定ubuntu:24.04sha256:4fbb8e...见 compose.yaml同时构建参数里钉死CLAUDE_CODE_VERSION: 2.1.220x-real-cli锚点compose.yaml集中声明了整组运行时限制x-real-cli: real-cli working_dir: /workspace network_mode: none read_only: true pids_limit: 256 cap_drop: - ALL security_opt: - no-new-privileges:true tmpfs: - /tmp:rw,nosuid,nodev,exec,size${ECC_TMPFS_SIZE:-2g},uid1000,gid1000,mode0700 - /workspace:rw,nosuid,nodev,noexec,size${ECC_WORKSPACE_SIZE:-1g},uid1000,gid1000,mode0700 environment: CLAUDE_CONFIG_DIR: /tmp/ecc-claude-config DISABLE_AUTOUPDATER: 1 HOME: /tmp/ecc-home NPM_CONFIG_CACHE: /tmp/npm-cache volumes: - type: bind source: ../.. target: /ecc read_only: true - type: bind source: ${TEST_PROJECT:-../../tests/fixtures/docker-plugin-project} target: /source-project read_only: true对照文档的隔离契约逐条看契约要求实现位置默认无网络network_mode: none仅real-cli-networked服务compose.yaml通过profiles: [networked]显式开启network_mode: default只读挂载仓库与源项目两个 bind mount 均read_only: true目标分别为/ecc与/source-project可变工作区放 tmpfs/workspace以noexec,mode0700挂载ECC_WORKSPACE_SIZE默认 1g独立控制npm 缓存必须可执行/tmp挂exec且NPM_CONFIG_CACHE/tmp/npm-cacheECC_TMPFS_SIZE默认 2g非 root 数值 UID/GID两个 tmpfs 均带uid1000,gid1000Dockerfile 先getent passwd 1000/getent group 1000校验账户存在再USER 1000:1000——因为不同发行版 uid 1000 的账号名不同用数值身份可跨发行版复用只读根文件系统 无新特权 全量丢弃 capabilities 有限 PIDsread_only: true、no-new-privileges:true、cap_drop: [ALL]、pids_limit: 256Dockerfile 还演示了文档固定具体标签的更强形式用ARG接受带 sha256 摘要的镜像引用运行时环境HOME、CLAUDE_CONFIG_DIR、NODE_PATH全部重定向到可写的/tmp区域并DISABLE_AUTOUPDATER1防止测试期间自更新。从只读 checkout 打包出可执行的 ECC CLIdry-run/install模式并不直接使用挂载的 checkout而是由 prepare-packed-cli.js 现场打一个 npm 包npm pack --ignore-scripts且npm_config_offlinetrueprepare-packed-cli.js不执行任何包生命周期脚本、不访问网络校验package.json包名必须是ecc-universalbin.ecc必须映射到scripts/ecc.js且scripts/ecc.js与三个安装清单manifests/install-components.json、install-modules.json、install-profiles.json必须存在prepare-packed-cli.js所有路径经isWithin校验拒绝任何逃出解包根目录的符号链接或相对路径prepare-packed-cli.js输出目录也强制限定在/tmp下prepare-packed-cli.js。这正对应文档中Use argument arrays orspawnSync(..., { shell: false })绝不把项目路径插值进 shell 命令的要求——run()辅助函数始终以参数数组方式spawnSyncprepare-packed-cli.js。四种模式与 dry-run 契约run-real-cli.sh 用case白名单只接受dry-run、install、plugin、shell四种模式未知模式直接以退出码 2 失败。脚本先把只读的/source-project完整拷贝到/workspace/project的可写私有目录run-real-cli.sh再进行任何变更。dry-run模式执行当前公开的命令契约ecc install --profile core --target claude-project --dry-run --json随后断言.claude目录不存在dry run 不得产生变更并把 JSON 计划交给 verify-install-plan.js 复核run-real-cli.sh。install模式则执行两次隔离安装、检查受管安装状态、list-installed并运行doctorrun-real-cli.sh。运行、连接与清理先校验 Compose 模型再构建docker compose -f docker/plugin-setup/compose.yaml config --quiet docker compose -f docker/plugin-setup/compose.yaml \ build real-cli real-cli-ubuntu在两个镜像中跑默认安全流程run-real-cli.sh 默认即dry-rundocker compose -p ecc-plugin-debian-test \ -f docker/plugin-setup/compose.yaml \ run --rm -T real-cli dry-run docker compose -p ecc-plugin-ubuntu-test \ -f docker/plugin-setup/compose.yaml \ run --rm -T real-cli-ubuntu dry-run需要长驻会话时不用--rm启动命名容器离开终端也不会销毁会话docker compose -p ecc-plugin-session \ -f docker/plugin-setup/compose.yaml \ run --detach --name ecc-plugin-shell real-cli shell确认运行后可直接复用终端接入docker inspect --format {{.State.Running}} ecc-plugin-shell docker exec -it -w /workspace/project ecc-plugin-shell bash退出 shell 后容器仍在用同一条docker exec -it重连。结束工作后精确删除具名容器并清理该 Compose 项目资源docker rm --force ecc-plugin-shell docker compose -p ecc-plugin-session \ -f docker/plugin-setup/compose.yaml \ down --remove-orphans关于凭据默认服务不继承任何宿主凭据、不挂载凭据目录、无网络确需网络时先用docker compose --profile networked run real-cli-networked shell显式开启。CI 必须继承宿主凭据时用显式--env NAME声明该注入、理解其值在容器生命周期内可被检查并在使用后立即删除该具名容器。此外 package.json 提供了test:plugin-setup-platform脚本把同一套 fixture 测试入口原生跑在宿主上补齐容器无法覆盖的平台行为。.dockerignore构建上下文裁剪直接决定镜像体积与敏感信息泄露面原文推荐的清单node_modules .git .env .env.* dist coverage *.log .next .cache docker-compose*.yml Dockerfile* README.md tests/其中.env系列必须排除防止本地密钥进入构建上下文被后续层捕获node_modules排除则避免宿主机依赖覆盖镜像内npm ci的结果——与前面匿名卷保护 node_modules的策略一脉相承。调试手册常用命令# View logs docker compose logs -f app # Follow app logs docker compose logs --tail50 db # Last 50 lines from db # Execute commands in running container docker compose exec app sh # Shell into app docker compose exec db psql -U postgres # Connect to postgres # Inspect docker compose ps # Running services docker compose top # Processes in each container docker stats # Resource usage # Rebuild docker compose up --build # Rebuild images docker compose build --no-cache app # Force full rebuild # Clean up docker compose down # Stop and remove containers docker compose down -v # Also remove volumes (DESTRUCTIVE) docker system prune # Remove unused images/containers注意down -v会连命名卷一并删除数据库数据将不可恢复是明确的破坏性操作。排查网络问题# Check DNS resolution inside container docker compose exec app nslookup db # Check connectivity docker compose exec app wget -qO- http://api:3000/health # Inspect network docker network ls docker network inspect project_default排查顺序即容器内 DNS → 连通性 → 网络拓扑nslookup失败说明服务未就绪或不在同一网络wget失败但 DNS 正常则多为端口/协议问题network inspect用来确认两个服务是否真的共享网络。反模式清单原文最后汇总了六条应避免的做法可作为 Code Review 时的检查项# BAD: Using docker compose in production without orchestration # Use Kubernetes, ECS, or Docker Swarm for production multi-container workloads # BAD: Storing data in containers without volumes # Containers are ephemeral -- all data lost on restart without volumes # BAD: Running as root # Always create and use a non-root user # BAD: Using :latest tag # Pin to specific versions for reproducible builds # BAD: One giant container with all services # Separate concerns: one process per container # BAD: Putting secrets in docker-compose.yml # Use .env files (gitignored) or Docker secrets小结这套模式的主线可以浓缩为一句话开发环境用多阶段 override 匿名卷换取热重载与快速重建安全用最小权限四件套非 root、只读根文件系统、丢弃 capabilities、禁新特权与网络最小暴露收口。ECC 仓库 docker/plugin-setup/compose.yaml 的x-real-cli锚点展示了如何在真实工程里把这些约束固化为可复用配置而 prepare-packed-cli.js 与 run-real-cli.sh 则演示了只读源 可写 tmpfs 副本 无网络 模式白名单的容器化测试隔离契约。按原文的路径 docker-patterns 技能 与 skills/docker-patterns/SKILL.md 对照阅读即可从通用模式到仓库级参考实现完整落地。【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考