Bootstrapping dotfiles with chezmoi in a DevContainer

chezmoi manages dotfiles as a templated, version-controlled source that renders per machine — ideal for DevContainers, where the same dotfiles must adapt to an ephemeral container user without leaking secrets into the image. This page bootstraps chezmoi during container creation so your shell, editor, and Git config land correctly on first attach. It extends the Automating Dotfiles Sync Across Containers cluster.

Prerequisites

  • A dotfiles repository managed by chezmoi (chezmoi init already run locally)
  • Network access to clone the dotfiles repo at create time (or a vendored copy)
  • A non-root remoteUser

How-To Steps

  1. Install and apply chezmoi in postCreateCommand, pointing it at your dotfiles repo:

    {
      "image": "mcr.microsoft.com/devcontainers/base:bookworm@sha256:2c5a...",
      "postCreateCommand": "sh -c \"$(curl -fsLS get.chezmoi.io)\" -- init --apply yourgithubuser",
      "remoteUser": "vscode"
    }
    

    Verify: chezmoi managed lists your tracked files and ~/.zshrc exists.

  2. Keep secrets out of the image by sourcing them from the host environment at render time. In a chezmoi template (dot_netrc.tmpl):

    machine api.internal login {{ env "API_USER" }} password {{ env "API_TOKEN" }}
    

    Pass the host values through without baking them in:

    { "remoteEnv": { "API_USER": "${localEnv:API_USER}", "API_TOKEN": "${localEnv:API_TOKEN}" } }
    
  3. Make re-apply idempotent so a rebuild or restart re-renders cleanly:

    { "postStartCommand": "chezmoi apply --force" }
    
  4. Confirm a clean render with no diff after apply:

    chezmoi apply && chezmoi diff   # diff should be empty
    

Common Pitfalls

SymptomRoot CauseRemediation
Secrets end up in the imageTokens hardcoded in dotfilesUse chezmoi templates reading remoteEnv values
Dotfiles overwrite each rebuild but lose editsEditing rendered files, not the sourceEdit via chezmoi edit; never touch rendered targets
Apply fails on missing template varHost env var not forwardedAdd it to remoteEnv via ${localEnv:...}
Wrong home directoryApplied as root before remoteUser switchRun apply in postCreateCommand as remoteUser

Conclusion

The invariant: the chezmoi source is the truth, rendered per container with secrets injected from the host at apply time — never committed. Bootstrap with init --apply in postCreateCommand and re-apply idempotently on start, and your personal environment is reproducible without leaking credentials into the image.

FAQ

Why chezmoi over a plain symlink script? Templating. chezmoi renders the same source differently per machine and pulls secrets from the environment, which a static symlink approach can’t do safely in a shared image.

Where should the dotfiles repo live for offline use? Vendor a copy into the workspace and run chezmoi init --source ./dotfiles --apply so creation doesn’t require network access.