Ubuntu and macOS on one team: a shared setup without a shared OS
Most teams do not run one operating system. The backend developers are on Ubuntu because it matches production; the rest are on macOS because that is what the company ships. Both camps are happy, and then every shared-setup decision quietly forks: two onboarding docs, two setup.sh scripts, a shell tweak that only helps half the team. The operating system is a personal choice, but the standard does not have to fork with it. One Ansible dotfiles collection can give both camps the same shell, the same aliases, and the same apply workflow, with the OS handled underneath.
The split that quietly taxes a team
A mixed fleet is the normal case, not the exception, and the cost is rarely the OS itself. It is the divergence around it:
- Onboarding forks. "Set up your machine" becomes a Linux path and a Mac path, maintained by whoever happens to be on each.
- "Works on my machine." A fix lands for the people who share your OS and silently skips everyone else, so the same bug gets solved two or three times.
- Knowledge does not transfer. Pair across the divide and the muscle memory breaks: different package managers, different shell setup, an alias that exists on one side only.
None of that is about Ubuntu or macOS being wrong. It is about there being no single answer to "how do we do this here?" that holds on both.
The OS is a layer, not the standard
The collection treats the operating system as one swappable layer. A small OS-neutral base owns the shell framework and the generic tooling; thin per-OS roles (ubuntu, macos) add only what is specific to apt or Homebrew; and the OS base is selected from gathered facts, so the same playbook applies on either machine with no per-person edits:
---
# 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.ansibleOn a Mac the macos role runs (Homebrew); on Ubuntu the ubuntu role runs (apt). The matching one runs and the other is skipped, decided by ansible_facts.os_family, so nobody has to remember to change the file for their machine.
Everyone shares the part that matters
What collaboration actually depends on is not the kernel. It is the interface: the commands you type, the aliases you share, the way you ship a change. Those are the same on both operating systems:
- The
~/.profile.d/shell framework, thesystem/listhelpers, thekube-namespacecontext switching, and thedotproapply commands are identical. git,docker, andkubernetesare configured the same way and driven by the same commands.- Applying and re-applying is one workflow everywhere (
dotpro apply).
What is genuinely OS-specific is handled automatically and stays out of sight: apt versus Homebrew, ~/.profile versus ~/.zprofile plus ~/.zshrc, the locale, and the Docker daemon (colima on macOS, behind the same docker CLI and docker compose).
One baseline, two teammates
The shared roles live in one place; each developer adds only what differs. A baseline every machine gets:
---
# group_vars/all.yaml — OS-neutral roles every machine shares. The OS base
# (ubuntu/macos) is not here; the playbook applies it per host from facts.
ansible_roles_all:
- dotpro
- ssh
- git
- starship
git_config:
user:
name: Your Name
email: you@personal.comThen one file per teammate, a Linux developer and a Mac developer, each adding their own roles and identity onto the same baseline:
---
# host_vars/ubuntu-laptop.yaml: your daily dev box
shell: zsh # off-diagonal: zsh on Linux (defaults to bash; macOS defaults to zsh)
roles_extra:
- docker
- terraform
- opentofu---
# host_vars/work-mac.yaml: macOS, work identity (the macos base is applied
# automatically from facts; this host only adds its extra roles and identity)
roles_extra:
- docker
- kubernetes
- aws
git_config:
user:
email: you@work.com # work email on this machine only
git_repositories:
work-project: git@github.com:yourorg/work-project.gitThe OS base is not even listed here. It is picked from facts when the playbook runs, so a new teammate's first apply works whether they open a ThinkPad or a MacBook, and the baseline reads the same to everyone.
Collaboration, not standardization
Once the standard is shared, the day-to-day stops branching by OS:
- A fix ships once, a merge request to the shared (or company) collection, and every machine picks it up on its next update, Ubuntu and macOS alike. No "and on a Mac, also do this" footnote.
- Onboarding is a single path: install, copy the starter, fill in a few vars. The same instructions hold on both.
- Pairing across the divide feels the same: same prompt, same
kube-namespace, same aliases. The OS stops being a variable in "how do I do X here?".
It is a floor, not a fence. Personal taste still lives in each developer's own vars and roles, and the expert with a lovingly tuned setup keeps it. What goes away is twenty people re-solving the same problems twice, once per platform.
What it does not try to make identical
Honesty matters more than a clean pitch. This is a shared interface, not two pixel-identical machines:
- GUI apps and OS preferences stay per platform:
macos_defaultsand casks on a Mac, apt packages on Ubuntu. They are declared per OS, not pretended away. - The container runtime genuinely differs underneath (colima versus
docker.io), even though the CLI you type matches.
The goal is not to erase the difference between the two operating systems. It is to put one shared standard on top of them, so the choice of OS stays personal and stops leaking into how the team works together.
Try it on your team
The cross-platform guide walks the structure end to end: one repo, a shared baseline, per-machine roles, and the OS base selected automatically (it covers WSL too, which is just Ubuntu under Windows). Point a teammate on Ubuntu and a teammate on macOS at the same repo and compare notes. That is the whole idea.
Let the operating system be a personal choice. Keep the standard shared.