Set up a company dotfiles collection
You run Dotfiles Pro on your own machine. Now you want the whole team on it, a shared baseline everyone gets, the company's own dev environment and internal tooling, and each developer's identity and taste left untouched. You do that with a second collection of your own, installed next to the public one. No fork.
This is the three-layer model; the team-fleet provisioning post argues the why. This guide is the how.
| Layer | Owns | Lives in |
|---|---|---|
dotfilespro.roles | Generic developer tooling | the public collection (upstream) |
company.roles | Company-specific roles + baseline defaults | your internal git repo |
| Personal repo | Identity, repo list, personal roles, taste | each developer's own repo |
The dividing line: if another company could use the role unchanged, it belongs upstream (and is worth contributing); if only your company needs it, it goes in company.roles; if only one person wants it, it stays in their personal repo under roles/.
When you need a company collection
A personal repo alone is enough for one person. Reach for a company collection once two or more developers share the same non-generic setup, the shop-platform dev environment, an internal CLI, a company CA, a standard alias set. Putting that in a collection means "everyone add this" becomes one merge request, not a Slack message and twenty manual edits.
The collection repo
A company collection is an ordinary Ansible collection, the same shape as the public one. Create a repo on your internal git host (gitlab.example.com/company/dotfiles) with a galaxy.yml and a roles/ tree:
company-dotfiles/
galaxy.yml
roles/
baseline/ # company-wide defaults (apt mirror, CA, standard aliases)
defaults/main.yaml
tasks/main.yaml
devstack/ # the shop-platform dev environment
defaults/main.yaml
tasks/main.yaml
internal-cli/
tasks/main.yaml
README.md# galaxy.yml: your namespace + name make roles addressable as company.roles.<role>
---
namespace: company
name: roles
version: 1.0.0
description: Company-specific roles, paired with dotfilespro.roles
authors:
- Your Company
readme: README.md
tags:
- dotfiles
- dotfilesproYour roles must meet the plugin requirements so they compose cleanly with the shared ones, the load-bearing ones:
- Tagged tasks: tag every task with the role name, so
dotpro tags devstack(and--tags devstack) targets just that role. - Configuration via
defaults/main.yaml: no hardcoded personal values; everything overridable from a personal repo'shost_vars. - Shell integration only via
~/.profile.d/: never write.bashrc/.zshrcdirectly (see aliases and functions). - Idempotent, local execution as the user, no secrets.
Baseline defaults, not just roles
Company-wide variable values ride in a role's defaults/main.yaml, the lowest precedence, so a developer's host_vars override only what differs. Run that role first and its defaults are available to the shared roles in the same play:
---
# roles/baseline/defaults/main.yaml: company-wide values, each one overridable
git_config:
user:
name: "" # personal repo fills this in
email: ""
git_repositories_path: /projects/
aliases:
deploy: "company-deploy" # an alias everyone getsPair the two collections
Each developer's personal repo installs both collections side by side. The only new line in requirements.yml is your collection:
# requirements.yml (personal repo)
---
collections:
- name: dotfilespro.roles # shared roles (Ansible Galaxy)
version: ">=1.0.0" # upstream: pin a tag, upgrade deliberately
- name: company.roles # your company-specific roles
type: git
source: https://gitlab.example.com/company/dotfiles.git
version: main # yours: track main, merging an MR is the rolloutGit is the default, Galaxy is optional
The requirements.yml above installs the company collection straight from git (type: git): a private GitLab/GitHub repo your team can clone, with no Ansible Galaxy account or publishing step. Pin version: to a branch, tag, or commit SHA; the public collection installs from git the same way.
Publishing to Galaxy is an option, not a requirement, reach for it only if you want versioned releases or public discoverability. Then the entry references the collection by name: and version: instead of the type: git / source: fields.
The playbook mixes roles from both, company baseline first so the shared roles see its defaults:
# playbook.yaml (personal repo)
---
- name: Configure localhost
hosts: localhost
connection: local
gather_facts: true
diff: true
roles:
- company.roles.baseline # company-wide defaults, first
# 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.ssh
- dotfilespro.roles.git
- dotfilespro.roles.docker
- company.roles.devstack # the platform-specific dev environmentBecause all three layers run as one engine in one run, the company baseline and a personal override merge into one deterministic outcome, not whoever wrote the file last. Identity, the repo list, and personal aliases stay in the personal repo's vars:
---
# inventory.yaml or group_vars/all.yaml (personal repo): only what differs
git_config:
user:
name: Alice Example
email: alice@company.example
git_repositories:
shop-acme: git@gitlab.example.com:clients/shop-acme.gitDifferent roles per machine
A fleet is rarely uniform, some developers need the kubernetes tooling, some run WSL, some don't. Instead of a static roles: list, list the ansible role and declare the roles in variables, it applies them for you. Use fully-qualified names so the list can span both collections:
---
# group_vars/all.yaml: the floor every machine in the company gets
ansible_roles_all:
- company.roles.baseline
- dotfilespro.roles.dotpro
- dotfilespro.roles.ssh
- dotfilespro.roles.git---
# host_vars/web-dev-laptop.yaml: this machine's extra roles
roles_extra:
- dotfilespro.roles.docker
- dotfilespro.roles.kubernetes
- company.roles.devstack---
# playbook.yaml: the ansible role applies the configured roles
- name: Configure machine
hosts: all
connection: local
gather_facts: true
diff: true
roles:
# OS base, applied per host from facts (the other role is skipped). Every
# machine needs one, so it's here rather than in the OS-neutral ansible_roles_all.
- role: dotfilespro.roles.ubuntu
when: ansible_facts.os_family == 'Debian'
- role: dotfilespro.roles.macos
when: ansible_facts.os_family == 'Darwin'
- dotfilespro.roles.ansibleansible_roles_all is the company floor (in group_vars/all.yaml); each host adds its own roles in ansible_roles. The role includes ansible_roles_include, the two merged and de-duplicated, with the ansible role filtered out. See Configuration for the full pipeline; the only company twist is fully-qualified names, since the list spans both collections.
Roll out a change
A company-wide change is a merge request to company.roles. Because each personal repo tracks your collection's main, every machine picks it up on its next apply:
dotpro requirements --upgrade # pull the latest company collection
dotpro apply # apply this host
dotpro tags devstack # apply just one role after a targeted changeNo reimaging, no broadcast. Pin the upstream collection to a tag and upgrade it deliberately; track your own main so merging is the rollout. The full pinning gradient (pin what you don't control, track what you do) is in the releases roadmap.
Onboarding a new hire
- Run the company installer (or Ubuntu autoinstall) to get a desktop.
- Copy
examples/single/into a new personal repo, add thecompany.rolesline torequirements.yml, and fill in ten lines of identity vars. - Run the playbook. The shared baseline, the company dev environment, and the client repositories all apply in one pass.
The senior with a lovingly tuned setup keeps it, this layer is an offer, not a mandate.
Next steps
- Hand off from an Ubuntu autoinstall wire day-0 imaging to this day-2 setup
- Twenty laptops, one installer the case for the pattern
- Plugins the full role contract your collection should meet
- Configuration the
single→multiple→manylayouts a personal repo can use - Team provisioning roadmap what's still coming for fleets