# Dotfiles: Version Control Your Environment

> Your shell config, editor settings, and tool preferences are code. Treat them like it.

- Published: 2026-03-14
- Tags: dx, productivity, tools, tutorial
- URL: https://www.woza.ink/blog/dotfiles

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 environment — 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 machine.

---

## stow

[GNU Stow](https://www.gnu.org/software/stow/) is the standard solution to this. It's a symlink manager — it takes a directory tree and creates symlinks into a target directory (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/
            └── config
```

Then running:

```bash
cd ~/dotfiles
stow zsh
```

Creates `~/.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 `ghostty`.

To unstow (remove the symlinks):

```bash
stow -D zsh
```

To restow (useful after moving files around):

```bash
stow -R zsh
```

That's basically the whole API.

---

## Setting up from scratch

Install stow — it's in every package manager:

```bash
# macOS
brew install stow

# Ubuntu / Debian
apt install stow
```

Create your dotfiles repo:

```bash
mkdir ~/dotfiles
cd ~/dotfiles
git init
```

Move a config file into the right package folder, then stow it:

```bash
mkdir zsh
mv ~/.zshrc zsh/.zshrc
stow zsh
# ~/.zshrc is now a symlink to ~/dotfiles/zsh/.zshrc
```

Push to GitHub. On a new machine:

```bash
git clone git@github.com:you/dotfiles.git ~/dotfiles
cd ~/dotfiles
stow zsh git nvim
```

Done.

---

## 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/config`** or whatever terminal you use
- **brew bundle** — a `Brewfile` that lists everything installed via Homebrew, so `brew bundle` restores 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 code. 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 noting.

## Notes

1. **note** on "Together they define your environment": The most-starred dotfiles repo on GitHub is [mathiasbynens/dotfiles](https://github.com/mathiasbynens/dotfiles) with 31K+ stars. Its `.macos` script 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](https://github.com/holman/dotfiles) (topic-based organization) and [thoughtbot/dotfiles](https://github.com/thoughtbot/dotfiles) (vim/zsh/tmux/git baseline with 8K+ stars). (https://github.com/mathiasbynens/dotfiles)
2. **note** on "tedious to maintain, easy to forget a file, and annoying to replicate on a new machine": thoughtbot built [rcm](https://github.com/thoughtbot/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 `~/.dotfiles-local`. (https://github.com/thoughtbot/rcm)
3. **context** on "it takes a directory tree and creates symlinks into a target directory": GNU Stow was originally written in 1993 by Bob Glickstein for managing software installed from source in `/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. (https://www.gnu.org/software/stow/)
4. **counter** on "a server gets git and zsh but not nvim or ghostty": For more complex setups, [chezmoi](https://www.chezmoi.io/) solves problems Stow cannot: templating (different email in `.gitconfig` per 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 require `chezmoi edit` instead of editing files directly. Stow wins on simplicity; chezmoi wins on flexibility. (https://www.chezmoi.io/comparison-table/)
5. **note** on "a Brewfile that lists everything installed via Homebrew": A `Brewfile` is one of the highest-leverage files you can add. Run `brew bundle dump` to snapshot everything currently installed, then `brew bundle` on 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.
6. **context** on "the habit of treating your environment as code": 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 `flake.lock` produces 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. (https://github.com/nix-community/home-manager)
7. **quote** on "if something doesn't have a clean, file-based config, that's worth noting": 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 (https://nix-community.github.io/home-manager/)
