every developer accumulates configuration. shell aliases, git settings, editor preferences, terminal colors. these files start on one machine and eventually need to exist on another - a new laptop, a work machine, a server. the common approach is a git repository with an install script that copies or symlinks everything into place.
the install script works until it doesn’t. it grows conditionals for different operating systems, handles edge cases around existing files, and becomes its own maintenance burden. gnu stow replaces that script with a single concept: a directory tree that mirrors the target, turned into symlinks by one command.
the short version
stow takes a directory - called a package - and creates symlinks from its contents into a target directory. the package’s internal structure mirrors where the files should end up. a file at zsh/.zshrc becomes a symlink at ~/.zshrc. no configuration file, no mapping, no script. the directory structure is the configuration.
how stow works
stow operates on packages. each package is a directory whose contents mirror the structure of the target directory. by default the target is the parent of the stow directory. given a dotfiles directory at ~/.dotfiles with this structure:
~/.dotfiles/
├── git/
│ └── .gitconfig
├── zsh/
│ └── .zshrc
├── tmux/
│ └── .tmux.conf
└── nvim/
└── .config/
└── nvim/
└── init.lua
running stow git from inside ~/.dotfiles creates a symlink from ~/.gitconfig to ~/.dotfiles/git/.gitconfig. the nested structure in the nvim package means stow nvim creates ~/.config/nvim/init.lua pointing back into the dotfiles directory. stow handles the intermediate directories automatically.
organizing packages
the power of stow is that each tool’s configuration is its own self-contained package. adding a new tool means creating a directory, placing the config files where they’d normally live relative to home, and running stow.
the typical dotfiles repository has a package per tool:
~/.dotfiles/
├── zsh/
│ └── .zshrc
├── git/
│ └── .gitconfig
├── tmux/
│ └── .tmux.conf
├── nvim/
│ └── .config/
│ └── nvim/
│ └── init.lua
├── ssh/
│ └── .ssh/
│ └── config
└── starship/
└── .config/
└── starship.toml
each package maps to one tool. this separation means removing a tool’s configuration is stow -D toolname - it removes the symlinks without touching the repository. switching machines means cloning the repo, installing stow, and running one command.
how many packages and which tools they cover depends on the workflow. a frontend developer’s dotfiles look different from an infrastructure engineer’s. the structure is the same regardless.
bootstrapping a new machine
the full setup sequence for a new machine:
sudo apt install stow
git clone git@github.com:user/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
stow zsh git tmux nvim
each argument after the target is a package name. stow processes them in order, creating symlinks for every file in each package. --dir and --target can be omitted when running from inside the dotfiles directory and targeting its parent, but being explicit avoids mistakes.
four commands from a bare machine to a configured environment. the order matters only when packages depend on each other - a shell config that sources a file placed by another package needs both stowed before opening a new session.
what to watch out for
existing files block stow. if ~/.gitconfig already exists as a regular file, stow refuses to overwrite it. the fix is to back up or remove the existing file first. stow is deliberately conservative - it will never silently replace a file that isn’t already a symlink it manages.
nested directories can conflict. if two stow packages both create files under ~/.config/, stow handles this automatically through tree unfolding - it replaces the directory symlink with a real directory and creates individual symlinks inside it. conflicts arise when a non-stow-managed file or symlink is already in the way. keeping packages granular - one tool per package - avoids most of these issues.
stow doesn’t handle secrets. api keys, tokens, and credentials should not be in the dotfiles repository. sensitive values should live in a ~/.secrets file sourced by the shell but excluded from version control, or injected at runtime through another tool.
the default target is the parent of the stow directory. placing dotfiles at ~/.dotfiles means the default target is ~/, which is the expected behavior. placing dotfiles elsewhere requires explicit --dir and --target flags on every command. pick a location and stick with it.
.stow-local-ignore replaces the default ignore list. by default stow ignores version control directories like .git. adding a .stow-local-ignore file in a package overrides these defaults entirely, so you need to re-include the vcs patterns alongside your own exclusions like README.md or LICENSE.
references
[1] gnu stow documentation. “gnu stow manual.”
gnu.org/software/stow/manual
[2] gnu stow documentation. “resource files.”
gnu.org/software/stow/manual/html_node/Resource-Files.html
[3] tmux documentation. “tmux manual.”
man.openbsd.org/tmux
[4] git documentation. “git-config.”
git-scm.com/docs/git-config