GNU Stow
Install and use GNU Stow to manage symlinks for configuration files, dotfiles, and multi-machine setups
What is GNU Stow?
GNU Stow is a symlink farm manager. It creates symbolic links from a source directory into a target directory, letting you keep files organized in one location while they appear in their expected paths.
Stow was originally designed for managing software installed from source in
/usr/local, but it has become the go-to tool for managing dotfiles. Instead of scattering config files across your filesystem, you keep them in a single directory and let Stow create the symlinks.
Prerequisites
None β GNU Stow works with any shell on macOS and Linux. On Windows, use WSL, MSYS2, or Cygwin for a POSIX-compatible environment.
Installation
brew install stow# Debian / Ubuntu
sudo apt install stow
# Fedora
sudo dnf install stow
# Arch
sudo pacman -S stowHow Stow Works
Stow operates on packages β directories that contain files mirroring the structure of a target directory. When you stow a package, Stow creates symlinks in the target so the files appear where they belong.
# Source (your repo)
my-dotfiles/
βββ zsh/
βββ .zshrc
# After: stow -t ~ zsh
~/.zshrc β ~/my-dotfiles/zsh/.zshrc
The key insight: the files inside a Stow package must mirror the directory structure they should have relative to the target directory.
Nested Directories
For configs in subdirectories, replicate the full path inside the package:
my-dotfiles/
βββ starship/
βββ .config/
βββ starship.toml
# After: stow -t ~ starship
~/.config/starship.toml β ~/my-dotfiles/starship/.config/starship.toml
Core Commands
Stow a Package
# Stow into home directory
stow -t ~ zsh
# Stow from a specific source directory
stow -d shared -t ~ zsh
The -d flag specifies the directory containing your packages. The -t flag
specifies the target directory where symlinks are created.
Unstow (Remove Symlinks)
stow -t ~ -D zsh
This removes the symlinks but leaves the original files in your repo untouched.
Re-stow (Update)
stow -t ~ -R zsh
Re-stow is equivalent to unstow + stow. Use it after reorganizing files within a package.
Dry Run (Preview)
stow -t ~ -n -v zsh
The -n flag simulates the operation without making changes. Combined with
-v (verbose), it shows exactly what Stow would do.
Working with Multiple Packages
Stow multiple packages at once:
stow -t ~ zsh git starship
Or use the -d flag to stow from a subdirectory:
# Stow all shared configs
stow -d shared -t ~ zsh ssh git
# Stow machine-specific configs
stow -d machines/my-laptop -t ~ .
Non-Home Targets
So far, every example has used ~ as the target directory. But Stowβs -t
flag can point anywhere β which is useful when configs belong outside your home
directory.
Stow scripts into ~/bin:
# Package structure: scripts/bin/system-update, scripts/bin/docker-cleanup
stow -d shared -t ~ scripts
my-dotfiles/shared/scripts/
βββ bin/
βββ system-update
βββ docker-cleanup
# After: stow -d shared -t ~ scripts
~/bin/system-update β ~/my-dotfiles/shared/scripts/bin/system-update
Stow Docker configs into /opt:
# Package structure: docker/opt/docker-compose.yaml, docker/opt/config/...
sudo stow -d machines/my-server -t / docker
Note: System directories like
/optand/usr/localrequiresudowhen stowing because Stow needs write access to the target.
When Stow Wonβt Work
Some programs cannot follow symlinks β particularly Docker containers running as unprivileged users. If a service runs as a restricted user inside a container, it may not be able to read symlinks that point outside the containerβs filesystem view.
In these cases, use cp to deploy the file but keep the source of truth in
your dotfiles repo:
sudo cp ~/my-dotfiles/machines/my-server/cloudflared/config.yml /opt/cloudflared/data/config.yml
You can wrap these copy commands in a task runner alongside your stow commands so that all deployments stay in one place.
Automating with a Task Runner
As your package count grows, running individual stow commands becomes tedious. A task runner like Just or Make can wrap all your deployment commands into simple recipes:
# Justfile (lives at the root of your dotfiles repo)
# Deploy shared configs
shared:
stow -d shared -t ~ zsh git starship scripts
# Deploy machine-specific configs
machine target:
stow -d machines/{{target}} -t ~ ssh
# Deploy Docker configs to /opt
docker:
sudo stow -d machines/my-server -t / docker
# Copy configs that cannot be symlinked
copy-configs:
sudo cp machines/my-server/cloudflared/config.yml /opt/cloudflared/data/config.yml
# Deploy everything
all: shared (machine "my-server") docker copy-configs
Run with just shared, just all, etc.
Handling Conflicts
Stow will refuse to create a symlink if a real file already exists at the target location:
WARNING! stowing zsh would cause conflicts:
* existing target is neither a link nor a directory: .zshrc
To resolve this, back up or remove the existing file first:
mv ~/.zshrc ~/.zshrc.backup
stow -t ~ zsh
Adopting Existing Files
Use --adopt to move existing files into the Stow package and replace them
with symlinks in one step:
stow -t ~ --adopt zsh
Warning:
--adoptmodifies your package directory by overwriting its files with the targetβs versions. Use with caution β review withgit diffafterward.
File Permissions
Stow creates symlinks, so the original fileβs permissions are what matter. This is particularly important for SSH configs:
# SSH requires strict permissions
chmod 600 ~/my-dotfiles/ssh/.ssh/config
chmod 700 ~/my-dotfiles/ssh/.ssh
The symlink itself does not have separate permissions β it inherits from the target file.
Ignoring Files
Create a .stow-local-ignore file in a package to exclude files from being
stowed:
# .stow-local-ignore
README.md
LICENSE
\.git
Or create a global .stow-global-ignore in your home directory.
Troubleshooting
BUG IN STOW β unexpected file type or broken symlink
This usually means Stow encountered an unexpected file type or a broken symlink. Check for broken symlinks in the target directory:
find ~ -maxdepth 1 -type l -xtype l
Stow creates directories instead of symlinks
This happens when the target directory already exists. Stow will create individual file symlinks inside the directory rather than symlinking the directory itself. This is normally fine β it allows other packages to contribute files to the same directory.
Permission denied when stowing to system directories
System directories like /opt or /usr/local are owned by root:
sudo stow -d machines/my-server -t / docker
Alternatively, change ownership of the target directory to your user if appropriate for your setup.
stow: ERROR: Stow directory does not exist
Ensure you are running the command from the correct working directory, or use
the -d flag to specify the source directory explicitly.
Resources
Official GNU Stow documentation with complete usage reference
A practical introduction to dotfile management with Stow
Step-by-step terminal setup guide covering Stow-managed dotfiles