The system
What chezmoi is, and why you run it.
chezmoi keeps the configuration for your environment in one Git repository, and lays those files down in the right places on each supported profile: full Mac desktop, Linux/WSL terminal layer, or headless container. Understanding it means you can explain, reproduce, and repair the setup instead of treating it as hidden magic.
#The mental model
There are two sides. On one side is a source repository — a folder full of your config files, tracked in Git. On the other is your home directory, where programs actually look for their settings. chezmoi's whole job is to copy from the first to the second, correctly.
Files in the repo start with dot_ instead of a dot so they're visible and easy to work with. A .tmpl ending means the file has blanks chezmoi fills in per machine — your name, your email, whether it's a work computer. That's how one repo serves many people without hard-coding anyone.
#What apply actually does
When you run chezmoi apply, it walks the source repo and, for each file, decides what your home directory should look like, then makes it match. Concretely it:
- fills in any templates with this machine's answers,
- writes or updates the real config files in your home directory,
- runs setup scripts that should run — installing software, setting macOS preferences — and skips the ones that already ran.
It only changes what's different. Run it twice in a row and the second run does almost nothing — that's the point. It's safe to run any time you want to be sure your machine or shell profile matches the repo.
#Why it's in version control
Because your setup is code. It lives in Git, so every change is recorded, you can see what changed and when, and you can roll back. Share the repo and someone else gets the same operating model: machine setup, terminal workflow, and agent instructions. Lose the laptop and a new one is minutes away, not days.
For files managed by chezmoi, the repo is the source of truth: chezmoi diff shows what would change, and chezmoi apply puts managed files back to the repo version. That is powerful, but it also means this setup intentionally changes the Mac.
#First-time setup
This is not a passive app installer. It changes shell, Git, terminal, tmux, editor, Homebrew, Finder, keyboard repeat, browser/developer, and app settings. First apply creates a focused backup at ~/.local/state/groundwork/backups/latest. Restore the backed-up files and macOS defaults with groundwork-restore. Already-used Macs can also choose an optional clean start that moves old Neovim, tmux, and zsh completion state to ~/.local/state/groundwork/reset/latest.
On a fresh Mac, run the bootstrap script. It installs the Apple/Homebrew/GitHub/chezmoi prerequisites, clones or updates github.com/jasondockery/groundwork at ${GROUNDWORK_DIR:-~/code/groundwork}, then applies it with chezmoi. The script is the source of truth, so the docs do not duplicate every internal command. On Windows, first install WSL2/Ubuntu and run the Unix shell path inside WSL rather than trying to apply the macOS bootstrap in PowerShell.
The repo is a visible project checkout; .chezmoiroot keeps only home/ mapped into your actual home folder.
Use the one-liner:
$ bash -c "$(curl -fsSL https://raw.githubusercontent.com/jasondockery/groundwork/main/bootstrap-mac.sh)"
To read it first, download it to /tmp/bootstrap-mac.sh, open it with less, then run bash /tmp/bootstrap-mac.sh.
The first run asks a few questions — your name and email, whether this is a work machine, whether a Mac is a server/CI machine, which password manager you use, and whether to install optional large toolchains such as Unity or Xcode. Your answers are saved, so it won't ask again. From there it installs the selected profile and configures the machine.
Two prompt styles appear during setup, because two tools ask. The setup questions are chezmoi's: a yes/no question ends with its accepted answers and its default spelled out — type y or t for yes, n or f for no, and a bare Enter keeps the named default (the dim bool, default false under the cursor is chezmoi's type hint, not something to type). Groundwork's own prompts (bootstrap, repair offers) use the shell convention instead: a bracketed affordance like [y/N] or [KEEP/switch/login], where the capitalized choice is the default and Enter takes it. In both styles, a run with no terminal attached takes the default and says so rather than hanging.
The backup is scoped to what this repo can reasonably restore: selected managed config files, selected macOS defaults keys, and Homebrew state notes. It does not uninstall apps, undo Homebrew upgrades, reverse manual permissions, or replace Time Machine.
If the Mac already has an old dev setup, answer yes to the reset prompt only when you want a cleaner start. It moves common conflict zones aside — Neovim config/state/cache, tmux legacy config/plugin/restore state, and zsh completion dumps — before applying this repo. It does not delete them.
Don't hand-edit a file that chezmoi manages (like ~/.zshrc) — the next apply overwrites it. Change it in the repo instead, commit, then apply. The repo always wins, by design.
#Customize without forking
Groundwork is a shared base with a personal layer. For normal personal preferences, use the local override files that Groundwork intentionally loads last: ~/.zshrc.local, ~/.gitconfig.local, and ~/.config/tmux/tmux.local.conf. For setup choices (your profile, work machine, password manager, and so on), run groundwork-configure — it shows your current answers, changes only what you pick, previews the diff, and applies after you confirm. (Plain chezmoi init only regenerates from your existing answers; it never asks what to change.) See Customizing for the full tiered model.
Groundwork treats Docker as a way to run the environment, not as a standalone lesson. Linux and WSL2 get the shared terminal layer: shell, Git, tmux, Neovim, mise/uv, helper scripts, and agent instructions. macOS GUI apps, App Store apps, browser helpers, and macOS defaults are skipped when .chezmoi.os is not darwin; on macOS, the server/CI prompt lets a headless Mac skip the same desktop layer.
The Docker image is the non-interactive headless profile for agents, CI, and disposable shells. From the repo root, build it with docker build -t groundwork . and run docker run -it --rm groundwork. Keep that groundwork tag for the image you actually use; when you or an agent rebuild only to test a Dockerfile change, build it with groundwork-docker-build-scratch <purpose> . instead — the wrapper applies the ephemeral labels and the groundwork/scratch:<purpose> tag so groundwork-docker-tidy can remove it later — see Platforms for the contract. Personal Macs can use Docker Desktop; work Macs use Colima plus the Docker CLI, so start the daemon with colima start first. This does not try to teach Dockerfiles, Compose, or app hosting; it just gives the Groundwork terminal environment somewhere reproducible to run. See Platforms for the profile-by-profile split.
#Day to day
When the shared repo gets new changes, pull them and apply:
$ chezmoi update --init
# pulls the latest repo and applies it in one step
# --init also refreshes the setup answers when new questions were added:
# every answer you already gave is kept, only the new questions are asked
That refreshes your config from the repo. If the update changes an install hook, such as the Brewfile bundle, that hook can add newly declared tools. It does not upgrade already-installed Homebrew apps, casks, or mise runtimes to newer upstream releases. To freshen everything at once, the setup ships an update-all command:
$ update-all
# chezmoi update + up to 3 brew update attempts + upgrade/cleanup + mise upgrade
Groundwork does not install unattended Homebrew or mise upgrade jobs. Run update-all intentionally and skim the output. It first synchronizes Groundwork, then hands the same invocation to the freshly applied groundwork-update-run; a newly delivered fix therefore governs the rest of that same run. The command works the same in every shell Groundwork sets up — macOS, Linux, and WSL2 — and each stage skips cleanly when its manager is not installed; native Windows outside WSL is not a target. Groundwork attempts brew update up to three times, with short backoff between attempts; if all three total attempts fail, it stops before upgrades and leaves the success timestamp unchanged. The following Homebrew upgrade uses that successful refresh without starting a secondary automatic update. This is also where floating mise baselines such as Node LTS advance to the newest matching patch. The shell prints a quiet reminder when the last successful update-all is more than seven days old. Set GROUNDWORK_UPDATE_REMINDER_DAYS=14 to change the cadence, or GROUNDWORK_UPDATE_REMINDER=0 in ~/.zshrc.local to silence it.
Existing users receive one migration notice because an old update-all() function already loaded in a terminal can shadow the new executable. Run exec zsh once in those terminals. After that transition, future command updates take effect during update-all itself without another shell reload.
AI tools are the fast-moving exception: Claude Code stays on its vendor-supported latest path, while Codex and OpenCode refresh through Homebrew in the visible update-all path.
To see the other helper commands this setup installs, run groundwork-help or open the Command catalog.
To see what a change would do before committing to it, ask first:
$ chezmoi diff
# shows exactly what apply will change, and nothing else
#Going deeper
- chezmoi — Quick start — the official fastest path from zero to a working Groundwork repo.
- chezmoi — User guide — the daily-workflow reference (add, edit, diff, apply, templates).
- GitHub does dotfiles — why dotfiles matter, with tools and examples.
- awesome-dotfiles — a big curated list of other people's setups to learn from.