Chezmoi + Mise + DevPod Reference Link to heading

The Three Approaches (and Why We Use #3) Link to heading

→ course: s1-mise-postCreate

ApproachHowProblem
DockerfileBake mise into container imageTied to DevPod, doesn’t work on bare metal
postCreatedevcontainer.json runs setup scriptStill container-only, no dotfiles
Dotfiles repoDevPod clones dotfiles, runs setup✅ Works everywhere: containers, bare metal, new Linux box

What the Two Tools Do Link to heading

→ course: 2-mise-global

They work together: chezmoi installs mise, mise installs everything else.

ToolJob
chezmoiCopies config files into $HOME (bashrc, nvim config, aliases, etc.)
miseDownloads and manages CLI tool binaries (kubectl, neovim, lazygit, etc.)

Full Install Sequence Link to heading

→ course: s1-mise-postCreate, s2-mise-dotfiles-setup, s3-mise-chezmoi-scripts

This is exactly what happens when you run:

devpod up . --ide none --dotfiles git@github.com:you/dotfiles-devpod.git
1. DevPod provisions container (Ubuntu base image)

2. DevPod clones dotfiles-devpod into container

3. DevPod finds and runs: setup
   └── if chezmoi not present: curl installs it
   └── chezmoi init --apply dotfiles-devpod repo
         │
         ├── 4. Applies dot_* files to $HOME
         │       dot_bashrc        → ~/.bashrc
         │       dot_bash_aliases  → ~/.bash_aliases
         │       dot_bash_profile  → ~/.bash_profile
         │       dot_tmux.conf     → ~/.tmux.conf
         │       dot_config/nvim/  → ~/.config/nvim/
         │       dot_config/mise/config.toml → ~/.config/mise/config.toml
         │
         ├── 5. Processes .chezmoiexternal.toml
         │       Downloads mise binary → ~/.local/bin/mise
         │
         ├── 6. Runs run_once_install-packages.sh  (first time only)
         │       sudo apt update
         │       sudo apt install -y vim-tiny curl
         │
         └── 7. Runs run_onchange_after_install_packages.sh.tmpl
                 (runs when config.toml hash changes)
                 mise trust ~/.config/mise/config.toml
                 mise install
                   └── installs all tools in config.toml:
                       neovim, lazygit, kubectl, k9s, bat, fzf, etc.

8. On first nvim launch: LazyVim bootstraps itself

mise trust — What, Why, and When Link to heading

→ course: s1-mise-postCreate

mise refuses to install from any config file it hasn’t explicitly been told to trust. This prevents a malicious repo from silently installing tools when you clone it.

When mise asks for trust:

  • First time it sees a config.toml at a new path
  • You moved or renamed the config file
  • You’re in a new container (fresh path every time)
  • You cloned a repo that has a mise.toml in it
  • chezmoi applied files to a new $HOME

The surprise: any directory you cd into that has a mise.toml will trigger a trust warning. This catches people off guard when working in cloned repos — it’s not broken, mise is just being cautious.

# Trust a specific config file
mise trust ~/.config/mise/config.toml

# Trust current directory's mise.toml
mise trust

# See what's currently trusted
mise trust --list

Why order matters in scripts:

$HOME/.local/bin/mise trust $HOME/.config/mise/config.toml && $HOME/.local/bin/mise install

Skip trust and mise install silently does nothing or errors. Trust first, always.


File Reference Link to heading

→ course: 1-mise-chezmoi-external, 2-mise-global, s2-mise-dotfiles-setup, s3-mise-chezmoi-scripts

dotfiles-devpod repo structure Link to heading

dotfiles-devpod/
├── setup                          ← DevPod entry point (must be executable)
├── .chezmoiexternal.toml          ← Downloads mise binary
├── .chezmoiignore                 ← Files chezmoi should skip
├── .chezmoiscripts/
│   ├── run_once_install-packages.sh              ← apt installs (once)
│   └── run_onchange_after_install_packages.sh.tmpl  ← mise install (on change)
├── dot_bashrc
├── dot_bash_aliases
├── dot_bash_profile
├── dot_tmux.conf
└── dot_config/
    ├── mise/
    │   └── config.toml            ← Tool list for mise
    └── nvim/                      ← LazyVim config
        └── init.lua

Key files explained Link to heading

setup — DevPod looks for this first. Bootstraps chezmoi and applies the repo.

#!/bin/bash
set -euo pipefail
if ! command -v chezmoi >/dev/null; then
    sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply git@github.com:you/dotfiles-devpod.git
else
    chezmoi init --apply git@github.com:you/dotfiles-devpod.git
fi

.chezmoiexternal.toml — Download the correct mise binary for current OS/arch.

[".local/bin/mise"]
type = "file"
executable = true
url = "https://mise.jdx.dev/mise-latest-{{.chezmoi.os}}-{{.chezmoi.arch}}"

run_once_install-packages.sh — Runs exactly once. For apt packages only.

#!/bin/bash
set -euo pipefail
sudo apt update
sudo apt install -y vim-tiny curl

run_onchange_after_install_packages.sh.tmpl — Reruns whenever config.toml changes.

#!/bin/bash
# packages hash: {{ include "dot_config/mise/config.toml" | sha256sum }}
$HOME/.local/bin/mise trust $HOME/.config/mise/config.toml && $HOME/.local/bin/mise install

dot_config/mise/config.toml — The tool list. Edit this to add/remove tools.

[tools]
age        = "1.3.1"
bat        = "0.26.1"
chezmoi    = "2.69.4"
fzf        = "0.70.0"
helm       = "latest"
k9s        = "0.50.18"
kubectl    = "1.35.2"
kubens     = "0.9.5"
lazygit    = "0.59.0"
neovim     = "0.11.6"
node       = "25.8.0"
sops       = "3.12.1"
terraform  = "1.14.6"
tmux       = "3.6a"

Chezmoi Script Naming Rules Link to heading

→ course: s3-mise-chezmoi-scripts

PrefixBehavior
run_once_Runs one time only, never again
run_onchange_Runs when file content changes
run_always_Runs every time chezmoi apply is called
_after_ in nameRuns after other scripts
.tmpl suffixTemplate — supports {{ }} expressions

How the config.toml hash works:

run_onchange_after_install_packages.sh.tmpl contains this at the top:

# packages hash: {{ include "dot_config/mise/config.toml" | sha256sum }}

When chezmoi processes this .tmpl file it reads config.toml, computes its sha256 hash, and renders it into the comment. Chezmoi stores that rendered output and compares it on every chezmoi apply.

If config.toml changed → hash changes → comment changes → chezmoi sees the script as changed → reruns it → mise install picks up the new tools.

The hash sits in a comment and has zero effect on script logic — it’s purely a trick to make chezmoi detect the change and rerun the script.


Where Things End Up After Install Link to heading

→ course: 2-mise-global, 3-mise-project

WhatLands at
mise binary~/.local/bin/mise
mise tools (kubectl, nvim, etc.)~/.local/share/mise/installs/
mise config~/.config/mise/config.toml
chezmoi source~/.local/share/chezmoi/
nvim config~/.config/nvim/
bashrc~/.bashrc

Common Commands Link to heading

→ course: 3-mise-project

# Apply dotfiles locally
chezmoi apply

# Dry run — see what would change
chezmoi apply --dry-run --verbose

# Pull latest from remote and apply
chezmoi update

# Edit a managed file (opens in $EDITOR, applies on save)
chezmoi edit ~/.bashrc

# Go to chezmoi source dir
chezmoi cd

# Force re-run a run_once script
chezmoi state delete-bucket --bucket=scriptState
chezmoi apply

# List installed mise tools
mise list

# Install tools from config.toml
mise install

# Add a tool (updates config.toml automatically)
mise use neovim@latest

devcontainer.json Link to heading

→ course: s1-mise-postCreate Configuration file that defines your dev container — image, features, postCreate commands, etc.

VS Code Gotcha Link to heading

When you run “Add Dev Container Configuration Files” in VS Code, the devcontainer.json appears in a tab but is not saved to disk. The file does not exist yet even though you can see it.

You must explicitly save it: File → Save As → choose your project folder.

If you skip this, DevPod and VS Code will not find it and nothing will work.

Basic structure Link to heading

{
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
  "postCreateCommand": "scripts/setup"
}

Add features via VS Code Link to heading

Command palette (ctrl-shift-p) → “Dev Containers: Configure Container Features” Select features (Azure CLI, GitHub CLI, 1Password CLI, etc.) → rebuild container.

DevPod Install Link to heading

Linux (amd64) Link to heading

curl -L -o devpod "https://github.com/loft-sh/devpod/releases/latest/download/devpod-linux-amd64" \
  && sudo install -c -m 0755 devpod /usr/local/bin && rm -f devpod

Mac (Apple Silicon / ARM64) Link to heading

curl -L -o devpod "https://github.com/loft-sh/devpod/releases/latest/download/devpod-darwin-arm64" \
  && sudo install -c -m 0755 devpod /usr/local/bin && rm -f devpod

Mac ARM64 note: Ensure your base image supports ARM64, or build explicitly with devpod build --platform linux/arm64 .

DevPod Commands Link to heading

→ course: s2-mise-dotfiles-setup

# Create/start workspace (from project dir)
devpod up . --ide none --dotfiles git@github.com:you/dotfiles-devpod.git

# Set dotfiles URL globally (so you don't have to pass --dotfiles every time)
devpod context set-options -o DOTFILES_URL=git@github.com:you/dotfiles-devpod.git

# Set default IDE permanently (no GUI)
devpod ide use none

# SSH into workspace
devpod ssh <workspace-name>

# List workspaces
devpod list

# Delete workspace (forces fresh rebuild next up)
devpod delete <workspace-name>

# Stop workspace
devpod stop <workspace-name>

Tip: Workspace name comes from the directory you run devpod up . from. Running from ~/projects/ubuntu creates workspace named ubuntu.