Persisting Zsh history across container rebuilds

Every DevContainer rebuild discards the container’s writable layer, and with it ~/.zsh_history — so your hard-won command history resets to empty each time you recreate the environment. This page moves HISTFILE onto a named volume so history survives rebuilds without polluting the image or the workspace. It extends the Shell Environment Customization (Zsh, Fish, Bash) cluster.

Prerequisites

  • Zsh as the default shell in the container
  • A non-root remoteUser (e.g. vscode)
  • Docker named-volume support

How-To Steps

  1. Create a dedicated volume for shell state and point HISTFILE at it:

    {
      "image": "mcr.microsoft.com/devcontainers/base:bookworm@sha256:2c5a...",
      "mounts": [
        "source=zsh-history,target=/commandhistory,type=volume"
      ],
      "containerEnv": { "HISTFILE": "/commandhistory/.zsh_history" },
      "remoteUser": "vscode"
    }
    
  2. Fix ownership and configure history options on create, since the fresh volume is root-owned:

    {
      "postCreateCommand": "sudo mkdir -p /commandhistory && sudo chown -R vscode:vscode /commandhistory && echo 'setopt INC_APPEND_HISTORY SHARE_HISTORY' >> ~/.zshrc"
    }
    

    INC_APPEND_HISTORY writes each command immediately, so history survives even an ungraceful container stop.

  3. Set generous history limits so the persisted file actually retains depth. Append to ~/.zshrc:

    export HISTSIZE=100000
    export SAVEHIST=100000
    
  4. Verify persistence by rebuilding and checking the count:

    wc -l /commandhistory/.zsh_history
    devcontainer up --workspace-folder . --remove-existing-container
    devcontainer exec --workspace-folder . wc -l /commandhistory/.zsh_history   # unchanged
    

Common Pitfalls

SymptomRoot CauseRemediation
History empty after rebuildHISTFILE in the container layerPoint HISTFILE at a named-volume path
permission denied writing historyFresh volume owned by rootchown the volume in postCreateCommand
Only last session’s commands keptINC_APPEND_HISTORY not setAdd setopt INC_APPEND_HISTORY SHARE_HISTORY
History truncatedLow HISTSIZE/SAVEHISTRaise both to a high value

Conclusion

The invariant: HISTFILE must resolve to a path on a persistent volume the remoteUser owns, with incremental append enabled. Do that and command history outlives every rebuild without ever being committed to the image or the repo.

FAQ

Why not store history in the workspace? The workspace is your repo — history doesn’t belong in version control and would create noise in git status. A dedicated volume keeps it personal and out of the tree.

Does this work for Bash or Fish too? Yes. Point the shell’s history variable (HISTFILE for Bash, fish_history location for Fish) at the same volume and apply the equivalent append option.