I've been seeing Omarchy all over the place lately and it looks really cool — so I got that familiar Linux distro-hopping itch. There's just one problem: I'd have to migrate my configs. And as of writing this, my last dotfiles commit was back in April 2024.
Part of the reason for the gap was dev burnout from a very long job hunt. I stepped back from tinkering with my tools, left my beloved Neovim behind, and returned to the much less config-heavy VS Code. But that also means when it comes to a fresh distro install, I'm essentially starting from scratch.
My original dotfiles were never very sophisticated. I mainly copied and pasted config files into the right places. I did attempt to write a symlink script at one point, but I was juggling a laptop and a desktop, so there were conditional paths and platform-specific files. I ended up maintaining two separate scripts — one per machine — and eventually just went back to copy-paste because it had less friction.
This time around, I have the benefit of hindsight. I want something structured, portable, and easy to maintain. That's where GNU Stow comes in.
What is GNU Stow?
Stow is a symlink farm manager. You give it a directory of config files organized the way they should appear in your home folder, and it creates the corresponding symlinks for you. The key insight is that you just mirror the structure relative to your home directory inside a ~/.dotfiles/<package>/ folder.
For example, if Starship's config lives at ~/.config/starship.toml, you'd put it at:
~/.dotfiles/starship/.config/starship.toml
Then running stow starship from inside ~/.dotfiles/ creates the symlink ~/.config/starship.toml → ~/.dotfiles/starship/.config/starship.toml.
That's it. Everything stays version-controlled in one repo, and your home directory just holds the links.
Setting Up the Repo
First, install Stow. On Arch-based systems:
sudo pacman -S stow
Then create a dotfiles directory and initialize a git repo:
mkdir ~/.dotfilescd ~/.dotfilesgit init
Organizing Configs by Package
Each "package" in Stow is just a subdirectory. I structure mine around each tool:
~/.dotfiles/├── starship/│ └── .config/│ └── starship.toml├── zellij/│ └── .config/│ └── zellij/│ └── config.kdl├── zsh/│ └── .zshrc├── lsd/│ └── .config/│ └── lsd/│ └── config.yaml└── vscode/└── .config/└── Code/└── User/├── settings.json└── keybindings.json
The directory names are arbitrary — they're just labels for Stow to use. I named them after the tool they configure.
Symlinking Everything
To stow a single package:
cd ~/.dotfilesstow starship
To stow everything at once:
stow */
To remove symlinks (unstow):
stow -D starship
Stow is smart enough to create intermediate directories when needed, and it'll warn you if a symlink target already exists rather than silently overwriting it.
Machine-Specific Configs
This is actually what killed my old approach. When I was juggling a laptop and a desktop, I had platform-specific differences — different PATH entries, different monitor configs, tools only installed on one machine. I ended up with two separate scripts and kept them in sync manually. That got old fast.
With Stow the pattern is to treat each machine's unique config as its own package. So instead of one monolithic zsh package, I'd have:
~/.dotfiles/├── zsh/ # shared across all machines│ └── .zshrc├── zsh-laptop/ # laptop-only overrides│ └── .zshrc_local└── zsh-desktop/ # desktop-only overrides└── .zshrc_local
The shared .zshrc sources .zshrc_local at the bottom if it exists:
[ -f ~/.zshrc_local ] && source ~/.zshrc_local
Then on each machine I stow the shared package plus the right machine-specific one:
# on the laptopstow zsh zsh-laptop# on the desktopstow zsh zsh-desktop
Everything stays in the same repo. No duplicate scripts, no branching, no wondering which machine has the "right" version.
My Current Config Suite
I'm keeping things light for now since I'm essentially starting over. The configs I'm migrating are:
- Starship — my cross-shell prompt. Minimal config, mostly just hiding some default segments I don't care about.
- Zellij — terminal multiplexer. I switched from tmux to Zellij mainly because it displays keybindings right in the TUI, so you don't have to memorize them or keep a cheatsheet open.
- zshrc — aliases, PATH exports, tool initializations.
- lsd — a modern
lsreplacement with icons and color. - VS Code — settings and keybindings. I symlink just those two files rather than the entire VS Code user directory.
- Eldritch theme — my current color scheme. I apply it to everything that supports custom themes: terminal, Zellij, VS Code, Starship.
One Gotcha: Folding
Stow has a behavior called "folding" where if an entire directory can be symlinked as a unit (rather than file by file), it will. This is usually fine but can cause problems if you later add files to that directory outside of Stow's management. You can disable folding with --no-folding if you want Stow to always symlink individual files instead of directories.
What's Next
Getting Neovim back into my workflow is next on the list. I deliberately left it out of this first migration since I want to build my config from scratch rather than copying over something stale. Having the Stow structure already in place means adding it later is just a matter of dropping configs into a new nvim/ package directory and running stow nvim.
The whole point of this setup is that adding configs to a new machine becomes git clone + stow */. No scripts, no manual copying, no wondering where a file lives. That's a good enough reason to finally get this right.
