GNU Stow
GNU Stow Install and use GNU Stow to manage symlinks for configuration files, dotfiles, and multi-machine setups
Infrastructure Quest #3 Beginner

GNU Stow

Install and use GNU Stow to manage symlinks for configuration files, dotfiles, and multi-machine setups

stowsymlinksconfigurationinfrastructure
Download as:

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

How 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.

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 /opt and /usr/local require sudo when 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: --adopt modifies your package directory by overwriting its files with the target’s versions. Use with caution β€” review with git diff afterward.

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

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

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

πŸ”—
GNU Stow Manual gnu.org

Official GNU Stow documentation with complete usage reference

πŸ”—
Using GNU Stow to Manage Dotfiles brandon.invergo.net

A practical introduction to dotfile management with Stow

πŸ”—
Mac Terminal Setup with Ghostty and Starship bitdoze.com

Step-by-step terminal setup guide covering Stow-managed dotfiles