Skip to content

Commit 80cae2f

Browse files
authored
devcontainer: bridge host known_hosts so push works under the CLI (#125)
Mirrors the existing host-gitconfig handling. Without this, git push from inside a CLI-launched devcontainer fails with "Host key verification failed" the first time it talks to github.com \xe2\x80\x94 the base image leaves ~/.ssh empty and SSH refuses unknown fingerprints by default. The fix is the same shape as the gitconfig bridge: - initialize.sh snapshots ~/.ssh/known_hosts into .git-plumbing/host-known-hosts (empty file if absent). - post-start.sh copies that into $HOME/.ssh/known_hosts only when the in-container file is missing or empty, creating ~/.ssh mode 700 first. - .gitignore covers the new runtime artifact. - README and CLAUDE.md updated alongside. VS Code\xe2\x80\x99s Dev Containers extension already bridges known_hosts itself, so the empty-check naturally lets it win when it\xe2\x80\x99s involved. CI\xe2\x80\x99s `devcontainer build` never reaches postStart, so the script is a no-op there.
1 parent 9ba2f2b commit 80cae2f

5 files changed

Lines changed: 53 additions & 6 deletions

File tree

.claude/CLAUDE.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,13 @@ the git path symlink).
6262
The host signs with **SSH** (`gpg.format = ssh`, `commit.gpgsign = true`, no explicit
6363
`user.signingkey``gpg.ssh.defaultKeyCommand` shells out to `ssh-add -L`). That makes two
6464
things load-bearing inside the container: a usable ssh-agent socket the in-container `git` can
65-
reach, and the host's `~/.gitconfig`. VS Code's Dev Containers extension supplies both
66-
automatically — it forwards the host ssh-agent through the VS Code Server's own SSH tunnel (a
67-
per-user socket published by the server process, *not* Docker Desktop's magic socket — that
68-
mount isn't even present in extension-launched containers) and copies the host gitconfig
69-
between `postCreate` and `postStart`. The `devcontainer` CLI does neither. The fix is three
70-
additive pieces:
65+
reach, and the host's `~/.gitconfig`. Pushing the resulting signed commit then needs a third —
66+
the host's `~/.ssh/known_hosts`, or SSH refuses the unknown github.com fingerprint. VS Code's
67+
Dev Containers extension supplies all three automatically — it forwards the host ssh-agent
68+
through the VS Code Server's own SSH tunnel (a per-user socket published by the server process,
69+
*not* Docker Desktop's magic socket — that mount isn't even present in extension-launched
70+
containers), and bridges the host gitconfig + known_hosts between `postCreate` and `postStart`.
71+
The `devcontainer` CLI does none of that. The fix is four additive pieces:
7172

7273
- **SSH agent**`devcontainer.json` binds Docker Desktop's magic socket
7374
`/run/host-services/ssh-auth.sock` (Desktop's documented mechanism for exposing the host's
@@ -94,6 +95,15 @@ additive pieces:
9495
reaches postStart, so the file is absent there and the script is a clean no-op. Same
9596
lifecycle/buildx-safety story as `host-git-common-path` and `host-timezone` — gitignored,
9697
regenerated every `up`, anchored by the tracked `.git-plumbing/README.md`.
98+
- **`~/.ssh/known_hosts`** — same shape as the gitconfig copy. `initialize.sh`
99+
snapshots the host's `~/.ssh/known_hosts` to `.git-plumbing/host-known-hosts`;
100+
`post-start.sh` installs it into `$HOME/.ssh/known_hosts` only if that file
101+
is missing or empty (creating `~/.ssh` mode 700 first). Without it, `git
102+
push` from inside the CLI-launched container fails with "Host key
103+
verification failed" on first contact with github.com — the base image's
104+
`~/.ssh` is empty and SSH refuses unknown fingerprints by default. VS Code
105+
bridges known_hosts itself, so the empty-check leaves that path alone.
106+
Same gitignored / regenerated-every-`up` lifecycle as the gitconfig snapshot.
97107

98108
`gpg.ssh.allowedSignersFile` in the copied gitconfig points to a host-only path that doesn't
99109
exist in the container. Irrelevant: git only reads it for `git verify-commit`, not for

.devcontainer/.git-plumbing/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ image. The files are gitignored and written on every `devcontainer up` by
1818
the `devcontainer` CLI path; VS Code's Dev Containers extension copies
1919
it for itself). See ".devcontainer signed commits under CLI" in
2020
`.claude/CLAUDE.md`.
21+
- `host-known-hosts` — snapshot of the host's `~/.ssh/known_hosts`.
22+
`post-start.sh` installs it into the container only when
23+
`~/.ssh/known_hosts` is empty, so `git push` from inside the CLI-
24+
launched container doesn't trip "Host key verification failed" on first
25+
contact with github.com. Same VS-Code-wins behaviour as the gitconfig
26+
copy.
2127

2228
This directory exists in git (via this README) so the Dockerfile's
2329
`COPY .devcontainer/.git-plumbing/ …` step always finds a source — buildx

.devcontainer/initialize.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ link="$here/.host-git-common"
5858
pathfile="$here/.git-plumbing/host-git-common-path"
5959
tzfile="$here/.git-plumbing/host-timezone"
6060
gitconfigfile="$here/.git-plumbing/host-gitconfig"
61+
knownhostsfile="$here/.git-plumbing/host-known-hosts"
6162

6263
# The .git-plumbing dir is tracked (via its README), so it normally exists
6364
# already; mkdir -p covers stray cases like a manual deletion without
@@ -119,6 +120,19 @@ else
119120
: >"$gitconfigfile"
120121
fi
121122

123+
# Snapshot host ~/.ssh/known_hosts the same way. Without it, `git push` from
124+
# inside a CLI-launched container fails with "Host key verification failed"
125+
# the first time it talks to github.com — the base image's $HOME/.ssh is
126+
# empty and SSH refuses unknown fingerprints by default. The host's
127+
# known_hosts is the right trust set to carry (same flavor as the gitconfig
128+
# — both are the user's existing trust state, neither secret). Empty file
129+
# if absent so the `[ -s ... ]` guard in post-start.sh stays a clean no-op.
130+
if [ -r "$HOME/.ssh/known_hosts" ]; then
131+
cp "$HOME/.ssh/known_hosts" "$knownhostsfile"
132+
else
133+
: >"$knownhostsfile"
134+
fi
135+
122136
# Pre-create the magic ssh-agent socket placeholder on hosts where Docker
123137
# Desktop isn't intercepting it (CI runners, plain Docker on Linux). Docker
124138
# Desktop auto-forwards the host ssh-agent at /run/host-services/ssh-auth.sock

.devcontainer/post-start.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@ if [ ! -s "$HOME/.gitconfig" ] && [ -s "$src" ]; then
1212
cp "$src" "$HOME/.gitconfig"
1313
fi
1414

15+
# Install host ~/.ssh/known_hosts the same way. Lets `git push` from inside
16+
# the CLI-launched container succeed first try; without it the user hits
17+
# "Host key verification failed" because the base image has no known_hosts
18+
# and SSH refuses unknown fingerprints by default. VS Code's Dev Containers
19+
# extension already bridges known_hosts when it's involved, so the empty-
20+
# check leaves that path alone.
21+
known_src="$here/.git-plumbing/host-known-hosts"
22+
known_dst="$HOME/.ssh/known_hosts"
23+
if [ ! -s "$known_dst" ] && [ -s "$known_src" ]; then
24+
# ~/.ssh must be mode 700 or SSH ignores it; known_hosts at 644 is fine.
25+
mkdir -p "$HOME/.ssh"
26+
chmod 700 "$HOME/.ssh"
27+
cp "$known_src" "$known_dst"
28+
chmod 644 "$known_dst"
29+
fi
30+
1531
# Docker Desktop bind-mounts the magic ssh-agent socket root-owned mode 660,
1632
# so the non-root remoteUser can't connect until we re-own it.
1733
sock=/run/host-services/ssh-auth.sock

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ target/
6464
/.devcontainer/.git-plumbing/host-git-common-path
6565
/.devcontainer/.git-plumbing/host-timezone
6666
/.devcontainer/.git-plumbing/host-gitconfig
67+
/.devcontainer/.git-plumbing/host-known-hosts
6768

6869
# ── Claude Code ───────────────────────────────────────────────────────────────────────────────────
6970
# settings.local.json holds per-developer overrides — never shared

0 commit comments

Comments
 (0)