Chezmoi + Mise + DevPod Reference Link to heading
The Three Approaches (and Why We Use #3) Link to heading
→ course: s1-mise-postCreate
| Approach | How | Problem |
|---|---|---|
| Dockerfile | Bake mise into container image | Tied to DevPod, doesn’t work on bare metal |
| postCreate | devcontainer.json runs setup script | Still container-only, no dotfiles |
| Dotfiles repo | DevPod 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.
| Tool | Job |
|---|---|
| chezmoi | Copies config files into $HOME (bashrc, nvim config, aliases, etc.) |
| mise | Downloads 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.tomlat 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.tomlin 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
| Prefix | Behavior |
|---|---|
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 name | Runs after other scripts |
.tmpl suffix | Template — 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
| What | Lands 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.
Links Link to heading
- https://containers.dev
- https://containers.dev/implementors/json_reference/
- https://containers.dev/guide/dockerfile
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/ubuntucreates workspace namedubuntu.