关于 setup-devcontainer
根据模板为仓库或本地工作区生成共享 Dev Container 配置。该技能会指导工作区检测、现有容器检查,以及 SSH 配置和出站网络限制等选项的决策。
主要功能
- 01根据模板生成 .devcontainer/
- 02检测仓库与工作区状态
- 03检查现有配置和容器
- 04配置可选的 SSH 自动化
- 05配置出站网络允许列表
适用场景
- 01为仓库创建 Dev Container
- 02在连接 GitHub 前配置本地工作区
- 03重新打开前检查遗留容器
- 04限制开发容器的出站网络访问
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ken-guru/ skills --skill setup-devcontainer安装后在 Claude Code 中输入/setup-devcontainer确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CONTEXT.mddocs/adding-a-backing-service.mdadding-firewall-later.mdadding-ssh-later.mdconnecting-local-checkout.mddocs/adr/0001-per-container-ssh-keys.md0002-collapse-tool-container-isolation.md0003-manual-first-deploy-key-registration.md0004-digest-pin-base-image-reject-docker-hardened-images.md0005-network-egress-firewall-and-network-manifest.md0006-capability-drop-reject-readonly-rootfs-and-seccomp.md0007-agent-trust-hub-audit-response.mdscripts/check-skill-sync-targets.shpatch-if-absent.shpatch-json-array-if-absent.shrender-devcontainer.shtest-devcontainer.shtest-firewall-lib.shtest-integration.shtest-network-manifest-domains.shtest-patch-if-absent.shtest-patch-json-array-if-absent.shverify-devcontainer.shscripts/lib/render-lib.shtemplates/allowed-domains.local.txtbash-env.shdevcontainer.jsondevcontainer.with-ssh.jsonDockerfileenv.baseline.example详细介绍
设置 Devcontainer
根据 templates/ 中的模板生成 .devcontainer/:
一个共享容器——单个 devcontainer.json 和 Dockerfile——供每个选定的 AI CLI 通过各自独立的技能安装到其中;每项技能都在本技能之后单独运行。有关本技能的术语(共享容器、本地检出、共享检出、脚手架、CLI 技能、自有块约定、项目自有块、能力接缝、网络清单、项目挂载),请参阅 CONTEXT.md。
本技能完全负责容器定义(devcontainer.json、Dockerfile)以及 post-create.sh/post-start.sh/README.md 中的共享部分——任何 CLI 技能都不会直接编辑它们,只有一个范围狭窄且明确的例外(能力接缝 runArgs,由 setup-codex-devcontainer 使用)。
1. 解析工作区根目录,然后检测目标仓库
首先,确保你位于 VS Code 实际会为此开发容器打开的目录中。从现在起,所有检查和文件写入(包括第 2 步中的过时容器检查)都以 $(pwd) 为准;如果目录不对,过时容器或放错位置的 .devcontainer/ 就会悄无声息地漏过检查。
git rev-parse --is-inside-work-tree 2>/dev/null
-
失败:当前还不是 Git 仓库(下文“本地检出”部分中
git init之前的情况)。完全跳过此检查,继续检测仓库。 -
成功:将当前检出的根目录与仓库的主工作树进行比较:
git rev-parse --show-toplevel git rev-parse --git-common-dir--git-common-dir是共享的.git目录;其父目录是主检出的根目录。链接工作树中的情况不同(每个工作树都有各自的顶层目录,但共享此公共目录)。--git-common-dir的父目录与--show-toplevel相同:你已经位于仓库根目录,或其普通子目录中。切换到--show-toplevel并继续——此技能中之后所有$(pwd)引用现在都正确,无需进一步更改。- 两者不同:这是一个链接的 Git 工作树——与此仓库共享历史记录的独立检出目录,常用于隔离的逐任务 Agent 会话。不要猜测 VS Code 会打开哪个目录(每个开发容器对应一个工作树也是合理的设置)。停止并询问:“这看起来是一个 Git 工作树,不一定是你会在 VS Code 中为开发容器打开的文件夹。你会打开哪个文件夹?如果不是这个,请从那个文件夹重新运行此技能——以下检查(过时容器、现有配置)只会检查当前目录。”
git remote get-url origin
从中解析出 owner/repo(适用于 [email protected]:owner/repo.git 和 https://github.com/owner/repo 两种形式)——这就是 {{REPO_SLUG}}。{{REPO_NAME}} 仅为其中的 repo 部分,用于卷名称。
如果还没有 origin 远程仓库,问一个问题:“此项目是否已有 GitHub 仓库?如果有,请提供其名称(owner/repo)——容器的 GH_TOKEN 需要有权访问它。如果没有,或者你暂时还不准备连接,则将其设为仅本地的本地检出——之后可以连接到 GitHub(参见‘将本地检出连接到真实的 GitHub 仓库’)。”
- 提供了现有仓库的名称:按照上文处理现有
origin的方式解析{{REPO_SLUG}}/{{REPO_NAME}}。 - 本地检出:
{{REPO_SLUG}}保持为空。{{REPO_NAME}}回退为本地工作目录的基本名称(basename "$(pwd)"),并按 Docker 的命名规则进行清理(转为小写,将无效字符替换为-)——在总结中向用户说明解析出的名称;不要悄悄自行决定。还要单独询问一个是/否问题:“是否使用git init初始化此工作区?”将答案记录为{{LOCAL_CHECKOUT_GIT_INIT}}("true"/"false")。如果回答是,则从主机的git config --global init.defaultBranch解析{{GIT_DEFAULT_BRANCH}}——如果主机未配置此项,则替换为空字符串;始终要用某个值替换此占位符,即使是""也可以。
完成条件:已获取 {{REPO_SLUG}}(可能为空)和 {{REPO_NAME}};此外,如果是本地检出,还需获取 {{LOCAL_CHECKOUT_GIT_INIT}};若答案为是,还需获取 {{GIT_DEFAULT_BRANCH}}。
2. 检查现有设置
test -f .devcontainer/devcontainer.json && echo "devcontainer already exists"
-
已存在:此仓库已有之前运行时创建的共享容器。无需重新生成——CLI 安装完全由 setup-
<tool>-devcontainer 技能处理;在现有开发容器中添加 SSH,请参阅稍后为工具添加 SSH。除非用户明确要求重新配置基础开发容器本身,否则到此为止。 Exception:如果devcontainer.json设置了workspaceFolder,却没有对应的workspaceMount,说明它早于下面的修复,且会导致仓库完全无法挂载。请直接就地修补(在workspaceFolder旁添加"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached"),并检查是否有此前失败尝试留下的过期容器(见下文)——仅重启无法应用修复,因为挂载配置在创建容器时就已固定。Exception:如果
devcontainer.json中没有containerEnv.BASH_ENV条目,说明它早于这项修复:该修复会让GH_TOKEN(以及.devcontainer/.env中的其余内容)对 AI CLI 自身发起的工具调用可见——如果没有这项配置,GH_TOKEN只会传递给三个生命周期脚本(postCreate/postStart/postAttach),永远不会传递给 Agent 自己运行的gh命令,即使刚添加了令牌并重建了容器也是如此。请直接就地修补:添加.devcontainer/bash-env.sh(复制自 templates/bash-env.sh,并执行 chmod +x),在devcontainer.json中添加"containerEnv": {"BASH_ENV": "/workspace/.devcontainer/bash-env.sh"},并将 templates/post-create-base.sh 中的devcontainer-env-load代码块添加到.devcontainer/post-create.sh(如果其中尚无该代码块)。需要执行 Dev Containers: Rebuild Container 才能生效。Warning,而非 exception(这里不会自动修补这一项):如果
devcontainer.json中没有build.target字段,说明它早于 Baseline Containment 加固(始终启用--cap-drop=ALL,并使用小型--cap-add允许列表;可选择启用网络防火墙;使用固定摘要的多阶段Dockerfile)——整个容器仍会使用 Docker 默认的完整 capability 集和未固定版本的单阶段镜像,无论上述 setup-<tool>-devcontainer 技能或“后续”文档对其做了什么。此项没有自动回填机制(这是有意的选择,并非疏忽——原因请参阅该技能自身的 git 历史记录)。请明确告知用户:要真正启用加固,必须删除.devcontainer/并从头重新生成,然后重新运行所有setup-<tool>-devcontainer技能,并再次回答 SSH/防火墙相关问题。 -
不存在:全新设置,继续执行第 3 步。
还要检查是否有此工作区文件夹之前其他设置遗留的容器——Dev Containers CLI 会根据 devcontainer.local_folder=<absolute workspace path> 为容器添加标签,与当前配置无关,因此即使旧配置已删除或从未提交,过期容器也可能残留:
docker ps -a --filter "label=devcontainer.local_folder=$(pwd)" --format '{{.ID}} {{.Image}}'
如果有任何返回结果,请在用户重新打开前发出警告:使用不同设置构建的容器不会包含此设置所需的 vscode 用户,重新打开时会失败,并显示难以理解的错误 unable to find user vscode: no matching entries in passwd file。可以提议移除它(docker rm -f <id>),但未经询问,不要移除。如果未安装 docker 或其未运行,则完全跳过此检查。
同样的情况也适用于在上述 workspaceMount 修复之前构建的容器:重新打开时会失败,显示 chdir to cwd ("/workspace") set in config.json failed: no such file or directory,随后显示 postCreateCommand ... failed with exit code 127(由于 shell 无法进入 /workspace,脚本甚至无法运行)。容器已成功构建,因此直到重新打开时才会发现问题。解决方法相同——先征得用户确认,再用 docker rm -f <id> 移除,因为挂载配置在创建时就已固定,重启不会更新。
3. 询问 SSH 和防火墙层
如果 {{REPO_SLUG}}(第 1 步)为空,请完全跳过 SSH 问题——本地检出尚未关联 GitHub 仓库,无法为其注册部署密钥/签名密钥;请向用户说明原因:“SSH 层目前不可用——此仓库尚未连接到 GitHub;连接后再添加(参见‘将本地检出连接到真实的 GitHub 仓库’)。”
否则,询问:此仓库是否需要由 Agent 驱动 git push 并签署提交?(添加部署密钥/签名密钥自动化——整个容器共用一对密钥。)以后可以添加,无需重新生成已生成的任何内容(参见稍后为工具添加 SSH)。
然后,无论 SSH 问题的答案是什么,也无论 {{REPO_SLUG}} 是否为空(限制出站访问对本地检出和已连接 GitHub 的仓库同样有益),都要询问:此仓库是否希望将此容器的出站网络限制为允许列表?(默认拒绝的 iptables+ipset 防火墙——通过网络清单自动允许每个已安装 CLI 技能所需的主机,以及 npm/GitHub;其他所有访问均会被阻止。权衡之处:如果某个 AI CLI 或其调用的工具需要访问其他主机(例如软件包注册表镜像或项目自己的 API),必须先将该主机添加到 .devcontainer/allowed-domains.local.txt,否则请求会被默认拒绝,而不是悄然成功。)推荐默认值:是,适用于运行带有 --dangerously-skip-permissions/YOLO 模式自动化功能的 AI CLI 的任何容器,因为这会限制行为异常或遭到入侵的进程能够访问的范围;现在不启用也没有任何损失。以后可以添加,无需重新生成已生成的任何内容(参见稍后添加防火墙层)。
4. 解析剩余占位符
{{REPO_SLUG}}、{{REPO_NAME}}、{{LOCAL_CHECKOUT_GIT_INIT}}、{{GIT_DEFAULT_BRANCH}}— 来自第 1 步。{{GIT_EMAIL_DEFAULT}}、{{GIT_NAME_DEFAULT}}— 在主机上运行git config --global user.email和git config --global user.name。如果其中任一项未设置,请勿自行编造默认值。
5. 生成 devcontainer
-
.devcontainer/Dockerfile← templates/Dockerfile,原样复制(无占位符)——无论是否启用防火墙,始终使用这一个文件。它采用多阶段构建:base(纯净镜像)和firewall(在base基础上扩展,加入防火墙脚本所需的iptables/ipset/iproute2/dnsutils/aggregate软件包,以及一条权限范围恰好限定为这两个脚本路径的sudo规则)。实际构建哪个阶段由下方devcontainer.json的build.target决定——而非复制了哪个文件——因此 Dockerfile 本身无需替换。 -
.devcontainer/bash-env.sh← templates/bash-env.sh,原样复制(无占位符),并执行 chmod +x。它会加载.devcontainer/.env;下方将其设为BASH_ENV,这样容器中的每个非交互式 shell(包括 AI CLI 自己的工具调用,这些调用会运行bash -c ...,且从不加载~/.bashrc)都能获取GH_TOKEN等环境变量,而不仅仅是三个生命周期脚本。 -
.devcontainer/network-manifest.json← templates/network-manifest.json,原样复制(无占位符)。这是网络清单(见 CONTEXT.md):一个以 CLI 名称为键的对象,每个值都包含一个由{host, purpose, source}条目组成的networkAllowlist数组。此处预先填入每个容器都需要的通用基线条目,无论最终安装了哪些 CLI 技能——用于 npm/git/gh 的registry.npmjs.org、github.com、api.github.com,以及archive.ubuntu.com/ports.ubuntu.com,以便在会话中临时执行sudo apt-get install <tool>(issue #291 story 19)时,即使未启用防火墙也能正常工作;若缺少后两个域名,apt-get update会静默地无法刷新软件包索引,之后只能安装缓存中已有的软件包。这些条目归入"baseline"键,而不是虚构的 CLI 名称,因为它们并不归任何一个 CLI 所有。之后每个 CLI 技能都会添加自己的键值条目(这是独立事项,不在此处生成)。无论是否选择启用防火墙,都会无条件生成——之后安装的 CLI 技能始终会在此记录自己的条目,而防火墙(无论现在还是之后添加)都会自动读取,无需额外步骤。 -
.devcontainer/network-manifest-domains.sh← templates/network-manifest-domains.sh,原样复制 (无占位符),并执行 chmod +x。它是网络清单的jq派生辅助脚本:给定清单文件,会输出所有键值条目中networkAllowlist的主机名,每行一个,且去重。此文件也会无条件生成;存在防火墙层时由firewall-lib.sh(见下文)加载,否则保留但不使用。 -
.devcontainer/project-mounts.local.json← templates/project-mounts.local.json,原样复制——一个空 JSON 数组。这是项目挂载接口(见 CONTEXT.md):一个由项目所有的文件,与下方的allowed-domains.local.txt并列,此后任何技能都不会再触碰它。无条件生成(挂载与 SSH/防火墙选项无关)。在此处添加本项目自己的devcontainer.jsonmounts条目(原始挂载字符串,例如"source=my-project-data,target=/var/lib/data,type=volume",格式与devcontainer.json已包含的mounts条目相同),然后使用以下命令将它们合并到devcontainer.json中:scripts/patch-json-array-if-absent.sh .devcontainer/devcontainer.json .mounts .devcontainer/project-mounts.local.json每次编辑后都可以安全地重新运行——已存在的条目会保持不变。需要执行 Dev Containers: Rebuild Container 才能生效,因为
mounts在创建容器时设置,不会在普通重启时重新读取。 -
如果
.devcontainer/project-mounts.local.json尚未被忽略,请将其添加到.gitignore(与上文的.devcontainer/.env同理)。 -
.devcontainer/devcontainer.json← templates/devcontainer.json (如果第 3 步对 SSH 的回答为“是”,则使用 templates/devcontainer.with-ssh.json),替换{{REPO_NAME}}。runArgs字段预先填入了无条件的 Baseline Containment capability-drop 条目(--cap-drop=ALL --cap-add=CHOWN --cap-add=DAC_OVERRIDE --cap-add=FOWNER --cap-add=SETUID --cap-add=SETGID——最后两项是sudo本身所需:它的二进制文件带有 setuid-root 权限,但它仍会在内部调用setresuid()/setresgid()以彻底切换身份,因此无论 setuid 位如何,这些能力都必须在 bounding set 中,否则每次调用sudo都会直接失败,包括防火墙自身的调用)——这些条目不受任何设置问题控制;这也是 Capability Seam,即唯一的扩展点,CLI 技能(目前只有setup-codex-devcontainer)可以在其基础上幂等地追加自己的条目。containerEnv.BASH_ENV指向上文的bash-env.sh——这使GH_TOKEN对 AI CLI 自身的工具调用可见,而不只是对生命周期脚本可见。initializeCommand通过sh调用生成的.devcontainer/initialize.sh(见下文),而不是bash——它在主机上运行,此时容器尚未创建;脚本本身不依赖 bash。mounts初始仅包含共享配置卷;项目自己的条目来自project-mounts.local.json(见下文),单独合并,绝不手动添加到此处。如果第 3 步对防火墙的回答为“是”,还需通过 Capability Seam 本身所基于的同一个patch-json-array-if-absent.sh基元,加入防火墙自身所需的能力授权(--cap-add=NET_ADMIN --cap-add=NET_RAW——iptables/ipset需要这些能力)。不要使用 Capability Seam 扩展点本身,因为这是基础技能在写入自己拥有的runArgs,而不是 CLI 技能在其基础上追加内容;应在生成时应用,而不是通过之后的 CLI 技能补丁应用,因为这取决于基础技能自身的设置问题,而非任何特定 CLI;并将build.target从"base"改为"firewall",这样 Dockerfile 的防火墙阶段(见上文)才会实际构建:scripts/patch-json-array-if-absent.sh .devcontainer/devcontainer.json .runArgs templates/firewall-capability-entries.json jq '.build.target = "firewall"' .devcontainer/devcontainer.json > .devcontainer/devcontainer.json.tmp \ && mv .devcontainer/devcontainer.json.tmp .devcontainer/devcontainer.json -
.devcontainer/post-create.sh——由 scripts/render-devcontainer.sh 生成,该脚本会组装基础骨架(git 身份信息、Local Checkout 的 git-init、共享的install_cli/chown_config_volume函数定义,以及启用时的 SSH 区块对),并将生成文件的权限改为 +x:scripts/render-devcontainer.sh \ --repo-name "{{REPO_NAME}}" --repo-slug "{{REPO_SLUG}}" \ --out .devcontainer/post-create.sh \ --initialize-out .devcontainer/initialize.sh \ [--ssh] \ [--git-email-default "<value>"] [--git-name-default "<value>"] \ [--local-checkout-git-init "{{LOCAL_CHECKOUT_GIT_INIT}}"] [--git-default-branch "{{GIT_DEFAULT_BRANCH}}"]使用 scripts/verify-devcontainer.sh 验证,传入完全相同的标志(去掉
--out/--initialize-out,添加--file/--initialize-file)。这个骨架是每个 CLI 技能之后追加自身安装区块的基础。SSH 区块不会导致构建失败——如果
GH_TOKEN权限不足或缺失(或DEVCONTAINER_HOST未设置),则会跳过其余 SSH 设置,并在~/.ssh/.ssh-setup-skipped中记录原因。 同一命令还会写入.devcontainer/initialize.sh(并执行 chmod +x)——使用上面的同一个--initialize-out标志,无条件执行,不提供--ssh/--firewall变体。这是initializeCommand的目标(见上文的devcontainer.json):将当前精确的.env引导 行为移植到可追加的脚本文件中,而非内联单行命令,这样需要自行添加主机端初始化步骤的项目(该步骤必须在容器 创建前运行,因此不能放在post-create.sh中)就可以像为post-create.sh/post-start.sh添加内容那样, 自行追加一个 项目所有者区块:scripts/patch-if-absent.sh append .devcontainer/initialize.sh "# --- Project: <name> ---" <block-file> -
.devcontainer/post-start.sh← templates/post-start-base.sh, 原样复制,并执行 chmod +x。空骨架——如果用户选择为某个 CLI 工具启用同步,各 CLI 技能会在此追加自己的技能同步区块; 此外(见下文),如果第 3 步中用户对防火墙的回答为是,基础技能会在此追加防火墙调用。 -
.devcontainer/post-attach.sh← templates/post-attach.sh, 替换内容,并执行 chmod +x。 -
.devcontainer/.env.example← templates/env.baseline.example,替换内容。 -
.devcontainer/README.md← templates/README.baseline.md,替换内容。其## Installed CLI Tools标题是文件的最后一个章节,且有意留空——每个 CLI 技能都只需将自己的条目直接追加到文件末尾, 无需查找插入位置。 -
如果
.devcontainer/.env尚未加入忽略列表,则将其添加到.gitignore。
如果第 3 步中用户对 SSH 的回答为是:
-
将 templates/env.ssh-block.example 幂等地追加到
.devcontainer/.env.example,并将其GH_TOKEN注释改为:Deploy-key registration is manual by default (see the SSH setup prompt after first attach) and needs no extra permission. Optional: grant this repo's Administration (read/write) permission too if you'd rather the deploy key auto-register itself — plus whatever else you use gh for.scripts/patch-if-absent.sh append .devcontainer/.env.example "# Your host machine's hostname — used to label the SSH deploy/signing keys so" templates/env.ssh-block.example -
将 templates/README.ssh-block.md 的内容 替换(
{{REPO_NAME}}、{{REPO_SLUG}})到临时副本中,再写入.devcontainer/README.md——与上面所有其他需要替换内容的模板不同, 此模板还会作为另一个命令的输入,而不是直接写入的文件,因此应在修补之前完成替换,而不是之后——将其插入到## Installed CLI Tools之前,而非追加到文件末尾——这样无论何时添加 SSH,该章节始终是文件的最后一个章节——并删除基线模板末尾的 “SSH deploy key and signing key automation — Not set up here”章节(由实际章节取代):scripts/patch-if-absent.sh insert-before .devcontainer/README.md "## SSH deploy key and signing key" "## Installed CLI Tools" <substituted-copy-of-templates/README.ssh-block.md> scripts/patch-if-absent.sh delete-section .devcontainer/README.md "## SSH deploy key and signing key automation"
如果第 3 步中用户对防火墙的回答为是:
-
.devcontainer/init-firewall.sh← templates/init-firewall.sh,原样复制(不含占位符),并执行 chmod +x。.devcontainer/refresh-allowlist.sh← templates/refresh-allowlist.sh,原样复制,并执行 chmod +x。.devcontainer/firewall-lib.sh← templates/firewall-lib.sh,原样复制, 并执行 chmod +x——由上面的两个脚本加载(而非直接执行),是两者构建域名列表的唯一来源 (通过network-manifest-domains.sh获取网络清单,再加上下面的allowed-domains.local.txt), 因此不会像两份手动复制的列表那样各自出现偏差。 -
.devcontainer/allowed-domains.local.txt← templates/allowed-domains.local.txt,原样复制——文件为空, 只有说明性头部注释。此仓库特有的项目网络主机名应添加到这里;之后任何技能都不会再改动此文件。 -
如果
.devcontainer/allowed-domains.local.txt尚未加入.gitignore,请将其加入 (与上面的.devcontainer/.env一样)。 -
将 templates/post-start-firewall-block.sh 以幂等方式追加到
.devcontainer/post-start.sh:scripts/patch-if-absent.sh append .devcontainer/post-start.sh "# --- Network egress firewall ---" templates/post-start-firewall-block.sh -
将 templates/README.firewall-block.md 插入
.devcontainer/README.md的## Installed CLI Tools之前,并删除基线模板末尾的 “Network egress firewall automation — Not set up here”部分(已由实际章节取代):scripts/patch-if-absent.sh insert-before .devcontainer/README.md "## Network egress firewall" "## Installed CLI Tools" templates/README.firewall-block.md scripts/patch-if-absent.sh delete-section .devcontainer/README.md "## Network egress firewall automation"
完成条件:以上所有文件均存在;devcontainer.json 可解析为有效 JSON,且 runArgs
包含且仅包含基线能力移除项;如果第 3 步的防火墙问题回答为是,还需包含
--cap-add=NET_ADMIN 和 --cap-add=NET_RAW
(拒绝时使用
jq -e '.runArgs | sort == ["--cap-add=CHOWN","--cap-add=DAC_OVERRIDE","--cap-add=FOWNER","--cap-add=SETGID","--cap-add=SETUID","--cap-drop=ALL"]' .devcontainer/devcontainer.json
进行验证,接受时使用
... sort == ["--cap-add=CHOWN","--cap-add=DAC_OVERRIDE","--cap-add=FOWNER","--cap-add=NET_ADMIN","--cap-add=NET_RAW","--cap-add=SETGID","--cap-add=SETUID","--cap-drop=ALL"]' .devcontainer/devcontainer.json
进行验证);拒绝时 build.target 为 "base",接受时为 "firewall"
(使用 jq -e '.build.target == "base"' .devcontainer/devcontainer.json 或
... == "firewall"' 进行验证);所有写入的文件中均不含 {{...}} 占位符
(使用 grep -rn '{{' .devcontainer/ 检查);并且 post-create.sh/initialize.sh 通过
scripts/verify-devcontainer.sh 的验证,且使用其生成时的确切标志
(--file/--initialize-file,并添加 --project-mounts-file .devcontainer/project-mounts.local.json,以确认其仍与模板逐字一致)。
6. 报告后续步骤
根据是否有 SSH/防火墙,向用户说明:
- 安装 Docker Desktop 和 VS Code 扩展 Dev Containers。
- 将
.devcontainer/.env.example复制为.devcontainer/.env,并填写GH_TOKEN{{,如果有 SSH,还要填写DEVCONTAINER_HOST(运行hostname)}}。{{如果是对现有容器重新运行:如果.devcontainer/.env仍包含github_pat_your_token_here或your-hostname-here,请替换为真实值或清除它们——较旧版本提供的.env.example将这些占位符作为实际值,其中的内容会作为真实凭据导出。}} - 在容器中重新打开仓库(Dev Containers: Reopen in Container)。
- 运行所需的
setup-<tool>-devcontainer技能,以添加 AI CLI。 - {{如果有 SSH:连接时,
post-attach.sh会打印尚未注册的部署密钥和签名密钥的设置说明(如果GH_TOKEN具有 Administration 权限,部署密钥可能已设置)——按说明操作,并使用给出的touch命令逐一关闭提示。}} - {{如果有防火墙:此容器的出站网络现在默认拒绝所有连接,仅允许网络清单中的许可列表。请在需要之前,将本项目自己的主机添加到
.devcontainer/allowed-domains.local.txt——按照设计,未列于该文件、网络清单或 GitHub/npm 自身网段中的主机均无法访问。}} - 如果本项目需要自己的
devcontainer.json挂载项(数据库数据目录、持久化历史记录卷等),请将其添加到.devcontainer/project-mounts.local.json,然后使用scripts/patch-json-array-if-absent.sh .devcontainer/devcontainer.json .mounts .devcontainer/project-mounts.local.json合并,再重新构建。如果需要自己的主机端初始化步骤,请像向post-create.sh/post-start.sh追加内容那样,将其追加到.devcontainer/initialize.sh。如果需要容器内的后端服务(例如 PostgreSQL),请参阅添加后端服务。
稍后为工具添加 SSH
适用于已有 devcontainer、现在需要由 Agent 驱动 git push/签名提交的仓库。请参阅 docs/adding-ssh-later.md。
稍后添加防火墙层
适用于已有 devcontainer、现在需要启用可选网络出口防火墙的仓库。请参阅 docs/adding-firewall-later.md。
添加后端服务
适用于需要在共享容器内运行后端服务(例如 PostgreSQL),且不重新引入 Docker Compose 的项目。请参阅 docs/adding-a-backing-service.md。
将本地检出连接到真实的 GitHub 仓库
适用于作为本地检出(步骤 1)生成、现在已有真实 GitHub 仓库可供推送的仓库。无需重新生成技能级内容——请参阅 docs/connecting-local-checkout.md。