Migrate from GNU Stow to Dotfiles Pro
GNU Stow is a clean way to manage dotfiles: keep one folder per program and let stow symlink each one into $HOME. It does one job well, and this guide keeps what you like about it while moving the machine under Dotfiles Pro. You convert package by package, not all at once, and you end up with the packages, templating, per-host config, and secrets a symlink farm cannot give you.
Your Stow repo today
A Stow repo is a set of packages, each mirroring the path it lands at under $HOME:
~/dotfiles/
git/.gitconfig
zsh/.zshrc
tmux/.tmux.conf
nvim/.config/nvim/init.luaYou install a package with stow git (which links ~/dotfiles/git/.gitconfig to ~/.gitconfig), refresh links after edits with stow -R git, and remove them with stow -D git. Everything is a symlink, and the repo is the source of truth.
Two ways to bring a package over
Each Stow package maps to one of two things in Dotfiles Pro.
Settings, where a role already manages the tool
For a program a role configures (git, kubeconfig, starship, and friends), declare the keys instead of symlinking the whole file. Your git/.gitconfig package becomes a few lines of git_config:
- hosts: localhost
# ...
vars:
git_config:
user:
name: Your Name
email: you@example.com
git_repositories:
.dotfiles:
repository: git@gitlab.com:yourname/mydotfiles.git
path: ~/.dotfilesThe git role writes only those keys and leaves the rest of ~/.gitconfig to git itself, so the tool keeps its own defaults and runtime writes across updates. That is the settings, not files idea, and it is the part Stow cannot do.
Verbatim files, with the dotfiles role
For a program with no role, or a file you simply want kept as-is (nvim, tmux), the dotfiles role links it from your repo, the same job stow did, folded into the apply:
---
# playbook.yaml — the provisioning layer, at the repo root beside the raw dotfiles,
# which stay usable on their own (a symlink, stow, or a plain copy). This playbook
# owns everything around them: packages, system config, and the repositories to clone.
- name: Provision this machine
hosts: localhost
connection: local
gather_facts: true
diff: true
vars:
dotpro_limit_host: false # default: true; single-host repo
# The raw dotfiles to link into $HOME. The dotfiles role symlinks each one from
# the repo root (dotfiles_dir defaults to dotpro_path), so there is no inline task
# to write: the files stay the source of truth, the role links them.
dotfiles_list: # default: []
- .bashrc
- .gitconfig
- .config/starship.toml
roles:
# OS base: the matching one runs, the other is skipped (facts decide)
- role: dotfilespro.roles.ubuntu
when: ansible_facts.os_family == 'Debian'
- role: dotfilespro.roles.macos
when: ansible_facts.os_family == 'Darwin'
- dotfilespro.roles.dotpro
- dotfilespro.roles.dotfiles
- dotfilespro.roles.git
- dotfilespro.roles.dockerdotfiles_dir defaults to your repo root, so the entries in dotfiles_list are paths inside the repo. The role links each one into $HOME, backs up a pre-existing real file first, and prunes the link if you later drop the entry.
Step by step
Add the collection. Reference it in your
requirements.yml:yamlcollections: - name: dotfilespro.rolesWrite a playbook beside your packages. List the OS base, the
dotproanddotfilesroles, and any tool roles, then the files to link indotfiles_list(the playbook above is a complete example).Promote the easy settings. Move the packages a role covers (git identity, shell aliases and functions) into role vars. Leave everything else as
dotfiles_listentries for now.Apply, preview first. A dry run shows exactly what each path would become:
bashdotpro apply # install + configure this machine ansible-playbook playbook.yaml --check --diff # or a dry run that changes nothingRetire Stow. Run
stow -Don the packages you have moved over once, or just let thedotfilesrole adopt the paths, it backs each one up before taking it. From then on the apply manages the links, notstow.
Conflicts Stow refused, the dotfiles role handles
Stow stops the moment a real file already exists at the target:
$ stow git
WARNING! stowing git would cause conflicts:
* existing target is neither a link nor a directory: .gitconfigSo you move conflicting files aside by hand before every install. The dotfiles role does that for you: the first time it adopts a path, it backs up any real file to <path>.dotpro.bak (unless you set dotfiles_backup: false) and then takes over. No manual shuffling, and dropping an entry prunes its link instead of leaving it dangling.
What you gain
- Packages, not just files. The same apply that links your configs installs the tools they configure and clones your repositories. No separate
setup.shbeside the symlinks. - Per-host and per-OS. Config is computed from your vars, so one declaration renders a work identity under
~/work/, a different value on macOS, or a host-specific tweak, with no preprocessor. - Secrets. Pull them at apply time with
ansible-vaultor thebitwardenrole rather than committing them next to the symlinks. - Idempotent reconcile. Re-run any time; it changes only what drifted, and
--diffpreviews it. Install and update are one command.
Keep what you liked about Stow
This is not all-or-nothing. Your repo stays a plain git repo, files you want kept verbatim stay verbatim through the dotfiles role, and you migrate one package at a time. The difference is that the files now arrive with the whole machine, and the configs a program co-writes are managed as settings rather than frozen whole.
Next steps
- GNU Stow vs Dotfiles Pro the focused comparison
- dotfiles role link, copy, or template modes, backup, and pruning
- Manage your dotfiles across machines the backup, restore, and sync workflow end to end
- Configuration organize vars by machine count, and vary roles per host