Skip to content

fix(entrypoint): docker exec inherits wrong HOME (image ENV); auto-set per runtime user #2

@abitofhelp

Description

@abitofhelp

Summary

When developers use docker exec against a running dev_containers instance (rootful, gosu path), HOME is inherited from the image's ENV HOME=... directive (/home/dev) rather than from the runtime user's actual home (/home/$(whoami)). This forces every developer to remember export HOME=/home/$(whoami) before any command that touches XDG paths (logs, state, config), defeating the dev_containers value proposition: builds should be reproducible without manual environmental fix-up.

This is a v1.0.x release blocker (adafmt v1.0.0 depends on dev_containers behaving correctly across organizations).

Owner-locked context (2026-05-07)

"We do not want devs to have to remember to do something manually. Defeats the purpose of the container."

— dev_containers is actively used by developers in other organizations. The container's value proposition is reproducibility across hosts AND a uniform developer experience without per-host manual workarounds.

What's broken

Symptom

docker exec -u mike -w /workspace/adafmt dev-container-ada-system-1 \
  bash -lc 'echo $HOME; ./bin/adafmt check src/'
# Output:
# /home/dev                                           <-- wrong; should be /home/mike
# Log: /home/dev/.local/state/adafmt/log/...          <-- log path follows wrong HOME
# Text_Log open failed: Text_Log sink: open failed for path
#   '/home/dev/.local/state/adafmt/log/...'
#   (directory not writable or path invalid).         <-- mike can't write to dev's home

Why

  • The Dockerfile chain establishes the build-time identity as ${USERNAME} (default dev), with HOME=/home/dev baked into image ENV.
  • The entrypoint.sh adapts the runtime identity at container launch (rootful path: useradd matching host UID, then exec gosu "${HOST_USER}" "$@"). This sets HOME=${TARGET_HOME} for the CMD-spawned process.
  • docker exec does NOT go through the entrypoint. It spawns processes directly as the requested user via Docker's execution model, inheriting the image's ENV (still HOME=/home/dev).
  • Login shells (bash -l, zsh -l) inherit HOME from the docker-set environment, which trumps /etc/passwd. So even though useradd correctly recorded mike's home as /home/mike in /etc/passwd, the login shell still sees HOME=/home/dev and writes user state there.

Net effect

  • Tools that use ~/.config, ~/.local/state, ~/.cache (XDG dirs) write to /home/dev/... paths but the runtime user mike (UID 501) doesn't own /home/dev (the dev user UID 1000 does, with chmod 750). Permission denied.
  • Worked around in adafmt session 2026-05-07 with export HOME=/home/$(whoami) per-invocation — fine as a tactical workaround but not a long-term contract.

Required fix

Make docker exec get the right HOME automatically, without per-invocation export.

Two approach options

(A) /etc/profile.d/ snippet (recommended)

Add a small, idempotent script that runs on every login shell to fix HOME based on whoami:

# /etc/profile.d/host-home.sh
# Auto-correct HOME for docker-exec-spawned login shells.
# The Dockerfile's ENV HOME=/home/dev is correct for the build-time
# user but wrong for the runtime host-UID-matched user.
expected_home="/home/$(whoami)"
if [ -d "$expected_home" ] && [ "$HOME" != "$expected_home" ]; then
    export HOME="$expected_home"
fi

Created via Dockerfile.system RUN step; chmod 644. Runs from any login shell (bash -l, zsh -l, sh -l). Idempotent (no-op if HOME already correct).

(B) Remove ENV HOME=... from Dockerfile

Don't bake HOME into image ENV; let /etc/passwd lookup populate HOME for login shells. Simpler but might break other paths that rely on ${HOME} resolving at image-build time (e.g., ENV PATH=${HOME}/.local/bin:...).

Recommendation: (A). Less risk to existing path resolution.

Implementation scope

  • Edit Dockerfile.system (and Dockerfile for symmetry) to add the profile.d RUN step.
  • Edit entrypoint.sh only if there's a path where the entrypoint's own export is wrong. Likely no change needed — the entrypoint's HOME export remains correct for entrypoint-driven processes.
  • No changes needed for docker run (entrypoint path) — that path already works.
  • Mostly affects the docker exec path.

Verification

After image rebuild + redeploy, the following should "just work" without manual export:

docker exec -u $(whoami) -w /workspace/adafmt dev-container-ada-system-1 \
  bash -lc 'echo $HOME; ./bin/adafmt check src/'
# Expected:
# /home/$(whoami)
# Log: /home/$(whoami)/.local/state/adafmt/log/...
# Files: ... processed, ... ok ...

Tests:

  • Test-1: docker exec -u <host_user> spawned bash login shell reports $HOME == /home/<host_user>.
  • Test-2: docker exec -u <host_user> spawned bash login shell can write to ~/.local/state/.
  • Test-3: regression — docker run (entrypoint path) still gets $HOME == /home/<host_user> (no break).
  • Test-4: docker exec as root (UID 0, kubectl-style runAsUser=0) doesn't break — root has its own /root home.
  • Test-5: rootless runtime (entrypoint stays as UID 0) — docker exec -u 0 and unsetenv'd HOME still resolves to TARGET_HOME via the existing entrypoint logic.

Out of scope

  • macOS Docker Desktop host file-system permissions on bind mounts (separate concern; not affected).
  • Windows host docker exec (uses different path conventions; out of scope until Windows 11 platform expansion lands per adafmt#68).

References

  • adafmt session 2026-05-07: discovered during bin/adafmt check runs inside adast as mike; tactical workaround was per-invocation export HOME=/home/mike.
  • entrypoint.sh lines 91-94 (HOST_USER / TARGET_HOME computation).
  • entrypoint.sh lines 270-281 (rootful HOME export + gosu exec).
  • Dockerfile.system existing RUN step pattern (alr config --global --set toolchain.assistant false) — the new profile.d RUN goes near here.

Sequencing

  • Targeted for the next dev_containers release (1.0.x or 1.1.0 — owner's call on version slot).
  • Adafmt v1.0.0 release readiness depends on this landing.
  • Pre-phase ask + GPT review for the profile.d snippet wording (cover edge cases in §Verification).
  • Edit + commit + push dev_containers.
  • Image rebuild + redeploy on each developer's host.
  • Revert any tactical export HOME=... workarounds in adafmt's docs / scripts (none committed today; just chat-level).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions