Keep your dotfiles in sync across machines. A Python CLI for backing up, versioning, and restoring your config files with Git-backed storage.
Every developer has been there β you get a new machine and spend hours reconfiguring your shell, editor, git, and tools. dotfile-sync makes it trivial to:
- Track specific dotfiles by registering them in a manifest
- Backup all tracked dotfiles into a Git repository
- Restore your dotfiles on any machine with a single command
- Diff your current dotfiles against the last backed-up version
- List all tracked files and their status
- Handle conflicts β detect when both local and repo versions changed
- Dry-run preview β see what would happen before making changes
- Lifecycle hooks β run custom commands before/after backup/restore
- Ignore patterns β exclude files using
.gitignore-style patterns - Template engine β use Jinja2 templates for machine-specific configs
Unlike complex dotfile managers, dotfile-sync is simple, transparent, and stores your files as plain files in a Git repo β no symlinks, no magic, just Git.
pip install dotfile-syncOr with pipx:
pipx install dotfile-sync# Initialize a new dotfile repository
dotfile-sync init
# Track your bash config
dotfile-sync track ~/.bashrc
# Track your git config
dotfile-sync track ~/.gitconfig
# Track a directory of Neovim config
dotfile-sync track ~/.config/nvim/
# Back up all tracked files
dotfile-sync backup -m "Added neovim config"
# Check what's changed since last backup
dotfile-sync diff
# List all tracked files
dotfile-sync list
# Restore everything on a new machine
dotfile-sync restore
# Restore a single file
dotfile-sync restore --only ~/.bashrc| Command | Description |
|---|---|
init |
Initialize a new dotfile repository in ~/.dotfile-sync |
track <path> |
Add a file or directory to the tracking manifest |
untrack <path> |
Remove a file or directory from the manifest |
list |
Show all tracked files and their sync status |
backup [-m MSG] |
Copy tracked files into the repo and commit |
restore [--only PATH] |
Copy files from the repo back to their original locations |
diff |
Show differences between live files and the last backup |
status |
Show which tracked files have been modified since last backup |
push |
Push the backup repo to its remote |
pull |
Pull changes from the remote and restore |
# Detect conflicts (both local and repo versions changed)
dotfile-sync diff --conflicts
# Resolve conflicts with strategy
dotfile-sync backup --conflict-strategy keep_local
dotfile-sync backup --conflict-strategy keep_repo
dotfile-sync backup --conflict-strategy keep_newer
dotfile-sync backup --conflict-strategy make_backup
dotfile-sync backup --conflict-strategy skip
dotfile-sync backup --conflict-strategy abort# Preview what backup would do
dotfile-sync backup --dry-run
# Preview what restore would do
dotfile-sync restore --dry-run
# Preview with specific file
dotfile-sync restore --dry-run --only ~/.bashrc# Add a pre-backup hook
dotfile-sync hook add pre_backup "echo 'Backing up...'"
# Add a post-restore hook
dotfile-sync hook add post_restore "source ~/.bashrc"
# List all hooks
dotfile-sync hook list
# Remove a hook
dotfile-sync hook remove pre_backup 0# Add an ignore pattern (fnmatch-style)
dotfile-sync ignore "*.local"
dotfile-sync ignore "secrets/"
# List all ignore patterns
dotfile-sync ignore --list
# Remove an ignore pattern
dotfile-sync ignore "*.local" --remove# Track a file as a template
dotfile-sync track ~/.bashrc --template
# Create machine-specific context
dotfile-sync context set machine-work '{"editor": "vim", "theme": "dark"}'
# Render template on restore
dotfile-sync restore --render-
dotfile-sync initcreates~/.dotfile-sync/with:- A bare Git repository for versioned storage
- A
manifest.jsontracking which files to sync and their original paths - Optional
hooks.jsonfor lifecycle hooks - Optional
templates/directory for template files - Optional
contexts/directory for machine-specific variables - Optional
.dotfileignorefor ignore patterns
-
dotfile-sync track <path>adds the absolute path to the manifest -
dotfile-sync backupcopies all tracked files into the repo (preserving directory structure) and commits -
dotfile-sync restorecopies files from the repo back to their original locations -
dotfile-sync push/pullsyncs with a remote Git repository
The manifest maps original paths to repo paths, so you always know where files came from and where they should go.
The manifest (~/.dotfile-sync/manifest.json) stores:
{
"version": "1.0",
"files": [
{
"original_path": "/home/user/.bashrc",
"repo_path": "home/user/_bashrc",
"added_at": "2026-05-17T00:00:00Z",
"is_template": false
}
]
}For machine-specific configurations, use Jinja2 templates:
-
Track a file as a template:
dotfile-sync track ~/.bashrc --template -
This moves
~/.bashrcto~/.dotfile-sync/templates/home/user/_bashrc.tmpland stores a rendered copy in the repo -
Create machine-specific contexts:
dotfile-sync context set machine-work '{"editor": "vim", "theme": "dark", "host": "work-laptop"}' dotfile-sync context set profile-dev '{"python_venv": "~/.venv"}'
-
On restore, templates are rendered with merged context:
dotfile-sync restore --render --machine work --profile dev
Context merge order: default β machine β profile β environment (DOTFILE_SYNC_*) β CLI extra
Available template variables:
machineβ current machine nameprofileβ current profilehomeβ home directoryuserβ usernamehostnameβ hostname- Any variables from context files or environment
| Event | When it runs |
|---|---|
pre_backup |
Before copying files to repo |
post_backup |
After commit |
pre_restore |
Before copying files from repo |
post_restore |
After files restored |
pre_track |
Before adding to manifest |
post_track |
After adding to manifest |
pre_push |
Before git push |
post_push |
After git push |
Hooks receive environment variables:
DOTFILE_SYNC_HOOK_EVENTβ the event nameDOTFILE_SYNC_FILE_PATHβ file being processed (for track/restore)DOTFILE_SYNC_OPERATIONβ operation nameDOTFILE_SYNC_REPO_DIRβ repo directory
Create .dotfileignore in ~/.dotfile-sync/ with patterns:
# Comments start with #
*.local
secrets/
!secrets/important.txt # negation
/tmp/ # only at root
build/ # directories
Built-in patterns: .git/, __pycache__/, *.pyc, .DS_Store, *.swp, *.swo, *~, .dotfile-sync/
The repo is at ~/.dotfile-sync/. Key files:
manifest.jsonβ tracked fileshooks.jsonβ lifecycle hooks.dotfileignoreβ ignore patternstemplates/β Jinja2 template files (.tmplextension)contexts/*.yamlβ machine/profile contextsfiles/β backed-up file copies
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License β see LICENSE for details.