Keep your raw dotfiles, add a machine: Dotfiles Pro in the same repo
Most dotfiles repos start the same way: a .bashrc, a .gitconfig, a starship.toml, committed verbatim at the root and dropped into $HOME with a symlink or stow. It is plain, it is readable, and anyone can clone it and use it in ten seconds without installing anything. The day you also want the machine managed (packages, Docker, the repositories you clone, the system config) the usual advice is to convert the whole thing to a provisioning tool. You do not have to. Keep the raw files exactly where they are and add Dotfiles Pro to the same repo. The files stay plain at the root, the provisioning layer sits beside them, and the dotfiles role links the files into place.
Two concerns, one repo
A raw dotfiles repo and a machine provisioner answer different questions, but they do not need separate directories to stay out of each other's way. What keeps them apart is the dotfiles role, which links only the files you name, never the whole tree:
- The raw files own
$HOME's config..bashrc,.gitconfig,.config/..., committed as-is. Their contract is "clone me and link me", no toolchain required. That is what keeps them portable to a server, a container, or a teammate who has never heard of Ansible. - The playbook owns the machine. Packages, system state, the repositories to clone, and putting those raw files in place. Its contract is "apply me", and it needs Ansible, sudo, and a real login session.
Because the dotfiles role links only the paths you list in dotfiles_list, the playbook and its roles/ sit at the root next to your .bashrc without ever being mistaken for a dotfile. No subdirectory needed.
The layout
Everything lives in one repository, flat at the root:
~/.dotfiles/
.bashrc ← raw files, committed verbatim
.gitconfig
.config/
starship.toml
playbook.yaml ← the provisioning layer, beside the files
requirements.yml
ansible.cfgSomeone who only wants the files never runs the playbook. They clone and link the ones they want:
git clone <this-repo-url> ~/.dotfiles
cd ~/.dotfiles
ln -s "$PWD/.bashrc" ~/.bashrc # or stow the files you want(If you stow . the whole repo, add the provisioning files to a .stow-local-ignore so playbook.yaml, requirements.yml, ansible.cfg, and roles/ are not symlinked into $HOME.)
The playbook links the root files
playbook.yaml runs the collection's roles for everything around the files, and lists the raw files in dotfiles_list so the dotfiles role links them from the repo root into $HOME. The files remain the source of truth; the role only puts them in place, the same job stow did above:
---
# 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_list is resolved against dotfiles_dir, which defaults to the repo root (dotpro_path), so the playbook and the files it links stay decoupled. Edit .bashrc at the root and the next apply re-points the link; no template, no re-render, and no inline task to maintain.
The requirements file is the ordinary one-line collection pin:
collections:
- name: dotfilespro.rolesFirst machine, and every machine after
Bootstrap by cloning and applying the playbook. After the first run the dotpro role has wired the day-2 commands, so updates are a single word:
# First machine: clone the repo and apply the playbook at the root
git clone <this-repo-url> ~/.dotfiles
cd ~/.dotfiles
ansible-playbook playbook.yaml
# Day-2: the dotpro role wired the commands to the root playbook
dotpro applyThe first apply installs the packages, configures the system, clones your repos, and links the root files. Re-running is idempotent, it only touches what drifted.
Or let your agent wire it in
You are already in the repo with a coding agent. Paste this to have it add the provisioning layer for you, and edit the last line if your files differ:
Copy this prompt to your coding agent
This is a plain dotfiles repo — raw files (.bashrc, .gitconfig, .config/…)
committed at the root and linked with a symlink or stow. Add Dotfiles Pro to
this same repo without moving those files. Create playbook.yaml,
requirements.yml, and ansible.cfg at the root, pin the dotfilespro collection
in requirements.yml, and set dotfiles_list in the playbook to the raw files I
already keep at the root so the dotfiles role links them into $HOME. Leave the
raw files exactly where they are. Start by listing the dotfiles currently at
the repo root and confirming that list with me before you write anything.The manual steps above and this prompt do the same job; the prompt just hands the repo edits to the agent and keeps you in the loop on which files get linked.
Why not just convert everything
You could fold the files into role vars and let Dotfiles Pro template them all. For a .gitconfig driven by per-machine identity, that is genuinely better, and the git role is built for exactly that. But a hand-tuned starship.toml or a long .bashrc is not improved by becoming a Jinja template, and templating it costs you the thing that made the repo nice: that anyone can read the file and use it without the toolchain.
Listing files in dotfiles_list lets you draw that line per file. Move the files that want to be data into role vars over time; leave the ones that are happiest as plain files at the root. Nothing forces an all-or-nothing migration, and when you want both at once the config roles write through a symlink, so a file the dotfiles role links can still take role-driven keys.
The dividing line
Keep the raw dotfiles at the root, where a bare clone and a symlink still work and your repo stays useful to anyone, toolchain or not. Add the playbook to the same repo, where Ansible installs the packages, configures the system, clones the repos, and links those same files into place. One repository, no subdirectory, and you can adopt the provisioning side without disturbing the files.
The single-machine example and the configuration guide cover the provisioning side in full; this post is just how to keep your plain dotfiles plain while you add it.