A new hire copies a colleague's ~/.gitconfig from a gist and pushes their first commits as Jane Doe <[email protected]>, which the hosting service does not link to their work account; another developer's version manager never activates because their ~/.zshrc is missing one line the rest of the team has. Symlinking a shared dotfiles directory fixes neither, because the files that need sharing also need per-machine differences. chezmoi manages dotfiles from a repository with templates, so a team can share defaults while each machine renders its own values. This page sets that up, as part of workstation provisioning and team dotfiles.

The model is two layers: a team dotfiles repository with shared defaults and templates, and per-developer data (name, work email, preferences) that chezmoi prompts for once and stores locally.

Diagnostic

Compare the configuration that matters on two machines, and see what chezmoi would change if it were already in use:

#!/usr/bin/env bash
set -euo pipefail
git config --global --get-regexp '^(user\.|commit\.gpgsign|gpg\.format|pull\.rebase|init\.defaultBranch)' || true
grep -nE 'mise activate|direnv hook|brew shellenv' ~/.zshrc ~/.zprofile 2>/dev/null || echo "no toolchain activation lines found"
command -v chezmoi >/dev/null && chezmoi status 2>/dev/null | head -10 || echo "chezmoi not in use"

Expected bad output on a machine set up by hand:

user.name Jane Doe
user.email [email protected]
pull.rebase false
no toolchain activation lines found
chezmoi not in use

The Git identity is personal, commits are unsigned, pull.rebase differs from the team convention, and neither mise nor direnv is activated in the shell — so pinned project tools silently fall back to system versions.

Copied Dotfiles vs Templated Dotfiles Comparison of copying dotfiles between machines against rendering them from chezmoi templates. Copied Dotfiles vs Templated Dotfiles copied or symlinked chezmoi templates someone else's identity your name and email one OS's paths per-OS conditions drift is invisible chezmoi status shows it secrets copied along secrets from a manager
Templates keep one shared source while each machine gets its own identity and paths.

Root cause

Dotfiles combine three kinds of content: team conventions (rebase on pull, signed commits, toolchain activation, aliases), per-person identity (name, email, signing key), and per-machine details (Homebrew prefix, OS-specific credential helpers, work versus personal context). Copying a file shares all three, including the parts that must differ. Symlinking a directory shares everything verbatim, so any per-machine difference becomes a local edit to a tracked file that either gets committed by mistake or blocks updates. Without a tool that separates the layers, teams give up on sharing and each machine drifts independently, which shows up as bugs that depend on who ran the command.

Resolution

  1. Create the team dotfiles repository with chezmoi's layout. Files named dot_* map to ~/.*, and a .tmpl suffix marks templates:
#!/usr/bin/env bash
set -euo pipefail
chezmoi init
cd "$(chezmoi source-path)"
cat > .chezmoi.toml.tmpl <<'EOF'
{{- $name := promptStringOnce . "name" "Full name" -}}
{{- $email := promptStringOnce . "email" "Work email" -}}
[data]
    name = {{ $name | quote }}
    email = {{ $email | quote }}
EOF
git remote add origin [email protected]:acme/dotfiles.git

promptStringOnce asks on the first chezmoi init and stores the answer in the local config, never in the repository.

  1. Template the Git config so identity comes from local data and conventions are shared:
[user]
    name = {{ .name }}
    email = {{ .email }}
    signingkey = ~/.ssh/id_ed25519.pub
[gpg]
    format = ssh
[commit]
    gpgsign = true
[pull]
    rebase = true
[init]
    defaultBranch = main
[include]
    path = ~/.gitconfig.local

Save as dot_gitconfig.tmpl. The [include] of ~/.gitconfig.local gives each developer an untracked place for personal settings that the team file does not manage.

  1. Template the shell profile with OS and architecture conditions, including toolchain activation:
{{- if eq .chezmoi.os "darwin" }}
{{- if eq .chezmoi.arch "arm64" }}
eval "$(/opt/homebrew/bin/brew shellenv)"
{{- else }}
eval "$(/usr/local/bin/brew shellenv)"
{{- end }}
{{- end }}
eval "$(mise activate zsh)"
eval "$(direnv hook zsh)"
[ -f ~/.zshrc.local ] && source ~/.zshrc.local

Save as dot_zshrc.tmpl. Personal aliases and prompts go in ~/.zshrc.local.

  1. Apply on a new machine in one command, which clones, prompts once and renders everything:
#!/usr/bin/env bash
set -euo pipefail
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$HOME/.local/bin" init --apply acme/dotfiles
"$HOME/.local/bin/chezmoi" diff --no-pager | head -20 || true
git config --global --get user.email
  1. Keep secrets out. Templates can read secrets from a password manager at apply time ({{ onepasswordRead "op://dev/npm/token" }}), so values never enter the repository. Anything personal and sensitive stays in the .local files.
Layers of a Rendered Dotfile Layers combined when chezmoi renders a dotfile, from team defaults to personal local overrides. Layers of a Rendered Dotfile Team template conventions, activation lines Machine facts OS, arch, hostname Personal data name, email, prompted once Secrets read from password manager Local override ~/.zshrc.local, untracked
Only the top layer is shared in git; the rest is local to each machine.

Expected output

$ git config --global --get-regexp '^(user\.email|commit\.gpgsign|pull\.rebase)'
user.email [email protected]
commit.gpgsign true
pull.rebase true
$ zsh -ic 'command -v mise direnv' 
/opt/homebrew/bin/mise
/opt/homebrew/bin/direnv
$ chezmoi status
$ 

The Git identity is the work account, team conventions apply, toolchain activation is present, and chezmoi status prints nothing — the machine matches the rendered source exactly.

When the team changes a convention — say, adding rerere.enabled = true to the Git template — each developer picks it up with chezmoi update, which pulls the repository and re-renders. Anyone who had edited ~/.gitconfig directly sees the conflict in chezmoi diff before anything is overwritten, which is the moment to move that personal setting into ~/.gitconfig.local where it belongs.

Prevention

  1. Run chezmoi status in the doctor script and print chezmoi diff guidance when files have drifted, so local edits are noticed and either upstreamed or moved into .local files.

  2. Scan the dotfiles repository for secrets in CI with the same scanner used for application code, as described in blocking committed secrets with a gitleaks pre-commit hook.

  3. Test rendering for each OS in CI with chezmoi execute-template and fixed data, so a template error does not break every machine on the next update.

Keeping Dotfiles in Sync Four-stage cycle of editing templates, reviewing, updating machines and checking for drift. Keeping Dotfiles in Sync Edit template pull request Review CI renders per OS chezmoi update every machine chezmoi status drift found
Changes flow through review into every machine, and drift flows back as proposals.

Platform caveats

macOS: the default shell is zsh; template dot_zshrc and dot_zprofile. Apple Silicon and Intel Homebrew paths differ; the .chezmoi.arch condition handles both.

WSL2: run chezmoi inside the distribution for the Linux shell and Git config. Windows-side Git (used by some Windows editors) has its own .gitconfig; either configure it separately or make the editor use WSL's Git.

Linux: distributions differ in where packages install; prefer command -v checks in templates over hard-coded paths.

Apple Silicon (ARM64): nothing else differs for chezmoi itself, which is a single Go binary with native builds.

Rollback

chezmoi does not delete files it did not create. To stop managing dotfiles, remove its source and config; the rendered files stay as they are and can be edited by hand:

#!/usr/bin/env bash
set -euo pipefail
chezmoi purge --force
ls -la ~/.gitconfig ~/.zshrc

Frequently Asked Questions

How is chezmoi different from symlinking a dotfiles directory?

chezmoi renders files from templates with per-machine data, supports secrets from password managers, and tracks drift. Symlinks share files verbatim, so any per-machine difference becomes a local edit to a tracked file.

Where do personal preferences go?

In untracked local files included by the templates, such as ~/.gitconfig.local and ~/.zshrc.local, or in each developer's fork of the team repository. The team source holds only conventions everyone shares.

Can chezmoi manage secrets?

It can read them from 1Password, Bitwarden, the OS keychain and others at apply time, so they appear in rendered files without ever being committed. For team repositories, keep secret use minimal and personal.

What happens if I edit a managed file directly?

chezmoi status shows it as modified, and chezmoi diff shows the difference. Move the change into the template (for team settings) or a local override (for personal ones), or it will be overwritten on the next apply.