# Chezmoi

> Manage dotfiles with chezmoi — the source-state dotfile manager that uses filename attributes (`dot_`, `private_`, `encrypted_`, `run_once_`), Go templates, and password-manager integration to keep one set of dotfiles working across many machines. Use when the user says "set up dotfiles", "manage my dotfiles", "bootstrap a new machine", "chezmoi", "encrypt my SSH key in dotfiles", "template dotfiles per OS", "add secrets to dotfiles", "what should be in my .chezmoi.toml.tmpl", or asks how to share configs across macOS / Linux / servers. Also trigger when the user is staring at a fresh machine and wants the one-liner that gets them home, or when they're about to commit something sensitive into a dotfiles repo. Do NOT use for stow / yadm / rcm or other dotfile managers, generic git repo setup, or password-manager setup unrelated to dotfiles.

- Skill: `paulnsorensen/chezmoi` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add paulnsorensen/chezmoi`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paulnsorensen/chezmoi/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: paulnsorensen (https://skillmd.com/u/paulnsorensen)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/paulnsorensen/chezmoi

---


# chezmoi

Wrap [chezmoi](https://chezmoi.io/) — manage dotfiles across machines using
source-state semantics, Go templates, and at-rest encryption.

## When to use

- Bootstrapping dotfiles on a new machine
- Adding a new file to existing chezmoi-managed dotfiles
- Templating a dotfile per-OS, per-host, or per-role (work vs. personal)
- Adding secrets via 1Password / Bitwarden / age / gpg
- Writing a `run_once_` install script
- Diagnosing "chezmoi apply changed something I didn't expect"
- Designing the `.chezmoi.toml.tmpl` config template for a fleet of machines

## Mental model

chezmoi maps a **source directory** (`~/.local/share/chezmoi`, a git repo) to
a **target directory** (the user's home). Source filenames carry attribute
prefixes that determine target name and behavior. Source files with a `.tmpl` suffix are rendered as Go `text/template` and
written to their target paths. Files under `.chezmoitemplates/` are reusable
template fragments included by other templates — they do not produce target
files on their own. A
config file at `~/.config/chezmoi/chezmoi.toml` (typically generated from
`.chezmoi.toml.tmpl` on first init) holds per-machine variables.

The two reasons people get into trouble:

1. They edit files directly under `~/.local/share/chezmoi/` instead of using
   `chezmoi edit $TARGET`. This breaks templates and decrypt round-trips.
2. They commit secrets in plaintext "because the repo is private". Repos
   leak, forks leak, history is forever. Use a secret backend.

## Where the config file lives

Two files, easy to confuse. Both can be in `toml`, `yaml`, `json`, or
`jsonc` (auto-detected by extension; two formats present at once is an
error, not "first one wins"):

| File | Path | Who writes it |
|---|---|---|
| `chezmoi.<fmt>` (the *config*) | `$XDG_CONFIG_HOME/chezmoi/chezmoi.<fmt>`, default `~/.config/chezmoi/chezmoi.<fmt>` (`.toml` most common). **NOT** in the source repo. | Generated by `chezmoi init` (or `apply --init`) from the template below. Per-machine. Holds secrets, API tokens, machine-specific settings — never commit. |
| `.chezmoi.<fmt>.tmpl` (the *config template*) | Source-state root: `~/.local/share/chezmoi/.chezmoi.<fmt>.tmpl` (`.toml` most common). **Committed** to the dotfiles repo. | You write it. Uses `promptBoolOnce` / `promptChoiceOnce` / `promptStringOnce` so a fresh machine generates the right config on first init. |

Override locations with `--config <path>` (use `--config-format <fmt>` if
the extension is unusual). Look up effective values with
`chezmoi cat-config` and `chezmoi dump-config`.

## Protocol

### 1. Detect what the user wants

| User says | Go to |
|---|---|
| "set up chezmoi from scratch" / "new machine" | §2 Bootstrap |
| "add this file to my dotfiles" | §3 Add a file |
| "make this dotfile depend on OS / host" | `references/templating.md` |
| "encrypt this" / "store this token" | `references/secrets.md` |
| "run a script on first apply" | `references/scripts.md` |
| "chezmoi did something weird" | `references/pitfalls.md` |
| "design my full setup" | `references/bootstrap.md` |

### 2. Bootstrap (new machine)

The full one-liner that clones, renders templates, and applies in one shot:

```bash
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply $GITHUB_USERNAME
```

For a fully fleshed `.chezmoi.toml.tmpl` recipe with `promptBoolOnce` for
work-vs-personal-vs-headless, see `references/bootstrap.md`.

### 3. Add a file

```bash
chezmoi add ~/.zshrc                  # plain copy
chezmoi add --template ~/.gitconfig   # add as a template (.tmpl suffix added)
chezmoi add --encrypt ~/.ssh/config   # encrypted at rest
chezmoi chattr +template ~/.zshrc     # turn an existing managed file into a template
chezmoi edit ~/.zshrc                 # edit the SOURCE — handles decrypt + .tmpl correctly
```

Then the safe-apply ritual (§5).

### 4. File-naming reference

The table users get wrong most often. **Prefixes must appear in a specific
order, and the allowed prefix set depends on the target type** —
`/reference/source-state-attributes/` is the canonical source.

| Prefix | Effect on target |
|---|---|
| `dot_` | Add leading `.` (`dot_zshrc` → `~/.zshrc`) |
| `private_` | Strip group + world permissions (mode 0600 / 0700) |
| `readonly_` | Strip all write permission bits |
| `executable_` | Add executable bit |
| `empty_` | Allow empty file (default would remove it) |
| `encrypted_` | Decrypt source via age/gpg before writing target |
| `symlink_` | Create a symlink instead of a regular file (file form, target is symlink target) |
| `exact_` (dirs) | Delete anything in target dir not present in source |
| `create_` | Create target only if missing — never overwrite |
| `modify_` | Treat source as a script (or template, see below) that produces target contents |
| `run_` | Treat source as a script to execute (not a target file) |
| `before_` / `after_` | Order scripts relative to applying files |
| `once_` (scripts) | Run only when contents have not been run successfully before |
| `onchange_` (scripts) | Run only when contents change for THIS filename |
| `remove_` | Remove the target if it exists |
| `external_` (dirs) | Mark a directory as an external — pairs with `.chezmoiexternal.<fmt>` / `.chezmoiexternals/`; chezmoi ignores attributes on child entries |
| `literal_` (anywhere) | Stop parsing further attributes from this point |

**Allowed prefix order per target type** (the order is rigid; rearranging is
a parse error):

| Target type | Allowed prefixes (in order) | Optional suffix |
|---|---|---|
| Directory | `remove_`, `external_`, `exact_`, `private_`, `readonly_`, `dot_` | — |
| Regular file | `encrypted_`, `private_`, `readonly_`, `empty_`, `executable_`, `dot_` | `.tmpl` |
| Create file | `create_`, `encrypted_`, `private_`, `readonly_`, `empty_`, `executable_`, `dot_` | `.tmpl` |
| Modify file | `modify_`, `encrypted_`, `private_`, `readonly_`, `executable_`, `dot_` | `.tmpl` |
| Remove file | `remove_`, `dot_` | — |
| Script | `run_`, `once_` or `onchange_`, `before_` or `after_` | `.tmpl` |
| Symlink | `symlink_`, `dot_` | `.tmpl` |

Suffix `.tmpl` makes the file a template. `.age` / `.asc` are stripped
automatically when the corresponding encryption is configured.

**Modify file detail.** A `modify_` source is a script by default — it reads
the current target on stdin and writes the new target to stdout. If the
file contains the string `chezmoi:modify-template`, lines containing that
marker are stripped and the rest is interpreted as a template; the existing
target is exposed in `.chezmoi.stdin`. Use the template form when you want
to surgically edit a structured file (JSON, TOML, YAML); use the script
form when you need a real interpreter (`jq`, `awk`, `sed`).

### 5. Safe-apply ritual

Before any apply that touches important files, run the inspection trio:

```bash
chezmoi status              # one line per changed file
chezmoi diff                # unified diff of pending changes
chezmoi apply --dry-run -v  # full plan, including scripts that would run
chezmoi apply               # only after the above looks right
```

For routine pulls on a machine you trust:

```bash
chezmoi update                                          # = git pull --autostash --rebase + apply
chezmoi git pull -- --autostash --rebase && chezmoi diff # preview only — no apply
```

### 6. Edit safely

```bash
chezmoi edit ~/.zshrc       # opens source in $EDITOR, handles .tmpl + encryption
chezmoi cd                  # subshell in the source directory (for git operations)
chezmoi re-add              # refresh source from current target after editing target
chezmoi merge ~/.zshrc      # 3-way merge target ↔ source ↔ destination
chezmoi merge-all           # 3-way merge every file whose actual ≠ target (add --init to re-render config first)
```

Editor resolution: `edit.command` → `$VISUAL` → `$EDITOR` → `vi` (or
`notepad.exe` on Windows). chezmoi prints a warning if the editor returns
in < `edit.minDuration` (default `1s`) — that catches the "I opened the
file but didn't actually edit it" case. Set to `0` to silence.

Never open `~/.local/share/chezmoi/encrypted_dot_ssh/...` or `*.tmpl` files
directly in your editor — the file you see isn't the file chezmoi sees.

### 7. Application order (mental model)

`chezmoi apply` is deterministic. Each run does:

1. Read source state, read destination state, compute target state.
2. Run `run_before_` scripts in ASCII order of target name.
3. Update entries in ASCII order of target name. Directories (including
   directories materialized by externals) are written before the files
   they contain. Externals are fetched/expanded during this phase.
4. Run `run_after_` scripts in ASCII order.

Implications:

- A `run_before_` script must not read a file that an external will
  provide — externals are applied in phase 3, not before.
- Target names are compared *with all attribute prefixes stripped*. Given
  `create_alpha` and `modify_dot_beta`, `.beta` is updated before `alpha`
  because `.beta` < `alpha` in ASCII.
- chezmoi assumes source and destination don't change during a run. A
  `run_before_` script that writes into either violates that assumption —
  behavior is undefined.

### 8. Partial-file management

When chezmoi shouldn't own the whole file, pick a pattern based on who
the file belongs to:

**`modify_` — user owns the file, chezmoi stamps a block.** The source
reads the existing target on stdin and writes the new target on stdout.

Script form, `modify_dot_zshrc`:

```bash
#!/usr/bin/env bash
set -euo pipefail
# Strip any prior chezmoi block from the existing file …
awk '/# chezmoi:begin/{s=1;next} /# chezmoi:end/{s=0;next} !s'
# … then append the current managed block.
cat <<'EOF'

# chezmoi:begin
export EDITOR=nvim
alias g=git
# chezmoi:end
EOF
```

Template form (for structured files), `modify_dot_config_private_app.yaml.tmpl`:

```go-template
{{- /* chezmoi:modify-template */ -}}
{{- $cfg := dict -}}
{{- if .chezmoi.stdin -}}{{- $cfg = .chezmoi.stdin | fromYaml -}}{{- end -}}
{{- $_ := set $cfg "telemetry" false -}}
{{- $_ := set $cfg "user" (dict "email" .email) -}}
{{ toYaml $cfg }}
```

The `if .chezmoi.stdin` guard handles the initial-apply case where the
target doesn't exist yet. Use `fromJsonc` / `fromToml` / `fromYaml` +
`toPrettyJson` / `toToml` / `toYaml` for the round-trip.

**`.chezmoitemplates/` — chezmoi owns the file, fragments are shared.**
Reusable blocks live under `.chezmoitemplates/`, pulled into regular
templates with `{{ template "name" . }}` (or `includeTemplate "name" .`
when you need to pipe the result through a function):

```
.chezmoitemplates/zsh-prompt          # one file
dot_zshrc.tmpl                        # {{ template "zsh-prompt" . }}
dot_config/fish/config.fish.tmpl      # {{ template "zsh-prompt" . }} too
```

**Choose the pattern by ownership.** `modify_` when the user can freely
edit the file outside the managed block. `.chezmoitemplates/` when
chezmoi renders the whole file but you want to DRY shared sections.
Mixing both in one target is usually a smell.

## Hard rules

These are non-negotiable. Each one corresponds to a real foot-gun:

1. **Never commit plaintext secrets**, even to a private repo. Repos leak,
   get forked, get logged, get backed up. Pick a backend from
   `references/secrets.md`.
2. **Never edit encrypted or templated source files directly.** Use
   `chezmoi edit $TARGET` so the decrypt and template round-trips happen.
3. **Always inspect before applying** on shared/important files —
   `chezmoi diff` or `chezmoi apply --dry-run -v`. `exact_` on a directory
   deletes unmanaged entries; you want to see that coming.
4. **`prompt*` template functions belong only in the config-file template**
   (`.chezmoi.toml.tmpl`). Using them in regular dotfile templates causes a
   prompt on every apply / diff / status, which defeats automation.

## Secrets decision tree (summary)

Full details in `references/secrets.md`. Pick exactly one per repo.

- Public repo + cloud password manager → 1Password CLI / Bitwarden template
  functions. Secrets fetched at apply time, never on disk.
- Private repo + offline-friendly → age (`encrypted_` prefix). Identity at
  `~/.config/chezmoi/key.txt`, lives outside the repo.
- Sharing across a team → SOPS-encrypted `.chezmoidata/secrets.yaml`.
- Air-gapped / first-bootstrap → gitignored plaintext `.chezmoidata/local.yaml`
  (transitional, not a long-term answer).

Mixing backends multiplies moving parts without making anything more secure.

## Templating quick reference

Full details in `references/templating.md`.

```go-template
{{- if eq .chezmoi.os "darwin" }}
export HOMEBREW_PREFIX="/opt/homebrew"
{{- else if eq .chezmoi.os "linux" }}
export PATH="/usr/local/bin:$PATH"
{{- end }}

{{- if and (eq .chezmoi.os "darwin") (lookPath "brew") }}
# brew is installed
{{- end }}

email = {{ .email | quote }}
```

Built-in template variables include `.chezmoi.os`, `.chezmoi.arch`,
`.chezmoi.hostname`, `.chezmoi.username`, `.chezmoi.homeDir`,
`.chezmoi.sourceDir`. User-defined data lives under `[data]` in
`chezmoi.toml`, or any `.chezmoidata/*.{toml,yaml,json}` file.

chezmoi runs templates with Go's `missingkey=error` option, so a typo
like `{{ .gitub.user }}` fails the apply instead of rendering empty.
Override via `[template] options = ["missingkey=zero"]` only if you have
a concrete reason — losing the typo guard is rarely worth it.

For symlink targets, leading/trailing whitespace in the rendered result
is stripped, and an empty result removes the symlink. For file targets,
an empty render removes the file (use the `empty_` prefix to keep it).

## Special files and directories

All entries in the source dir beginning with `.` are ignored **except** the
specials below. They are evaluated in this order on every operation:
`.chezmoiroot` → `.chezmoi.<fmt>.tmpl` → data files → `.chezmoitemplates/`
→ `.chezmoiignore` → `.chezmoiremove` → externals → `.chezmoiversion`.

| Path | Role |
|---|---|
| `.chezmoiroot` | Single-line file pointing at a sub-directory as the source root |
| `.chezmoi.<fmt>.tmpl` | Config-file template used on first `chezmoi init` (and on `--init`) |
| `.chezmoidata.<fmt>` / `.chezmoidata/` | Data merged into the template `.` namespace before any template renders |
| `.chezmoitemplates/` | Reusable template fragments (`{{ template "name" . }}`) — no target files |
| `.chezmoiscripts/` | Scripts not bound to a target file; honor `run_before_` / `run_after_` |
| `.chezmoiignore` | Per-target ignore list — source-relative, templatable |
| `.chezmoiremove` | Targets to remove on apply — templatable |
| `.chezmoiexternal.<fmt>` / `.chezmoiexternals/` | Pull files/archives from URLs at apply time |
| `.chezmoiversion` | Min chezmoi version required by this source repo |

`.chezmoiignore` matches **source-relative paths** (e.g. `dot_zshrc`), not
target paths. People get this wrong constantly.

`<fmt>` means `toml`, `yaml`, `json`, or `jsonc`. The directory forms
(`.chezmoidata/`, `.chezmoiexternals/`) merge their contents in lexical
order with any same-prefix file forms — useful for splitting big config
across files or composing externals per role.

## What you don't do

- Write a competing dotfile manager or shim around `chezmoi apply`
- Edit files under `~/.local/share/chezmoi/` directly when they are templates
  or encrypted
- Commit a `.env` / `secrets.yaml` to a "private" repo
- Suggest `chezmoi apply` without first running `chezmoi diff` on a shared
  machine
- Use `prompt*` template functions outside of `.chezmoi.toml.tmpl`
- Mix multiple secret backends (age + 1Password + SOPS) in the same repo
  without a clear, written reason

## Gotchas

- `fork/exec ...: exec format error` running a templated script means a
  newline before `#!`. Add `-` inside the closing `}}` on the first
  template line to suppress it.
- `fork/exec ...: permission denied` for scripts means `$TMPDIR` is
  mounted `noexec`. Set `scriptTempDir` in `chezmoi.toml`.
- Windows line endings (CRLF) in templates produce broken shell scripts on
  Linux/macOS. Add `* text eol=lf` to a `.gitattributes` in the dotfiles
  repo, or set `core.autocrlf = input`.
- `chezmoi init` on a new machine before `.chezmoi.toml.tmpl` exists
  silently skips per-machine prompts. Land that template before adding
  host-specific files.
- `chezmoi doctor` is the first command to run when something is off — it
  reports missing dependencies (`op`, `age`, etc.), broken paths, and
  shell integration issues in one pass.
- Context7 may be unavailable. The chezmoi CLI is self-documenting via
  `chezmoi help` and `chezmoi help <command>`; fall back to that.
- Two config files (e.g. both `chezmoi.toml` and `chezmoi.yaml`) in
  `~/.config/chezmoi/` is an error, not "first one wins". When migrating
  formats, delete the old file in the same commit.
- Config format is detected by extension. Force it with `--config-format`
  if you pipe a config from an unusual path.
- `chezmoi <unknown>` looks for `chezmoi-<unknown>` on `$PATH` (git-style
  plugin convention) before failing. Useful for adding repo-local
  helpers; surprising when a typo silently runs an unrelated binary.

## Reference URL index

The chezmoi docs evolve fast — new template functions, new config keys,
new prefixes every few releases. **When a question goes deeper than
this skill, fetch the live page** rather than guessing from training
data. Prefer `mcp__tavily__tavily_extract` (single URL, `extract_depth:
"advanced"`, `format: "markdown"`) — it returns the canonical
`/reference/*` content directly. Use Context7 only as a fallback. Do
not `tavily_map` or crawl — the URLs below are stable; pick the exact
one.

Example call shape:

```
mcp__tavily__tavily_extract(
  urls: ["https://www.chezmoi.io/reference/templates/functions/jq/"],
  extract_depth: "advanced",
  format: "markdown",
)
```

Map of what lives where:

| Topic | URL |
|---|---|
| Concepts (source / target / destination / working tree) | `/reference/concepts/` |
| Source-state attribute order per target type | `/reference/source-state-attributes/` |
| Target types (file / dir / symlink / script / modify / create / remove) | `/reference/target-types/` |
| Application order on `chezmoi apply` | `/reference/application-order/` |
| All command-line flags (global / common / developer) | `/reference/command-line-flags/` |
| Per-command reference (`chezmoi <name>`) | `/reference/commands/<name>/` |
| Configuration file structure | `/reference/configuration-file/` |
| Hooks (read-source-state / apply / etc.) | `/reference/configuration-file/hooks/` |
| Interpreters for scripts by extension | `/reference/configuration-file/interpreters/` |
| Special files (`.chezmoiignore` etc.) | `/reference/special-files/<name>/` |
| Special directories (`.chezmoidata/` etc.) | `/reference/special-directories/<name>/` |
| Template variables (`.chezmoi.os` etc.) | `/reference/templates/variables/` |
| Template directives (`chezmoi:template:`, etc.) | `/reference/templates/directives/` |
| Built-in template functions | `/reference/templates/functions/<name>/` |
| `init` template functions (`promptBoolOnce` etc.) | `/reference/templates/init-functions/<name>/` |
| Password-manager template functions | `/reference/templates/<vendor>-functions/<name>/` (1password, bitwarden, dashlane, doppler, ejson, gopass, keepassxc, keeper, keyring, lastpass, pass, passhole, protonpass, vault, aws-secrets-manager, azure-key-vault, secret) |
| GitHub template functions | `/reference/templates/github-functions/<name>/` |
| Plugins | `/reference/plugins/` |

Heuristic: when the user asks "what does prefix X do" → source-state-
attributes; "what does `chezmoi <cmd>` do" → `commands/<cmd>`; "what
template var holds Y" → templates/variables; "how do I read a secret from
Z" → `templates/<vendor>-functions`. Fetch only the page you need with
`mcp__tavily__tavily_extract` — single URL, advanced depth — so the
answer is always grounded in the canonical docs as of today, not what
this skill happened to know when it was last edited.

