Skip to content

fix: replace host bind mounts with named volumes for SF auth - #8

Open
gambe94 wants to merge 4 commits into
mainfrom
feat/devcontainer-auth
Open

fix: replace host bind mounts with named volumes for SF auth#8
gambe94 wants to merge 4 commits into
mainfrom
feat/devcontainer-auth

Conversation

@gambe94

@gambe94 gambe94 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

What & why

This devcontainer used to consume the published sf-devcontainer image and inherit
Salesforce org auth by bind-mounting the host's ~/.sf/~/.sfdx directly into the
container. That's broken: the SF CLI's stored auth tokens are encrypted with a key from
the host OS's keychain (macOS Keychain here), which a Linux container can't read — every
org fails sf org list with AuthDecryptError even though the credential files are
right there.

~/.sf/~/.sfdx are now named Docker volumes instead
(sf-develop-demo-sf-config/sf-develop-demo-sfdx-config). Auth is created and read
entirely inside the container via sf org login web (or non-interactively via
SF_AUTH_URL), and persists across rebuilds without ever touching host-encrypted files.
Trade-off: a host login is no longer automatically visible in the container (and vice
versa) — you log in once per container, matching how sf-docker-images' own reference
devcontainer and Docker Compose recipe already work.

.devcontainer/post-create.sh and README.md are updated to match: error messages now
point at logging in inside the container instead of "either side persists to both,"
and the README's auth section explains the volume model and why the old bind mount
broke.

Also included from earlier in this branch: dropped the local Dockerfile/.zshrc/
.p10k.zsh — this devcontainer now pulls gforceinnovation/sf-devcontainer:latest
directly instead of building locally, so it always matches the maintained image
(Starship, not Oh My Zsh/Powerlevel10k — see sf-docker-images PR #15 for that switch).

Test plan

  • Dev Containers: Rebuild Container — new named volumes created, container starts
  • sf org login web --alias <org> --set-default inside the container succeeds
  • sf org list shows the org Connected, no AuthDecryptError
  • Dev Containers: Rebuild Container again (no volume removal) — auth still present,
    post-create.sh passes without re-authenticating
  • Manual verification pending on the reporter's machine (this is the fix for a bug
    they hit directly)

🤖 Generated with Claude Code

gambe94 and others added 4 commits August 6, 2026 19:03
Opening the devcontainer left you unauthenticated every rebuild, because
credentials live in the host ~/.sf and were not shared. That directory is now
bind-mounted, so auth is inherited and a login on either side persists to both.

post-create.sh resolves target-org — falling back to a committed alias, since
.sf/ is gitignored and never reaches a fresh clone — and fails loudly with the
exact login command when the org is not authorized, rather than leaving a shell
that looks fine and cannot deploy.

Also deletes .devcontainer/{Dockerfile,.zshrc,.p10k.zsh}: devcontainer.json
pulls the published image, so none of them were ever built, and the Dockerfile
claimed Node 20 where the image ships 24.
The README still documented the local Dockerfile/.zshrc/.p10k.zsh build that
28a3a3c deleted, describing Ubuntu 22.04, Node 18, and Oh My Zsh +
Powerlevel10k — none of which the published gforceinnovation/sf-devcontainer
image ships. It also never mentioned that Salesforce auth is now inherited
from the host via the ~/.sf bind mount. Rewrite it to match what a developer
actually gets: no local build to edit or rebuild, real aliases from the
image's .zshrc, host-inherited auth (including the production-org exposure
caveat), and what post-create.sh does when the target org isn't authorized.

Also replace the GitHub Actions "::error::" annotation syntax in
post-create.sh with a plain message — it only renders in Actions; here it
just printed literally. Exit code and behavior are unchanged.
Commits 28a3a3c and ac4f998 bind-mounted the host's ~/.sf and ~/.sfdx into
the devcontainer on the premise that Salesforce CLI auth would be inherited.
That premise is false: macOS stores the CLI's encryption key in the OS
Keychain, not in ~/.sf/~/.sfdx. Bind-mounting carries the encrypted auth
files but not the key, and a Linux container cannot reach the host Keychain
- every org fails `sf org list` with AuthDecryptError. Measured on this
machine with both bind-mounted orgs.

Fix: named Docker volumes instead of host bind mounts, so auth is created
and decrypted entirely inside the container (container-side key) and
persists across rebuilds via the volume, independent of host credentials.
This is the same pattern already proven in sf-docker-images'
examples/docker-compose.yml and its own .devcontainer/devcontainer.json.

Also:
- Fix /commandhistory being root-owned (container runs as vscode, UID 1000)
  by chowning it in postCreateCommand before npm install runs.
- Rewrite post-create.sh to stop checking for/describing a host mount, and
  to point users at logging in inside the container on auth failure.
- Rewrite the README auth section (drop the "container can use every host
  org, including production" warning - that's no longer true) and add a
  version-accuracy note that the documented Starship shell requires
  sf-devcontainer >= 3.0.0, which is unreleased; :latest is still v2.0.0
  with Oh My Zsh + Powerlevel10k.

Do not revert this to a bind mount - it will silently break every org's
auth again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
post-create.sh trusted `sf config get target-org`, but that resolves the
host's /workspace/.sf/config.json — the workspace is bind-mounted, so it
reads as sf's *Local* config and outranks the Global config in the named
auth volume. The result was a script that resolved an alias only the host
could reach and failed even when the container was correctly authorized,
while the SF_DEFAULT_ORG_ALIAS fallback never fired (it was gated on the
target-org being *unset*, which never happens on a developer machine).

- Verify the resolved org with `sf org display` before accepting it; fall
  back to SF_DEFAULT_ORG_ALIAS when it is missing OR unauthorized.
- Pin the alias with `sf config set target-org --global` only after it
  verifies, so it lands in the volume instead of writing container state
  back into the host's bind-mounted .sf/.
- Repoint SF_DEFAULT_ORG_ALIAS from the github1 scratch org (expires
  2026-08-15) to the gabor_dev Dev Hub, so the fallback does not rot.
- Drop --set-default from the documented login commands for the same
  write-back reason, and replace `sf org display --verbose | jq .sfdxAuthUrl`
  with `sf org auth show-sfdx-auth-url` — recent sf releases redact the
  former.

Verified against gforceinnovation/sf-devcontainer:latest with the exact
mounts from devcontainer.json: authorized org passes (exit 0), fresh clone
with no local .sf passes (exit 0), empty auth volume still fails loudly
(exit 1), and the host's .sf/config.json and target-org are left untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant