chezmoi for the files, Dotfiles Pro for the machine: one repo, a dotpro/ subdir
chezmoi is very good at one job: templating and syncing the files in your home directory across machines, from a single Go binary with no runtime to install. What it deliberately does not do is manage the machine around those files. Its own design FAQ says so, beyond a handful of files it points you at Ansible. You do not have to choose. Keep chezmoi's source directory at your repo root, exactly as it is today, and add Dotfiles Pro in a dotpro/ subdirectory for the packages, the system config, and the repositories. One repo, two tools, each doing the job it is best at.
Two tools, two scopes
The two tools own different layers, so let each keep its own:
- chezmoi owns the files in
$HOME. Thedot_bashrc, the.tmplfiles, theprivate_entries, the age-encrypted secrets. This is chezmoi's home turf and nothing here changes. - Dotfiles Pro owns the rest of the machine. apt or Homebrew packages, services and system state, Docker, the repositories you clone. This is the layer chezmoi's FAQ tells you to reach for another tool for.
They meet at exactly one point: the provisioner installs chezmoi and then asks chezmoi to apply the files. chezmoi stays the source of truth for $HOME; Dotfiles Pro just makes sure it is installed and run as part of the same apply.
The layout
chezmoi's source directory sits at the repo root, where chezmoi init expects it. The provisioning layer is a single subdirectory beside it:
~/.dotfiles/
dot_bashrc ← chezmoi source, at the repo root, unchanged
dot_gitconfig.tmpl
private_dot_ssh/
.chezmoiignore ← excludes dotpro/ from chezmoi (see below)
dotpro/ ← the provisioning layer, all Dotfiles Pro lives here
playbook.yaml
requirements.yml
ansible.cfgAnyone who only wants the files uses chezmoi directly and never touches dotpro/:
chezmoi init --apply <this-repo-url>Tell chezmoi to ignore dotpro/
This is the one piece of glue the layout needs. chezmoi maps every entry in its source directory to a target in $HOME, and dotpro/ has no chezmoi prefix, so by default chezmoi would happily create a literal ~/dotpro/ directory. Add one line to .chezmoiignore to keep the provisioning layer out of chezmoi's world:
dotpro/The provisioning layer installs chezmoi and hands off
dotpro/playbook.yaml runs the collection's roles for the machine, then installs chezmoi and applies it against the repo root as the source directory. The --source flag is what lets chezmoi's source live at the root while the playbook runs from dotpro/:
---
# dotpro/playbook.yaml — Dotfiles Pro owns packages, system config, and the repos.
# chezmoi (its source directory is the repo root) owns the files in $HOME. The
# playbook installs chezmoi and then hands the files over to it.
- name: Provision this machine
hosts: localhost
connection: local
gather_facts: true
diff: true
vars:
# Point the dotpro-* day-2 commands at the playbook inside dotpro/
dotpro_playbook: dotpro/playbook.yaml # default: playbook.yaml
dotpro_requirements: dotpro/requirements.yml # default: requirements.yml
dotpro_limit_host: false # default: true; single-host repo
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.git
- dotfilespro.roles.docker
tasks:
- name: Install chezmoi
ansible.builtin.shell: >-
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b {{ ansible_facts.user_dir }}/.local/bin
args:
creates: "{{ ansible_facts.user_dir }}/.local/bin/chezmoi"
# The repo root is chezmoi's source dir; dotpro/ is excluded in .chezmoiignore
- name: Apply the dotfiles with chezmoi
ansible.builtin.command:
cmd: >-
{{ ansible_facts.user_dir }}/.local/bin/chezmoi apply
--source {{ playbook_dir }}/..
register: chezmoi_apply
changed_when: chezmoi_apply.stdout | length > 0Two details make the subdirectory work:
dotpro_playbook: dotpro/playbook.yamltells thedotprorole where the playbook lives, so the day-2dotpro applycommand targetsdotpro/.chezmoi apply --source /..points chezmoi at the repo root, one level up from the playbook, so chezmoi keeps owning$HOMEwhile Dotfiles Pro stays indotpro/. You never duplicate chezmoi's job in Ansible; you just invoke it.
The requirements file is the ordinary one-line collection pin, kept inside dotpro/ with everything else:
collections:
- name: dotfilespro.rolesFirst machine, and every machine after
Bootstrap by cloning and applying the dotpro/ playbook. It installs chezmoi as part of the run, so you do not have to install it first. After the first run the dotpro role has wired the day-2 commands to the right path:
# First machine: clone the repo and apply the playbook inside dotpro/
git clone <this-repo-url> ~/.dotfiles
cd ~/.dotfiles
ansible-playbook dotpro/playbook.yaml
# Day-2: the dotpro role wired the commands to the dotpro/ playbook
dotpro applyThe first apply installs the packages, configures the system, clones your repos, installs chezmoi, and runs chezmoi apply. From then on you have a choice that maps to the split: chezmoi apply when you only changed files, dotpro apply when you changed the machine (and that runs chezmoi too, so it is the safe catch-all).
Or let your agent wire it in
Already in the repo with a coding agent? Paste this to have it scaffold the dotpro/ layer, and adjust the last line if your source layout differs:
Copy this prompt to your coding agent
This repo is a chezmoi source directory at the root. Add Dotfiles Pro in a
dotpro/ subdirectory for the machine layer without disturbing chezmoi. Create
dotpro/ with playbook.yaml, requirements.yml, and ansible.cfg; pin the
dotfilespro collection in requirements.yml; add a "dotpro/" line to
.chezmoiignore so chezmoi skips it; set dotpro_playbook to dotpro/playbook.yaml
so the day-2 commands target dotpro/; and have the playbook install chezmoi and
run `chezmoi apply --source` against the repo root (one level up from dotpro/).
Show me the plan and confirm it with me before you create any files.It does the same work the sections above walk through by hand — the glue is just the .chezmoiignore line and the --source path, which the agent wires while you confirm the plan.
Why keep them separate instead of merging
It is tempting to make Dotfiles Pro template the files as well and drop chezmoi. Resist it if chezmoi is already doing the job. chezmoi's password-manager integrations, its age encryption, and its template functions are a lot to reimplement as role vars, and you would gain nothing for the files that already work. The honest division is the one the tools themselves draw: chezmoi for the files in $HOME, a configuration manager for the machine. The dotpro/ subdir lets you keep both without either one pretending to be the other.
For the full picture of where the two tools differ in capability, see the chezmoi alternative comparison and the scored alternatives tables. This post is just how to run them together in one repository.