Every developer has been there: you spend time getting your terminal just right — your prompt, your aliases, your editor config — and then you set up a new machine and have to do it all over again. Or worse, you make a tweak on one machine and forget to replicate it on the other.
My setup lives in a single Git repository. One git pull and stow */ and any machine is identical to the rest.
How it works Link to heading
Config files on Unix-like systems (the ones that control your shell, editor, terminal, etc.) are typically hidden files in your home directory — things like ~/.zshrc or ~/.config/nvim. The trick is to store them in a Git repo instead and symlink them back to where the tools expect them.
For the symlinking I use GNU Stow, a small tool that takes a directory and symlinks its contents into a target directory (your home directory by default). So if I run stow nvim inside my dotfiles repo, it creates ~/.config/nvim pointing back into the repo. Edit the file anywhere and both locations see the change.
The repo ends up looking like this:
~/.dotfiles/
zsh/
.zshrc
.zshenv
nvim/
.config/nvim/
tmux/
.config/tmux/
...
Each tool gets its own directory. To apply one: stow nvim. To apply everything at once: stow */.
Zsh Helpers Link to heading
One .zshrc for every machine sounds good until a tool isn’t installed somewhere and it errors on startup. To avoid that I use two one-line helpers:
eval_if_exists() { (( ${+commands[$1]} )) && eval "$("$@")" }
source_if_exists() { [[ ! -f "$1" ]] || source "$1" }
eval_if_exists only evaluates if the command exists:
eval_if_exists starship init zsh
eval_if_exists zoxide init zsh
eval_if_exists mise activate zsh
source_if_exists only sources if the file exists:
source_if_exists "$HOME/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh"
source_if_exists "$HOME/.zshrc.local"
~/.zshrc.local is where machine-specific config goes — work credentials, paths that only exist on one machine. It’s never committed to the repo.
You probably don’t need Oh My Zsh Link to heading
Most articles on zsh setup will point you toward Oh My Zsh for plugins. It works, but it’s a lot of framework for what most people actually use it for — autosuggestions and syntax highlighting.
I use Git submodules instead:
git submodule add https://github.com/zsh-users/zsh-autosuggestions zsh/.zsh/zsh-autosuggestions
git submodule add https://github.com/zsh-users/zsh-syntax-highlighting zsh/.zsh/zsh-syntax-highlighting
Then source them with source_if_exists:
source_if_exists "$HOME/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh"
source_if_exists "$HOME/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
No plugin manager, no extra tooling. That’s also why the clone command uses --recursive — it pulls the submodules along with the repo.
Setting up a new machine Link to heading
git clone --recursive https://github.com/your/.dotfiles
cd .dotfiles
stow */
That’s it. Everything is symlinked, plugins are pulled in via Git submodules, and anything machine-specific goes into .zshrc.local.