A lightweight shell tool to back up and restore your macOS development environment — dotfiles and Homebrew packages — with support for layered profiles.
Setting up a new Mac means reinstalling apps, copying config files, and remembering what you had. Packer solves this with a single command to snapshot your setup and another to restore it. Profiles let you separate personal configs from work-specific ones and selectively apply what you need.
Packer uses a copy-based sync strategy. backup copies files from your home directory into the data repo. restore copies them back. No symlinks, no magic — just rsync.
The tool itself (packer script) is separate from your data (~/.packer-data/), so you can version-control them independently.
<tool-repo>/ # Tool repo — just the script
└── packer
~/.packer-data/ # Data repo — your configs and Brewfiles
├── base/ # Shared across all machines
│ ├── packer.conf # List of dotfile paths to track
│ ├── Brewfile # Homebrew packages
│ └── dotfiles/ # Backed-up config files
│ ├── .zshrc
│ ├── .gitconfig
│ ├── .config/nvim/
│ └── ...
├── work/ # Company-specific overrides
│ ├── packer.conf
│ ├── Brewfile
│ └── dotfiles/
└── personal/ # Personal machine extras
├── packer.conf
├── Brewfile
└── dotfiles/
# 1. Clone or copy the packer script somewhere in your PATH
cp packer /usr/local/bin/ # or add the repo directory to PATH
# 2. Create your first profile
packer init base
# 3. Add dotfiles to track
packer add base .zshrc
packer add base .gitconfig
packer add base .config/nvim
packer add base .ssh/config
# 4. Back up everything
packer backup
# 5. Version-control your data
cd ~/.packer-data && git init && git add -A && git commit -m "Initial backup"# 1. Install Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 2. Clone your data repo
git clone <your-repo-url> ~/.packer-data
# 3. Get the packer script (from your tool repo or copy it)
# 4. Restore
packer restore base # shared configs + brew packages
packer restore dots work # overlay work-specific dotfilespacker backup # Back up all profiles (dotfiles + Brewfile)
packer backup dots # All profiles, dotfiles only
packer backup brew # All profiles, Brewfile only
packer backup base # Just the base profile
packer backup dots work # Just work profile dotfilespacker restore # Restore all profiles (alphabetical order)
packer restore base work # Restore base, then overlay work
packer restore dots base # Dotfiles only from base
packer restore brew work # Install only work's Brewfile packagesWhen restoring multiple profiles, they are applied in the order specified. Later profiles overwrite earlier ones for overlapping files. This is the layering mechanism — put shared configs in base, machine-specific overrides in work or personal.
Before any restore, packer automatically snapshots your current live files so you can roll back if needed (see Snapshots & Rollback).
packer diff # Show all changes across all profiles
packer diff base # Changes in base profile onlyShows a colorized unified diff between backed-up files and their live counterparts in ~.
packer list # List all tracked paths across all profiles
packer list work # List only work profile pathsShows each tracked path and whether it exists in ~ (HOME) and in the repo (REPO).
packer profiles # List all profiles with summary
packer init <name> # Create a new empty profileEvery restore dots automatically saves a timestamped snapshot of your current live files to ~/.packer-data/.snapshots/ before overwriting anything.
packer snapshots # List all saved snapshots
packer rollback # Roll back to the most recent snapshot
packer rollback 20260401-153012 # Roll back to a specific snapshot
packer -n rollback # Preview what rollback would dopacker add <profile> <path> # Start tracking a path
packer remove <profile> <path> # Stop tracking a pathPaths can be absolute, relative to ~, or use ~ notation:
packer add base .config/ghostty
packer add work ~/.ssh/config
packer add work /Users/$USER/.stCommitMsg| Flag | Description |
|---|---|
-f, --force |
Skip confirmation prompts (useful in scripts) |
-n, --dry-run |
Preview what would happen without making changes |
--data-dir <path> |
Override data directory (default: ~/.packer-data) |
The data directory can also be set via the PACKER_DATA_DIR environment variable.
Each profile's packer.conf is a simple text file — one path per line, relative to $HOME. Comments start with #.
# Shell
.zshrc
.zprofile
.zshenv
# Git
.gitconfig
.gitignore_global
# Editors
.config/nvim
.config/helix
# Terminals
.config/ghostty
.config/weztermThe recommended setup for a work machine:
| Profile | Contains |
|---|---|
base |
Editor configs, terminal configs, shell configs, general CLI tools |
work |
Company-specific git config, SSH config, internal brew taps, VPN tools |
For a personal machine:
| Profile | Contains |
|---|---|
base |
Same shared configs |
personal |
Personal git config, personal SSH keys config, hobby project tools |
Restore only what you need:
# Work laptop
packer restore base work
# Personal laptop
packer restore base personalBrewfiles are manually curated — packer will not silently overwrite them. This prevents accidental loss of a carefully split Brewfile when running packer backup.
When a profile has no Brewfile yet, packer backup brew generates one by dumping all installed packages. You then edit it to keep only what belongs to that profile.
packer init work
packer backup brew work # Creates work/Brewfile with full system dump
# Edit ~/.packer-data/work/Brewfile to keep only work-specific packagesIf you need to regenerate a Brewfile from scratch (overwrites existing):
packer brew-dump base # Prompts before overwriting
packer -f brew-dump base # Force overwrite without promptAfter installing new packages, use brew-diff to see what's not in any Brewfile:
packer brew-diff
# Output:
# brew "newpackage"
# cask "newapp"
# Add these to the appropriate profile's Brewfilebase/Brewfile — tools you use everywhere:
brew "fzf"
brew "ripgrep"
brew "neovim"
brew "starship"
cask "ghostty"work/Brewfile — company-specific:
tap "company/internal"
brew "company-cli"
cask "company-vpn"- bash (v3.2+ — ships with macOS)
- rsync (ships with macOS)
- diff (ships with macOS)
- brew + brew bundle (for Homebrew sync)
No other dependencies. No Python, no Ruby, no Node.
- Run
packer -n restore base workbefore an actual restore to preview what will change - Use
packer diffafter making config changes to see what's drifted since last backup - Keep
~/.packer-dataas a git repo — commit after eachpacker backupto maintain history - Add
alias packer="/path/to/packer"to your.zshrcfor convenience .DS_Storefiles are automatically excluded from all operations