Notes
Migrating from Manually Managed Dotfiles to Chezmoi (Ongoing)
Updated: September 4, 2025
Notes
chezmoi is a dotfiles management tool, but it does not work by simply placing symbolic links. It can make a simple dotfiles setup unnecessarily complicated, so it should be introduced with some care.
Goal
To support additional Ubuntu machines, I am migrating from manually managed dotfiles (~/dotfiles deployed into my home directory via symbolic links, together with shell scripts) to a unified, cross-platform setup using chezmoi.
The final goal is to manage both Ubuntu and macOS configurations in a single repository.
Current Setup
Before introducing chezmoi, my dotfiles repository looked roughly like this:
~/dotfiles/
├── home # configuration files
├── scripts # shell scripts
└── others
It stored dotfiles and scripts for operating them.
Files under home were deployed into $HOME through symbolic links.
This structure is simple, but
- branching by OS (split with
ifstatements), - managing secrets such as SSH keys and tokens,
- and automating initialization on new machines
all looked annoying enough that I decided to move to
chezmoi. (It is not strictly necessary, but I also wanted to learn how it works.)
What Is Chezmoi?
chezmoi is a dotfiles management tool written in Go.
Its main features include cross-platform support, templating, and encrypted secret management.
Since it handles dotfiles management for you, it saves you from writing your own shell-script-based setup from scratch.
Migration Flow
Bring in the Repository
chezmoi init $GithubUserName
This clones $GithubUserName/dotfiles.git into $HOME/.local/share/chezmoi.
Then delete the configuration files under chezmoi/home as a cleanup step. (This is only the directory cloned by chezmoi, so it does not affect the original repository itself.)
rm -rf $HOME/.local/share/chezmoi/home/.*
Change the Source Directory
By default, chezmoi expects dotfiles to live at the root of the repository, but I want them under home, so:
chezmoi cd
touch .chezmoiroot
Create .chezmoiroot and change the source directory. Its contents are simply:
home
Import Existing Dotfiles
chezmoi add --follow ~/.bash_profile
chezmoi add --follow ~/.zshrc
chezmoi add --follow ~/.zprofile
chezmoi add --follow ~/.profile
chezmoi add --follow ~/.gitconfig
chezmoi add --follow ~/.vimrc
chezmoi add --follow ~/.clang-format
chezmoi add --follow ~/.condarc
chezmoi add ~/.config/karabiner
chezmoi add ~/.config/nvim
chezmoi add <file> places the specified file under chezmoi management. Since my home directory already contains symbolic links, I use the --follow option to import the actual target file behind each link. (For directories, however, --follow cannot be used.)
Import Secrets
I use Bitwarden, so I integrate Bitwarden CLI with chezmoi to manage secrets in encrypted form. See the official documentation for details.
brew install bitwarden-cli # on macOS
bw login # log in to Bitwarden
bw unlock # unlock Bitwarden if prompted
After unlocking, you get a session key, which should be set as the BW_SESSION environment variable:
export BW_SESSION="xxxxxxxxxxxxxxxx"
With this session key set in the environment, chezmoi can retrieve secrets from Bitwarden.
Incidentally, if you write the following in your chezmoi configuration (chezmoi.toml or .json), chezmoi will automatically call bw unlock when BW_SESSION is not set:
{
"bitwarden": {
"unlock": "auto"
}
}
chezmoi secret bitwarden init
Result:
$HOME/.local/share/chezmoi/
└── dot_zshrc
For more information about chezmoi, see the official documentation.
Handling Existing Symbolic Links
If you already have symbolic links pointing to ~/dotfiles,
chezmoi add ~/.zshrc will follow the link and import the real file,
so you can run add without deleting the link first.
Migration flow:
chezmoi init
chezmoi add ~/.zshrc
chezmoi add ~/.config/nvim
chezmoi diff
chezmoi apply
After the migration is complete, remove the symbolic links that are no longer needed:
find $HOME -type l -lname '*dotfiles*' -delete
Branching by OS
Use Chezmoi’s template mechanism (.tmpl).
Example: dot_zshrc.tmpl
{{ if eq .chezmoi.os "darwin" }}
# macOS-specific
export PATH="/opt/homebrew/bin:$PATH"
{{ else if eq .chezmoi.os "linux" }}
# Linux-specific
export PATH="/usr/local/bin:$PATH"
{{ end }}
When chezmoi apply runs, the content is expanded automatically according to the OS.
Separating Secrets
chezmoi can manage secret files in encrypted form using the encrypted_ prefix:
chezmoi add --encrypt ~/.ssh/config
chezmoi secret add github_token
Encryption can be handled with either GPG or age.
Unified Structure for Ubuntu and macOS
An example of the final structure of $GithubUserName/dotfiles.git:
dotfiles/
├── dot_zshrc.tmpl
├── dot_gitconfig
├── private_dot_ssh/
│ └── config.age
├── run_once_install-packages.sh.tmpl
└── README.md
run_once_*.share setup scripts that run automatically on first execution.- By embedding OS branching in
.tmplfiles, the same repository can reproduce both environments.
Workflow
-
Set up a new environment:
chezmoi init $GithubUserName chezmoi apply -
Apply changes:
chezmoi edit ~/.zshrc chezmoi diff chezmoi apply -
Push to GitHub:
chezmoi cd git add . git commit -m "Update macOS zshrc" git push origin main
Future Extensions
- Automatically install brew/apt packages with
run_oncescripts - Branch inside templates based on environment variables
- Roll dotfiles forward with
chezmoi update