Dotfiles: An Environment That Follows Me
Four years and 168 commits of dotfiles: GNU Stow mirroring $HOME, a Linux-to-macOS migration, a graveyard of Neovim configs, scripts that encode my workflow, and keyboard firmware as config too.
On this page
The first commit in my dotfiles repo is from August 2021 and is called “Current Dotfiles”. The most recent is from October 2025. In between are 168 commits that tell the story of how I like to work: which window manager I was obsessed with, which Neovim distribution I was trying that month, which keyboard I’d just flashed.
I keep dotfiles for one practical reason: a new machine should feel like mine within an hour, not within a month. New laptop, new job, a fresh WSL install on a Windows box — I want to clone one repo and have my shell, editor, terminal multiplexer and scripts exactly where my fingers expect them.
The model: a folder that mirrors $HOME
I use GNU Stow. Each top-level folder in the repo is a “package”, and inside it the files sit at the path they should have relative to your home directory:
.dotfiles/
├── zsh/.zshrc → ~/.zshrc
├── tmux/.tmux.conf → ~/.tmux.conf
├── nvim/.config/nvim/… → ~/.config/nvim/…
├── bin/bin/… → ~/bin/…
└── wezterm/.wezterm.lua → ~/.wezterm.lua
stow zsh creates symlinks from $HOME into the repo. Edit ~/.zshrc and you’re editing the file in git. There’s no
copy step to forget and no sync script to drift.
The install script is tiny. It was inspired by ThePrimeagen’s approach: unstow and restow each package listed in an environment variable.
#!/usr/bin/env zsh
pushd $DOTFILES
for folder in $(echo $STOW_FOLDERS | sed "s/,/ /g")
do
echo "stow $folder"
stow -D $folder
stow $folder
done
popd
A per-OS wrapper picks the defaults, so a minimal Linux box gets just the essentials (lightly simplified):
#!/usr/bin/env zsh
if [ -z "$STOW_FOLDERS" ]; then
STOW_FOLDERS="bin,nvim,tmux,zsh"
fi
if [ -z "$DOTFILES" ]; then
DOTFILES=$HOME/.dotfiles
fi
STOW_FOLDERS=$STOW_FOLDERS DOTFILES=$DOTFILES $DOTFILES/install
Choosing packages per machine is the quiet superpower. A headless server gets four folders; my desktop gets twenty.
From Linux tiling to macOS tiling
The repo is a fossil record. The Linux era (2021–23) is all there: i3 and xmonad as window managers, polybar for the
status bar, picom for compositing, rofi as the launcher, dunst for notifications, eww and conky widgets. Several have a
_laptop twin (polybar_laptop, picom_laptop, dunst_laptop), because a 14-inch screen and a big monitor want
different settings, and a separate Stow package was the simplest way to switch.
Then the macOS era: sketchybar replaced polybar in 2023, WezTerm arrived in 2024, and AeroSpace (a tiling window manager for macOS) landed in October 2025. Different tools, same idea. Every machine I use tiles windows and is driven from the keyboard, which is the setup I describe in Typing Speed and a Keyboard-First Setup.
The Neovim graveyard
Here’s the honest bit. My repo contains nvim, nvim_old, nvim_laptop, lvim, kickstart_nvim, NvChad and
lazyvim: seven Neovim configurations, added between 2021 and 2024.
I could tidy that up, and I probably should delete a few. But I’ve come to like it as a record of experimentation. LunarVim taught me what a batteries-included distribution feels like. Kickstart taught me to understand every line of my config. LazyVim showed how much a well-designed plugin manager changes things. Each experiment left something behind in the config I actually use. Because they’re separate Stow packages, trying a new one never put my working setup at risk.
Scripts that encode a workflow
The bin package is where habits become tools. A few I use constantly:
git-clone-bare-for-worktreesclones a repo as a bare.baredirectory with a.gitfile pointing at it, so every branch can live as a sibling worktree.create-git-worktreefuzzy-finds a project and a branch withfzf, adds the worktree, and installs dependencies in the background, with a desktop notification when they’re done.tmux-git-sessionlists every worktree across my projects, and creates or switches to a tmux session named after the branch.tmux-windowizeropens (or reuses) a named tmux window and sends a command to it.
Together they make a branch feel like a desk you sit down at rather than a state you check out. The full workflow is in Git Worktrees: A Desk per Branch. The point here is that the workflow is versioned: when I improve it, every machine gets the improvement.
Keyboard firmware is config too
The repo also holds a QMK keymap for my Corne (crkbd) split keyboard and tools for an Anne Pro 2. Flashing a board is one command from the README:
qmk flash -kb crkbd -km <keymap> -e BOOTLOADER=atmel-dfu
If the editor config belongs in git, so does the layout of the keys I type it with.
Keeping secrets out
There’s a secrets/ folder, and the only thing in it that git tracks is a .gitignore. Tokens, work credentials and SSH
keys never go in the repo. It’s the same lesson I took from cleaning tokens out of a repo’s history at work
(Removing Secrets from Git History): it’s far easier never to commit a secret than to remove one.
What’s still manual
The README still has a checklist of things to install by hand: a list of yay commands from my Arch days, brew
installs for the Mac, a reminder to untick “Automatically rearrange Spaces” in macOS settings. Stow handles config; it
doesn’t install software or flip OS settings.
That gap is what pushed me towards Provisioning My Laptop with Ansible. And it’s also fine. A setup that’s 80% automated and 100% understood beats one that’s fully automated and fragile.
Progressive overload for your environment
The thing I value most isn’t any single config. It’s the habit of small, continuous improvements: notice a friction, write a script or a keybinding, commit it, and every future machine benefits. One more rep each session. Over four years that adds up to an environment that follows me from job to job and lets me be productive on day one — Progressive Overload and Incremental Migration applied to my own desk.
- #dotfiles
- #gnu-stow
- #neovim
- #tmux
- #developer-experience
- #infrastructure-as-code
↩ Linked from (2)
I've automated my own machine for years (Dotfiles: An Environment That Follows Me, Provisioning My Laptop with Ansible). This note is a proposal, not a case study: what it would look like to take the same idea to a team…
My dotfiles handle configuration (Dotfiles: An Environment That Follows Me), but they don't install anything. In autumn 2022 I was rebuilding Linux machines often enough that the README checklist of "install this, then t…
↪ Links to (5)
Related by topic
- Growing
Books That Shaped How I Build
Career - Growingcase study
Building a Web Platform for 80 Sites
Platform engineering - Seed
Don't Read the Code? Verifying AI-Written Code
Platform engineering - Seed
Low-Code Platforms: Where Do They Belong?
Platform engineering