mcpField
S
setup-devcontainer

setup-devcontainer

github.com/ken-guruクラウド・DevOps146 回インストール6 日前に更新
リポジトリ

setup-devcontainer について

テンプレートから、リポジトリまたはローカルチェックアウト用の共有 Dev Container 設定を生成するスキルです。ワークスペースの検出、既存コンテナの確認、SSH 設定や外向き通信の制限といった選択を案内します。

主な機能

  1. 01テンプレートから .devcontainer/ を生成
  2. 02リポジトリとワークスペースの状態検出
  3. 03既存設定とコンテナの確認
  4. 04SSH 自動化の任意設定
  5. 05外向き通信の許可リスト設定

活用シーン

  1. 01リポジトリ用 Dev Container の作成
  2. 02GitHub 接続前のローカルチェックアウト設定
  3. 03再オープン前の残存コンテナ確認
  4. 04開発コンテナの外向き通信制限

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ken-guru/skills --skill setup-devcontainer

インストール後、Claude Code で/setup-devcontainerを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

CONTEXT.md
docs/
adding-a-backing-service.md
adding-firewall-later.md
adding-ssh-later.md
connecting-local-checkout.md
docs/adr/
0001-per-container-ssh-keys.md
0002-collapse-tool-container-isolation.md
0003-manual-first-deploy-key-registration.md
0004-digest-pin-base-image-reject-docker-hardened-images.md
0005-network-egress-firewall-and-network-manifest.md
0006-capability-drop-reject-readonly-rootfs-and-seccomp.md
0007-agent-trust-hub-audit-response.md
scripts/
check-skill-sync-targets.sh
patch-if-absent.sh
patch-json-array-if-absent.sh
render-devcontainer.sh
test-devcontainer.sh
test-firewall-lib.sh
test-integration.sh
test-network-manifest-domains.sh
test-patch-if-absent.sh
test-patch-json-array-if-absent.sh
verify-devcontainer.sh
scripts/lib/
render-lib.sh
templates/
allowed-domains.local.txt
bash-env.sh
devcontainer.json
devcontainer.with-ssh.json
Dockerfile
env.baseline.example

README(原文)English

プロジェクトの README より

この README の日本語訳はまだありません。以下は英語の原文です。日本語の紹介はページ上部をご覧ください。

Setup Devcontainer

Generates .devcontainer/ from the templates in templates/: one Shared Container — a single devcontainer.json and Dockerfile — that every selected AI CLI installs into via its own independent skill, run separately after this one. See CONTEXT.md for this skill's vocabulary (Shared Container, Local Checkout, Shared Checkout, Scaffold, CLI Skill, Own-Block Contract, Project-Owned Block, Capability Seam, Network Manifest, Project Mounts).

This skill owns the container definition (devcontainer.json, Dockerfile) and the shared parts of post-create.sh/post-start.sh/README.md entirely — no CLI skill ever edits them directly, except for one narrow, explicit exception (the Capability Seam, runArgs, used by setup-codex-devcontainer).

1. Resolve the workspace root, then detect the target repo

Before anything else, make sure you're standing in the directory VS Code will actually open for this devcontainer. Every check and file write from here on — including the stale-container check in step 2 — trusts $(pwd); getting this wrong lets a stale container or a misplaced .devcontainer/ slip through silently instead of being caught.

git rev-parse --is-inside-work-tree 2>/dev/null
  • Fails: not a git repo yet (the Local Checkout case below, before git init). Skip this check entirely and continue to detecting the repo.

  • Succeeds: compare the current checkout's root against the repo's main working tree:

    git rev-parse --show-toplevel
    git rev-parse --git-common-dir
    

    --git-common-dir is the shared .git directory; its parent is the main checkout's root, which is what differs in a linked worktree (each worktree has its own top level but shares this common dir).

    • Parent of --git-common-dir matches --show-toplevel: already at the repo root, or in a plain subdirectory of it. cd to --show-toplevel and continue — every $(pwd) reference later in this skill is now correct, no further changes needed.
    • They differ: this is a linked git worktree — a separate checkout directory sharing this repo's history, common in isolated per-task agent sessions. Don't guess which one VS Code will open (worktree-per-devcontainer is a legitimate setup too). Stop and ask: "This looks like a git worktree, not necessarily the folder you'll open in VS Code for the devcontainer. Which folder will that be? If it's not this one, re-run this skill from there instead — the checks below (stale containers, existing config) only ever look at the current directory."
git remote get-url origin

Parse owner/repo from it (works for both [email protected]:owner/repo.git and https://github.com/owner/repo forms) — this is {{REPO_SLUG}}. {{REPO_NAME}} is the repo part alone, used in the volume names.

If there's no origin remote yet, ask one question: "Does a GitHub repository already exist for this project? If yes, name it (owner/repo) — the container's GH_TOKEN will need access to it. If no, or you're not ready to connect yet, this becomes a local-only Local Checkout — connectable to GitHub later (see 'Connecting a Local Checkout to a real GitHub repo')."

  • Named an existing repo: resolve {{REPO_SLUG}}/{{REPO_NAME}} from it, same as the existing-origin case above.
  • Local Checkout: {{REPO_SLUG}} stays empty. {{REPO_NAME}} falls back to the local working directory's basename (basename "$(pwd)"), sanitized to Docker's naming rules (lowercase, invalid characters replaced with -) — state the resolved name back to the user as part of your summary; don't decide it silently. Also ask, as a separate yes/no question: "Initialize this workspace with git init?" Record the answer as {{LOCAL_CHECKOUT_GIT_INIT}} ("true"/"false"). If yes, resolve {{GIT_DEFAULT_BRANCH}} from the host's git config --global init.defaultBranch — substitute it as an empty string if the host has none configured; always substitute this placeholder with something, even "".

Done when you have {{REPO_SLUG}} (possibly empty) and {{REPO_NAME}} — plus, if Local Checkout, {{LOCAL_CHECKOUT_GIT_INIT}} and, if that's yes, {{GIT_DEFAULT_BRANCH}}.

2. Discover existing setup

test -f .devcontainer/devcontainer.json && echo "devcontainer already exists"
  • Already exists: this repo already has a Shared Container from a prior run. Nothing to regenerate — CLI installs are handled entirely by the setup-<tool>-devcontainer skills, and adding SSH to an existing devcontainer is Adding SSH to a tool later. Stop here unless the user is specifically asking to reconfigure the base devcontainer itself.

    Exception: if devcontainer.json sets workspaceFolder without a matching workspaceMount, it predates the fix below and will fail to mount the repo at all. Patch it in place (add "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached" next to workspaceFolder) and check for a stale container from the earlier failed attempt (see below) — a restart alone won't pick up the fix, since the mount is baked in at container creation.

    Exception: if devcontainer.json has no containerEnv.BASH_ENV entry, it predates the fix that makes GH_TOKEN (and the rest of .devcontainer/.env) visible to an AI CLI's own tool calls — without it, GH_TOKEN only ever reaches the three lifecycle scripts (postCreate/postStart/ postAttach), never a gh command an agent runs itself, even right after a token was added and the container rebuilt. Patch it in place: add .devcontainer/bash-env.sh (copy of templates/bash-env.sh, chmod +x), add "containerEnv": {"BASH_ENV": "/workspace/.devcontainer/bash-env.sh"} to devcontainer.json, and add the devcontainer-env-load block from templates/post-create-base.sh to .devcontainer/post-create.sh if it isn't already there. Needs Dev Containers: Rebuild Container to take effect.

    Warning, not an exception (nothing here auto-patches this one): if devcontainer.json has no build.target field, it predates the Baseline Containment hardening (--cap-drop=ALL plus a small --cap-add allowlist, unconditionally; the opt-in network firewall; the digest-pinned, multi-stage Dockerfile) — the whole container still runs with Docker's full default capability set and an unpinned, single-stage image, regardless of anything the setup-<tool>-devcontainer skills or the "later" docs above do to it. There's no automated backfill for this (a deliberate choice, not an oversight — see the skill's own git history for the reasoning). Tell the user plainly: to actually get the hardening, delete .devcontainer/ and regenerate from scratch, rerunning any setup-<tool>-devcontainer skills and redoing the SSH/firewall answers afterward.

  • Doesn't exist: fresh setup, continue to step 3.

Also check for a leftover container from an unrelated prior setup of this same workspace folder — the Dev Containers CLI labels containers by devcontainer.local_folder=<absolute workspace path>, independent of what the current config says, so a stale container can survive even after its old config was deleted or never committed:

docker ps -a --filter "label=devcontainer.local_folder=$(pwd)" --format '{{.ID}} {{.Image}}'

If this returns anything, warn the user before they reopen: a container built from a different setup won't have the vscode user this setup expects, and reopening fails with a cryptic unable to find user vscode: no matching entries in passwd file. Offer to remove it (docker rm -f <id>), but don't remove it without asking. Skip this check entirely if docker isn't installed or isn't running.

The same applies to a container built before the workspaceMount fix above: it fails on reopen with chdir to cwd ("/workspace") set in config.json failed: no such file or directory, followed by postCreateCommand ... failed with exit code 127 (the script can't even be reached because the shell never got into /workspace). The container built successfully, so this isn't caught until reopen. Same remedy — remove it with docker rm -f <id> after confirming with the user — since the mount config is fixed at creation and won't update on restart.

3. Ask SSH and firewall layers

Skip the SSH question entirely if {{REPO_SLUG}} (step 1) is empty — Local Checkout, with no GitHub repo yet to register deploy/signing keys against; tell the user why: "SSH layer isn't available yet — this repo has no GitHub connection; add it once one exists (see 'Connecting a Local Checkout to a real GitHub repo')."

Otherwise ask: does this repo need agent-driven git push and signed commits? (Adds deploy-key/signing-key automation — one shared key pair for the whole container.) Addable later without redoing anything already generated (see Adding SSH to a tool later).

Then, independent of the SSH answer and regardless of whether {{REPO_SLUG}} is empty (a Local Checkout gains just as much from restricting outbound reach as a GitHub-connected repo), ask: does this repo want to restrict this container's outbound network to an allowlist? (A default-deny iptables+ipset firewall — every installed CLI Skill's own required hosts, plus npm/GitHub, are allowed automatically via the Network Manifest; everything else is blocked. Trade-off: an AI CLI or a tool it shells out to that needs to reach some other host — a package registry mirror, a project's own API — needs that host added to .devcontainer/allowed-domains.local.txt first, or the request fails closed instead of silently succeeding.) Recommended default: yes, for any container running an AI CLI with --dangerously-skip-permissions/YOLO-mode automation, since it bounds what a misbehaving or compromised process can reach; declining costs nothing today. Addable later without redoing anything already generated (see Adding the firewall layer later).

4. Resolve remaining placeholders

  • {{REPO_SLUG}}, {{REPO_NAME}}, {{LOCAL_CHECKOUT_GIT_INIT}}, {{GIT_DEFAULT_BRANCH}} — from step 1.
  • {{GIT_EMAIL_DEFAULT}}, {{GIT_NAME_DEFAULT}} — run git config --global user.email and git config --global user.name on the host. If either is unset, don't invent a default.

5. Generate the devcontainer

  • .devcontainer/Dockerfile ← templates/Dockerfile, copied verbatim (no placeholders) — always this one file, firewall answer or not. It's multi-stage: base (the plain image) and firewall (extends base with the iptables/ipset/iproute2/dnsutils/aggregate packages the firewall scripts need, plus a sudo rule scoped to exactly those two scripts' paths). Which stage actually gets built is devcontainer.json's build.target, below — not which file was copied — so the Dockerfile itself never needs swapping.

  • .devcontainer/bash-env.sh ← templates/bash-env.sh, copied verbatim (no placeholders), chmod +x. Loads .devcontainer/.env; referenced as BASH_ENV below so every non-interactive shell in the container (an AI CLI's own tool calls, which run bash -c ... and never source ~/.bashrc) sees GH_TOKEN and friends, not just the three lifecycle scripts.

  • .devcontainer/network-manifest.json ← templates/network-manifest.json, copied verbatim (no placeholders). This is the Network Manifest (see CONTEXT.md): an object keyed by CLI name, each value holding a networkAllowlist array of {host, purpose, source} entries. Seeded here with the universal baseline every container needs regardless of which CLI Skills end up installed — registry.npmjs.org, github.com, api.github.com for npm/git/gh, plus archive.ubuntu.com/ports.ubuntu.com so ad hoc sudo apt-get install <tool> mid-session (issue #291 story 19) keeps working under the firewall too, not just with it declined; without these two, apt-get update silently fails to refresh the package index and only already-cached packages stay installable — filed under a "baseline" key rather than a fake CLI name, since none of this is owned by any one CLI. Each CLI Skill later adds its own keyed entry (a separate concern, not generated here). Generated unconditionally, firewall opt-in or not — a CLI Skill installed later always gets its own entry recorded here, and the firewall (whether added now or later) picks it up automatically with no extra step.

  • .devcontainer/network-manifest-domains.sh ← templates/network-manifest-domains.sh, copied verbatim (no placeholders), chmod +x. The Network Manifest's jq-based derivation helper: given a manifest file, prints one deduplicated host per line across every keyed entry's networkAllowlist. Also generated unconditionally; sourced by firewall-lib.sh (below) when the firewall layer is present, otherwise present but unused.

  • .devcontainer/project-mounts.local.json ← templates/project-mounts.local.json, copied verbatim — an empty JSON array. This is the Project Mounts seam (see CONTEXT.md): a project-owned file, parallel to allowed-domains.local.txt below, never touched again by any skill after this. Generated unconditionally (mounts are orthogonal to the SSH/firewall questions). Add this project's own devcontainer.json mounts entries here (raw mount strings, e.g. "source=my-project-data,target=/var/lib/data,type=volume", matching the format of the mounts entry devcontainer.json already carries), then fold them into devcontainer.json with:

    scripts/patch-json-array-if-absent.sh .devcontainer/devcontainer.json .mounts .devcontainer/project-mounts.local.json
    

    Safe to re-run after every edit — already-present entries are left untouched. Needs Dev Containers: Rebuild Container to take effect, since mounts is set at container creation, not read again on a plain restart.

  • Add .devcontainer/project-mounts.local.json to .gitignore if it isn't already ignored (parallel to .devcontainer/.env above).

  • .devcontainer/devcontainer.json ← templates/devcontainer.json (or templates/devcontainer.with-ssh.json if step 3's SSH answer was yes), substitute {{REPO_NAME}}. The runArgs field starts pre-populated with the unconditional Baseline Containment capability-drop entries (--cap-drop=ALL --cap-add=CHOWN --cap-add=DAC_OVERRIDE --cap-add=FOWNER --cap-add=SETUID --cap-add=SETGID — the last two are sudo itself: its binary is setuid-root, but it still calls setresuid()/setresgid() internally to fully switch identity, which needs those capabilities in the bounding set regardless of the setuid bit, or every sudo invocation fails outright, including the firewall's own) — not gated behind any setup question — and this is also the Capability Seam, the one extension point a CLI skill (today, only setup-codex-devcontainer) may idempotently append its own entries to on top. containerEnv.BASH_ENV points at bash-env.sh above — this is what makes GH_TOKEN visible to an AI CLI's own tool calls, not just the lifecycle scripts. initializeCommand invokes the generated .devcontainer/initialize.sh (below) via sh, not bash — it runs on the host, before the container exists, and the script's own content has no bash dependency to preserve. mounts starts with just the shared config volume; a project's own entries come from project-mounts.local.json (below), folded in separately, never hand-added here. If step 3's firewall answer was yes, also patch in the firewall's own capability grant (--cap-add=NET_ADMIN --cap-add=NET_RAW — iptables/ipset need them), through the same patch-json-array-if-absent.sh primitive the Capability Seam itself is built on — not the Capability Seam extension point itself, since this is the base skill writing to runArgs it already owns, not a CLI skill appending on top — applied at generation time rather than via a later CLI-skill patch, since this is conditional on the base skill's own setup question, not on any particular CLI, and switch build.target from "base" to "firewall" so the Dockerfile's firewall stage (above) actually gets built:

    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 — generated by scripts/render-devcontainer.sh, which assembles the base skeleton (git identity, Local Checkout git-init, the shared install_cli/chown_config_volume function definitions, and the SSH block pair if enabled), chmod'ing the result +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}}"]
    

    Verify with scripts/verify-devcontainer.sh, passing the exact same flags (minus --out/--initialize-out, plus --file/--initialize-file).

    This skeleton is what every CLI skill later appends its own install block to. The SSH block never fails the build — an under-scoped or missing GH_TOKEN (or unset DEVCONTAINER_HOST) degrades to skipping the rest of the SSH setup, recording why in ~/.ssh/.ssh-setup-skipped.

    The same command also writes .devcontainer/initialize.sh (chmod +x'd) — the same --initialize-out flag above, unconditional, no --ssh/--firewall variants. This is initializeCommand's target (see devcontainer.json above): today's exact .env-bootstrap behavior, ported into an appendable script file rather than an inline one-liner, so a project needing its own host-side initialization step (something that must run before the container exists, so it can't live in post-create.sh) can append its own Project-Owned Block the same way it already can for 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, copied verbatim, chmod +x. Empty skeleton — each CLI skill appends its own skill-sync block here if the user opts into it for that tool, and (below) the base skill appends the firewall invocation if step 3's firewall answer was yes.

  • .devcontainer/post-attach.sh ← templates/post-attach.sh, substituted, chmod +x.

  • .devcontainer/.env.example ← templates/env.baseline.example, substituted.

  • .devcontainer/README.md ← templates/README.baseline.md, substituted. Its ## Installed CLI Tools heading is deliberately the file's last section, empty — every CLI skill's own bullet is a plain append to end-of-file, landing there without needing to search for a position.

  • Add .devcontainer/.env to .gitignore if it isn't already ignored.

If step 3's SSH answer was yes:

  • .devcontainer/.env.example gets templates/env.ssh-block.example appended, idempotently, and its GH_TOKEN comment gets: 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
    
  • .devcontainer/README.md gets templates/README.ssh-block.md, substituted ({{REPO_NAME}}, {{REPO_SLUG}}) into a scratch copy first — unlike every other substituted template above, this one is also an input to another command rather than a file written directly, so substitute before patching, not after — inserted before ## Installed CLI Tools, not appended to end-of-file — this keeps that section as the file's last one no matter when SSH is added — and the baseline template's closing "SSH deploy key and signing key automation — Not set up here" section is deleted (superseded by the real section):

    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"
    

If step 3's firewall answer was yes:

  • .devcontainer/init-firewall.sh ← templates/init-firewall.sh, copied verbatim (no placeholders), chmod +x. .devcontainer/refresh-allowlist.sh ← templates/refresh-allowlist.sh, copied verbatim, chmod +x. .devcontainer/firewall-lib.sh ← templates/firewall-lib.sh, copied verbatim, chmod +x — sourced (not executed directly) by the two scripts above, the single place both build their domain list from (the Network Manifest, via network-manifest-domains.sh, plus allowed-domains.local.txt below), so they can't independently drift the way two hand-duplicated lists would.

  • .devcontainer/allowed-domains.local.txt ← templates/allowed-domains.local.txt, copied verbatim — empty except for its explanatory header comment. This repo's own project-specific network hosts go here; never touched again by any skill after this.

  • Add .devcontainer/allowed-domains.local.txt to .gitignore if it isn't already ignored (parallel to .devcontainer/.env above).

  • .devcontainer/post-start.sh gets templates/post-start-firewall-block.sh appended, idempotently:

    scripts/patch-if-absent.sh append .devcontainer/post-start.sh "# --- Network egress firewall ---" templates/post-start-firewall-block.sh
    
  • .devcontainer/README.md gets templates/README.firewall-block.md inserted before ## Installed CLI Tools, and the baseline template's closing "Network egress firewall automation — Not set up here" section is deleted (superseded by the real section):

    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"
    

Done when every file above exists, devcontainer.json parses as valid JSON with runArgs containing exactly the baseline capability-drop entries, plus --cap-add=NET_ADMIN and --cap-add=NET_RAW if step 3's firewall answer was yes (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 declined, or ... 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 accepted), build.target is "base" if declined or "firewall" if accepted (jq -e '.build.target == "base"' .devcontainer/devcontainer.json or ... == "firewall"'), no {{...}} placeholder remains in any written file (grep -rn '{{' .devcontainer/), and post-create.sh/initialize.sh pass scripts/verify-devcontainer.sh for the exact flags they were rendered with (--file/--initialize-file, plus --project-mounts-file .devcontainer/project-mounts.local.json to confirm it still matches the template verbatim).

6. Report next steps

Tell the user, adapted to whether SSH/the firewall are present:

  1. Install Docker Desktop and the Dev Containers VS Code extension.
  2. Copy .devcontainer/.env.example to .devcontainer/.env and fill in GH_TOKEN{{, and DEVCONTAINER_HOST (run hostname) if SSH is present}}. {{On a re-run against an existing container: if .devcontainer/.env still contains github_pat_your_token_here or your-hostname-here, replace it with the real value or clear it — an older .env.example shipped those placeholders as values, and they're exported as real credentials.}}
  3. Reopen the repo in the container (Dev Containers: Reopen in Container).
  4. Run whichever setup-<tool>-devcontainer skill(s) you want, to add AI CLIs.
  5. {{If SSH is present: on attach, post-attach.sh prints setup instructions for whichever of the deploy key and signing key aren't registered yet (the deploy key may already be handled if GH_TOKEN had Administration access) — follow them, dismissing each with the touch command it gives you.}}
  6. {{If the firewall is present: this container's outbound network now default-denies everything except the Network Manifest's allowlist. Add any of this project's own hosts to .devcontainer/allowed-domains.local.txt before they're needed — a host missing from there, the Network Manifest, or GitHub's/npm's own ranges is unreachable by design.}}
  7. If this project needs its own devcontainer.json mounts (a database data directory, a persistent history volume, etc.), add them to .devcontainer/project-mounts.local.json and fold them in with scripts/patch-json-array-if-absent.sh .devcontainer/devcontainer.json .mounts .devcontainer/project-mounts.local.json, then rebuild. If it needs its own host-side initialization step, append it to .devcontainer/initialize.sh the same way you'd append to post-create.sh/post-start.sh. If it needs an in-container backing service (e.g. PostgreSQL), see Adding a backing service.

Adding SSH to a tool later

For a repo that already has a devcontainer and now needs agent-driven git push / signed commits. See docs/adding-ssh-later.md.

Adding the firewall layer later

For a repo that already has a devcontainer and now wants the opt-in network egress firewall. See docs/adding-firewall-later.md.

Adding a backing service

For a project that needs a backing service (e.g. PostgreSQL) running inside the Shared Container, without reintroducing Docker Compose. See docs/adding-a-backing-service.md.

Connecting a Local Checkout to a real GitHub repo

For a repo generated as Local Checkout (step 1) that now has a real GitHub repository to push to. Needs no skill-level regeneration — see docs/connecting-local-checkout.md.

コメント