Dotfiles: Version Control Your Environment
Your shell config, editor settings, and tool preferences are code. Treat them like it.
Every time you set up a new machine, you have two options. Spend a day and a half reinstalling things, tweaking configs, and trying to remember that one zsh alias you spent twenty minutes perfecting. Or clone a repo and be back to your exact environment in ten minutes.
Dotfiles are how you get the second option.
What dotfiles are
Dotfiles are the config files that live in your home directory, named with a leading dot so they stay hidden by default. .zshrc, .gitconfig, .vimrc, .config/nvim/, .ssh/config. Every tool you use has one.
Together they define your environment1 — how your shell behaves, how your editor looks, what aliases you've built up, how git formats output. They're personal infrastructure. Most developers let them accumulate on a single machine and lose them when something breaks.
Versioning them in a git repo means your environment is reproducible, diffable, and shareable.
The naive approach and why it breaks
The obvious move is to make ~ a git repo. It works until it doesn't — git status in your home directory becomes noise, you accidentally commit things you shouldn't, and the repo scope bleeds everywhere.
The slightly better approach is symlinking: keep your dotfiles in ~/dotfiles/, and manually ln -s ~/dotfiles/.zshrc ~/.zshrc for each file. This works. It's also tedious to maintain, easy to forget a file, and annoying to replicate on a new machine2.
stow
GNU Stow is the standard solution to this. It's a symlink manager — it takes a directory tree and creates symlinks into a target directory3 (your home folder by default), mirroring the structure exactly.
You structure your dotfiles repo like this:
~/dotfiles/
├── zsh/
│ └── .zshrc
├── git/
│ └── .gitconfig
├── nvim/
│ └── .config/
│ └── nvim/
│ └── init.lua
└── ghostty/
└── .config/
└── ghostty/
└── configThen running:
cd ~/dotfiles
stow zshCreates ~/.zshrc → ~/dotfiles/zsh/.zshrc. One command per package. Or stow */ to stow everything at once.
The package structure (one folder per tool) means you can selectively stow only what you need on a given machine — a server gets git and zsh but not nvim or ghostty4.
To unstow (remove the symlinks):
stow -D zshTo restow (useful after moving files around):
stow -R zshThat's basically the whole API.
Setting up from scratch
Install stow — it's in every package manager:
# macOS
brew install stow
# Ubuntu / Debian
apt install stowCreate your dotfiles repo:
mkdir ~/dotfiles
cd ~/dotfiles
git initMove a config file into the right package folder, then stow it:
mkdir zsh
mv ~/.zshrc zsh/.zshrc
stow zsh
# ~/.zshrc is now a symlink to ~/dotfiles/zsh/.zshrcPush to GitHub. On a new machine:
git clone git@github.com:you/dotfiles.git ~/dotfiles
cd ~/dotfiles
stow zsh git nvimDone.
What to put in there
The obvious ones: .zshrc / .bashrc, .gitconfig, editor config, terminal config.
A few less obvious ones worth including:
.gitignore_global— a global gitignore for.DS_Store,.env, editor swap files.ssh/config— host aliases and per-host settings (not the keys themselves, obviously)- shell functions and aliases — if you've split these into separate files sourced from
.zshrc .config/ghostty/configor whatever terminal you use- brew bundle — a
Brewfilethat lists everything installed via Homebrew5, sobrew bundlerestores your packages on a new machine
What to leave out: secrets, tokens, anything machine-specific that shouldn't roam.
The compounding return
The lasting benefit is the habit of treating your environment as code6. You start reviewing changes before committing them. You write comments explaining why an alias exists. You see the history of how your workflow evolved.
It's also a surprisingly good signal when evaluating tools — if something doesn't have a clean, file-based config, that's worth noting7.
Notes
- 1Note
The most-starred dotfiles repo on GitHub is mathiasbynens/dotfiles with 31K+ stars. Its
source →.macosscript that sets "sensible hacker defaults" for macOS is legendary -- it automates hundreds of system preferences that would take hours to configure manually. Other influential repos: holman/dotfiles (topic-based organization) and thoughtbot/dotfiles (vim/zsh/tmux/git baseline with 8K+ stars). - 2Note
thoughtbot built rcm specifically to solve the dotfile management problem without requiring the directory-mirroring structure that Stow demands. It handles host-specific and tag-based overrides natively. Their dotfiles repo is designed as a shared baseline that the whole team uses, with personal overrides layered on top via
source →~/.dotfiles-local. - 3Context
GNU Stow was originally written in 1993 by Bob Glickstein for managing software installed from source in
source →/usr/local/stow/. Its use for dotfiles management is a creative repurposing of a tool designed for a completely different problem. The entire implementation is a single Perl script with no dependencies beyond core Perl -- which is why it's available in every package manager and never breaks. - 4Counter
For more complex setups, chezmoi solves problems Stow cannot: templating (different email in
source →.gitconfigper machine), secrets management (integrates with 1Password, Bitwarden, etc.), and cross-OS differences. The trade-off is that chezmoi uses a source-of-truth copy rather than symlinks, so edits requirechezmoi editinstead of editing files directly. Stow wins on simplicity; chezmoi wins on flexibility. - 5Note
A
Brewfileis one of the highest-leverage files you can add. Runbrew bundle dumpto snapshot everything currently installed, thenbrew bundleon a new machine to restore it all. Combined with Stow for configs, this covers ~90% of machine setup. The pattern: Brewfile for packages, dotfiles for configs, and a small bootstrap script to glue them together. - 6Context
The next evolution beyond dotfiles + Stow is Nix with Home Manager. Instead of managing symlinks to config files, you declare your entire environment -- packages, configs, shell setup -- in a single Nix expression. Builds are reproducible down to the byte: the same
source →flake.lockproduces the exact same environment on any machine. The learning curve is steep, but for developers who manage multiple machines or want CI-testable environments, it is the endgame. - 7Quote
Unlike dotfiles repositories, Home Manager supports specifying programs as well as their configurations. Contents are reproducible -- a home will be the exact same every time it is built, and you can have the exact same home on different hosts.
— Home Manager documentation
source →