This repo ships no application toolchain. It keeps two per-language Dev Container definitions under catalog/snippets/devcontainer/ as a reference for fleet code repos, each carrying one toolchain, extension surface, and postCreateCommand. The mechanics below (SSH commit signing, bind mounts, and gh auth) apply to any repo that uses them.
| Devcontainer | Image | Toolchain |
|---|---|---|
catalog/snippets/devcontainer/dotnet/devcontainer.json |
mcr.microsoft.com/devcontainers/dotnet:1-10.0 |
.NET 10 SDK |
catalog/snippets/devcontainer/python/devcontainer.json |
mcr.microsoft.com/devcontainers/python:1-3.14-bookworm |
Python 3.14 + version-pinned uv |
In a repo that carries one of these definitions, install the Dev Containers extension and pick Reopen in Container.
Prerequisite: complete host setup first. Without git config, an SSH key, and the allowed-signers file on the host, the devcontainer cannot sign commits.
| Component | Source | Purpose |
|---|---|---|
gh CLI |
ghcr.io/devcontainers/features/github-cli:1 |
Issue/PR/release management from inside the container |
| Common utilities | ghcr.io/devcontainers/features/common-utils:2 |
bash, curl, wget, sudo, vscode user |
| VS Code extensions | customizations.vscode.extensions in each devcontainer.json |
Mirrors the matching workspace's recommendations so the container has the same tooling |
The .NET container additionally ships the csharpier/dotnet-outdated local tools (restored by catalog/snippets/devcontainer/dotnet/post-create.sh). The Python container additionally ships uv (installed by catalog/snippets/devcontainer/python/post-create.sh from a version-pinned URL) and pre-syncs the Python package venv where one is present.
Each devcontainer's extension list and the matching workspace's recommendations are kept identical, so when you add an extension to one, add it to the other.
The host SSH key, allowed-signers file, and gh config directory are mounted into the container so commits sign correctly and gh is pre-authenticated when the host stores its gh token in a file (~/.config/gh/hosts.yml). Hosts that store the token in macOS Keychain or Linux libsecret will need an in-container gh auth login instead. See gh credential store below for the full picture.
| Host path | Container path | Mode | Purpose |
|---|---|---|---|
~/.ssh/id_ed25519.pub |
/home/vscode/.ssh/id_ed25519.pub |
read-only | Public half of the SSH key. The private key never enters the container, since SSH agent forwarding handles signing. |
~/.config/git/allowed_signers |
/home/vscode/.config/git/allowed_signers |
read-only | Maps your email to your public key so git verify-commit and git log --show-signature work inside the container. |
~/.config/gh |
/home/vscode/.config/gh |
read-write | gh CLI auth state shared with the host. See gh credential store below. |
VS Code Dev Containers automatically copies your host ~/.gitconfig into the container at startup, so user.name, user.email, user.signingkey, gpg.format, and commit.gpgsign propagate without an explicit mount.
The SSH agent is forwarded automatically by the Dev Containers extension via SSH_AUTH_SOCK, so signing works as long as the agent on the host has your key loaded.
Both devcontainer.json files run two scripts at well-defined points:
onCreateCommand-sudo install -d -m 700 -o vscode -g vscode /home/vscode/.ssh. On macOS hosts the bind-mount surfaces/home/vscode/.sshas root-owned, which would block writes from inside the container (e.g.ghupdatingknown_hosts). This chown fixes it. Idempotent on Linux and WSL2.postCreateCommand- language-specific:- .NET:
catalog/snippets/devcontainer/dotnet/post-create.sh, which runsdotnet tool restore(csharpier, dotnet-outdated). - Python:
catalog/snippets/devcontainer/python/post-create.sh, which installs the pinneduvand pre-syncs the Python package if present.
- .NET:
Re-runs of either are idempotent. No git hooks are installed by default.
To force them to run again after editing a script: VS Code -> Command Palette -> Dev Containers: Rebuild Container.
gh auth login writes its token to either a file or an OS credential store. Which one depends on your host:
| Host | Default token storage |
|---|---|
| Linux | libsecret (gnome-keyring) when available, otherwise file |
| WSL2 | file (no native credential store) |
| macOS | macOS Keychain |
The bind-mount of ~/.config/gh covers the file case. If your host stores the token in Keychain or libsecret, the bind-mount carries the rest of gh config but not the token, so the container will report "no authentication" until you either:
- Re-run
gh auth logininside the container (writes a file token to the mounted directory), or - Skip in-container
ghand run those commands on the host instead.
The file-token path is slightly less secure than Keychain/libsecret because it's plaintext on disk inside ~/.config/gh/hosts.yml. For most contributors that's an acceptable trade-off, and if it isn't, use option 2.
After Reopen in Container finishes, run the language-appropriate checks.
Both containers. Verify SSH signing and gh:
gh auth status # logged in as you
git -c gpg.format=ssh commit -S --allow-empty -m "verify-signing"
git log --show-signature -1 # "Good 'git' signature for ...".NET container (in a repo that ships .NET):
dotnet --version # 10.x
which uv # nothing - uv intentionally absent
dotnet build # 0 warnings, 0 errors
dotnet test # tests passPython container (in a repo that ships Python):
uv --version # uv 0.x
which dotnet # nothing - dotnet intentionally absent
cd <package> && uv sync && uv run pytest # tests passIf git -c gpg.format=ssh commit -S errors with signing failed: no allowed signers, the bind-mount of allowed_signers is missing or the file on the host is empty, so re-run the snippet in host setup.
Permission denied writing to ~/.ssh/known_hosts in the container. The onCreateCommand should have chowned ~/.ssh to vscode. Rebuild the container, and if it persists, open a shell and run the same sudo install -d -m 700 -o vscode -g vscode ~/.ssh manually.
git commit fails with "no SSH agent socket". VS Code Dev Containers forwards SSH_AUTH_SOCK automatically, but only if the host has ssh-agent running with at least one key. Run ssh-add -l on the host first, and if it says "could not open a connection to your authentication agent", start the agent (see host setup).
uv not on PATH after rebuild (Python container). The post-create installer adds ~/.local/bin to PATH via the user shell init scripts, which take effect on next shell. Either re-open the integrated terminal or source ~/.bashrc.
Container builds but extensions don't auto-install. Make sure VS Code is using the Dev Containers extension (not "Remote - SSH" or "Remote - Tunnels"). The extension auto-install is keyed on customizations.vscode.extensions and only Dev Containers honors that.
Wrong-language work in the wrong container. The .NET container has no uv and no Python extensions, and the Python container has no dotnet SDK and no C# extensions. This is intentional, so use the matching container rather than installing the missing toolchain ad hoc.