---
title: "chezmoi: one set of dotfiles across a Linux server and two Macs"
description: "Chezmoi templates replaced years of stow and Makefiles with one set of dotfiles that renders per machine, secrets from 1Password included."
url: "https://michael.zazon.us/notes/chezmoi-one-source-of-truth-for-every-dotfile"
published: "2026-06-16T12:00:00-04:00"
kind: "build-log"
subject: "tools"
tags: ["security","dotfiles"]
---
Chezmoi let me simplify most of the per-machine special scripts and Makefiles I needed to build years ago when I was using stow.

For those who haven't run into either one, [GNU Stow](https://github.com/aspiers/stow) is a great little tool which symlinks a folder of config files into your home directory, and [chezmoi](https://www.chezmoi.io/) keeps a source copy of your dotfiles in a git repo and renders them onto each machine (I have a Linux server and two Macs that it now covers).

I had been a user of stow for years and combined with Makefiles, leaned on it to manage my dev environment and dotfiles across multiple machines.. using git versioned repos, and helping me keep consistency across environments. Recently, one of my colleagues mentioned they used chezmoi and gave me a peek at their configuration files, templates, and some things they were doing and I was intrigued. Since installing it, I have simplified my dotfiles using templates and can easily onboard and offboard configurations and manage everything with Claude Code.

The Linux machine was the biggest mess. In March, the dotfiles were 5 files pushed out by Ansible copy tasks, no templating, no secret injection at all. Starting March 19, chezmoi became the only thing writing dotfiles there (after I fixed a conflict with Ansible).

## One template per config file

I love the logic that can be used (like conditionals) and the templates I can use to manage configuration files across environments (like personal, development, and work). The templates have been the biggest impact for my needs. One .gitconfig or one .zshrc and the logic is where it belongs in these file templates. Logic meaning the if/else blocks I use for gating what makes it into each rendered template on the target machine or environment.

A trimmed example with placeholder values, where the source file is `dot_gitconfig.tmpl` and each machine sets its own profile in its local chezmoi config. The [templating docs](https://www.chezmoi.io/user-guide/templating/) cover the rest of the syntax.

```
# ~/.config/chezmoi/chezmoi.toml (one per machine, not in the repo)
[data]
    profile = "personal"

# ~/.local/share/chezmoi/dot_gitconfig.tmpl
[user]
    name = Your Name
{{- if eq .profile "work" }}
    email = you@work.example
{{- else }}
    email = you@example.com
{{- end }}

{{- if eq .chezmoi.os "darwin" }}
[credential]
    helper = osxkeychain
{{- end }}
```

Running `chezmoi apply` on a Mac with the personal profile gets the personal email and keychain helper. The server gets the same file without the macOS block.

## Diff before apply!

Editing files managed (and not managed yet) by chezmoi and checking the diff (`chezmoi diff`) has been so useful, especially if handing modifications and tweaks off to an AI agent. Bringing a new environment or machine online is almost as simple as a git clone and chezmoi init + apply.

```
chezmoi add ~/.tmux.conf    # start managing a file
chezmoi edit ~/.zshrc       # edit the template (not the rendered file)
chezmoi diff                # see what would change
chezmoi apply               # write it out
```

The second laptop came online in a similar fashion on May 8. A couple weeks after that I moved the dotfiles repo to my self-hosted Forgejo and started templating my Claude Code settings per machine as well.

Another big win has been integrating with 1Password CLI, being able to render secrets files on demand when I run chezmoi apply (more on the 1Password op CLI in a future post). chezmoi has [1Password template functions](https://www.chezmoi.io/user-guide/password-managers/1password/) that call the [1Password CLI](https://developer.1password.com/docs/cli/), so the secret never sits in the repo.

## Layers and separation of duties

On the server, chezmoi isn't the only thing installing stuff. For now, ansible handles system packages through apt, and [Homebrew on Linux](https://docs.brew.sh/Homebrew-on-Linux) covers my command line tools. I’m currently on a setup where each layer gets exactly one owner and this seems to be working well:

| Owner | Owns | Examples | Why |
|---|---|---|---|
| apt (via Ansible) | System packages, daemons, kernel modules | docker, zfs, samba, tailscale, nut, smartmontools | Needs root, systemd integration, kernel coupling like ZFS DKMS |
| Linuxbrew | User-level CLI tools where the latest version matters | ripgrep, fd, jq, gh, fzf, bat, eza | Tracks upstream faster, user-scoped, no sudo |
| chezmoi | Dotfiles and shell config | .zshrc, .gitconfig, starship.toml, tmux.conf | One source, rendered per machine |

`~/.linuxbrew/bin` comes before `/usr/bin` in my PATH, so brew "wins" if the tool exists in both places. I don't really like to rely on that though, so if I notice a tool showing up in both places, I'll pick one or the other and remove the other source.

Finally, Ansible is not allowed to deploy dotfiles directly. I tried that before and it broke a few things like credentials, so now Ansible is only allowed to manage system packages and daemons, and chezmoi is only  allowed to write a dotfile.