Syncing Configs Across Machines: Dotfiles at Scale
In this tutorial, you'll learn to sync developer configurations across multiple machines including Git-based sync, cloud storage, dotfile managers, and handling secrets and environment-specific settings.
Why Config Sync Matters
A developer with three machines -- a work laptop, a personal laptop, and a desktop -- faces a problem: every machine needs the same shell config, editor setup, and development tools. Manually copying files is error-prone and outdates quickly. A config sync Strategy ensures every machine stays in sync, new machines are set up in minutes, and you never lose your carefully tuned environment.
By the end of this guide, you will implement a multi-machine config sync system that handles secrets, machine-specific settings, and automatic deployment.
The Config Sync Challenge
As the number of machines grows, so does the complexity of keeping configurations in sync.
flowchart TD A[Config Source of Truth] --> B[Work Laptop] A --> C[Personal Laptop] A --> D[Home Desktop] A --> E[CI Server] A --> F[Dev VM] B --> G[Machine-Specific Overrides] C --> G D --> G G --> H[Environment Variables] G --> I[OS-Specific Settings] G --> J[Work vs Personal Keys]
Strategy 1: Git-Based Sync (Recommended)
This is the most common and reliable approach. A single Git Repository contains all configurations, deployed via a script.
Repository Structure
dotfiles/
├── zsh/
│ ├── .zshrc
│ ├── .zprofile
│ ├── aliases.zsh
│ └── functions.zsh
├── nvim/
│ └── .config/
│ └── nvim/
│ ├── init.lua
│ ├── lsp.lua
│ └── plugins.lua
├── tmux/
│ └── .tmux.conf
├── git/
│ ├── .gitconfig
│ └── .gitignore_global
├── scripts/
│ ├── bootstrap.sh
│ ├── sync.sh
│ └── install-deps.sh
├── machine-specific/
│ ├── work/
│ │ ├── .env.local
│ │ └── .gitconfig-work
│ └── personal/
│ ├── .env.local
│ └── .gitconfig-personal
└── README.md
Bootstrap Script
#!/bin/bash
# bootstrap.sh — Setup a new machine
set -euo pipefail
DOTFILES_REPO="https://github.com/username/dotfiles.git"
DOTFILES_DIR="$HOME/.dotfiles"
echo "Bootstrapping dotfiles..."
# Clone repo
if [ ! -d "$DOTFILES_DIR" ]; then
git clone "$DOTFILES_REPO" "$DOTFILES_DIR"
fi
cd "$DOTFILES_DIR"
# Detect machine type
if [ -n "${WORK_MACHINE:-}" ]; then
MACHINE_TYPE="work"
elif [ -n "${PERSONAL_MACHINE:-}" ]; then
MACHINE_TYPE="personal"
else
echo "Machine type not set. Using 'personal' as default."
MACHINE_TYPE="personal"
fi
# Create symlinks
echo "Creating symlinks..."
# Zsh
ln -sf "$DOTFILES_DIR/zsh/.zshrc" "$HOME/.zshrc"
ln -sf "$DOTFILES_DIR/zsh/.zprofile" "$HOME/.zprofile"
ln -sf "$DOTFILES_DIR/zsh/aliases.zsh" "$HOME/.aliases.zsh"
# Neovim
ln -sf "$DOTFILES_DIR/nvim/.config/nvim" "$HOME/.config/nvim"
# Tmux
ln -sf "$DOTFILES_DIR/tmux/.tmux.conf" "$HOME/.tmux.conf"
# Git
ln -sf "$DOTFILES_DIR/git/.gitconfig" "$HOME/.gitconfig"
ln -sf "$DOTFILES_DIR/git/.gitignore_global" "$HOME/.gitignore_global"
# Machine-specific configs
if [ -f "$DOTFILES_DIR/machine-specific/$MACHINE_TYPE/.env.local" ]; then
ln -sf "$DOTFILES_DIR/machine-specific/$MACHINE_TYPE/.env.local" "$HOME/.env.local"
fi
if [ -f "$DOTFILES_DIR/machine-specific/$MACHINE_TYPE/.gitconfig-$MACHINE_TYPE" ]; then
ln -sf "$DOTFILES_DIR/machine-specific/$MACHINE_TYPE/.gitconfig-$MACHINE_TYPE" "$HOME/.gitconfig-local"
fi
# Install dependencies
bash "$DOTFILES_DIR/scripts/install-deps.sh"
echo "Bootstrap complete! Restart your terminal."
Sync Script
#!/bin/bash
# sync.sh — Pull latest configs and apply
set -euo pipefail
DOTFILES_DIR="$HOME/.dotfiles"
echo "Syncing dotfiles..."
cd "$DOTFILES_DIR"
git pull origin main
# Re-run bootstrap to update symlinks
bash "$DOTFILES_DIR/bootstrap.sh"
echo "Sync complete!"
Strategy 2: Cloud Storage Sync
Use services like Dropbox, Google Drive, or iCloud to sync config files.
Dropbox Example
# Store configs in Dropbox
mv ~/.zshrc ~/Dropbox/Dotfiles/zsh/.zshrc
ln -s ~/Dropbox/Dotfiles/zsh/.zshrc ~/.zshrc
# Works out of the box on all machines with Dropbox
Pros and Cons
| Factor | Git-Based | Cloud Storage |
|---|---|---|
| Version history | Yes | Limited |
| Conflict Resolution | Excellent | Poor |
| Secret handling | Careful needed | Same risk |
| Offline access | Yes (after clone) | Yes (if synced) |
| Multi-machine | Excellent | Excellent |
| Collaboration | Yes (PRs) | Manual |
Handling Secrets Across Machines
Option 1: Environment-Specific Files
# ~/.zshrc — Source local env
if [ -f "$HOME/.env.local" ]; then
source "$HOME/.env.local"
fi
# ~/.env.local (NOT in Git)
export GITHUB_TOKEN="ghp_abc123"
export SSH_KEY_PATH="$HOME/.ssh/id_ed25519"
Option 2: Encrypted Files with Git-Crypt
# Install git-crypt
brew install git-crypt
# Initialize
git-crypt init
# Mark files for encryption
echo "secrets/** filter=git-crypt diff=git-crypt" >}} .gitattributes
# Add file
echo "API_KEY=supersecret" > secrets/.env.local
git add secrets/.env.local
git commit -m "Add encrypted secrets"
# Unlock on another machine
git-crypt unlock /path/to/key
Option 3: Pass (Password Store)
# Install pass
brew install pass gpg
# Initialize store
pass init "your-gpg-key-id"
# Store secret
pass insert dev/github-token
# Retrieve
pass dev/github-token
Option 4: Environment Variables from CI/Secrets Manager
# For secrets managed outside dotfiles
# ~/.zshrc
if command -v vault &> /dev/null; then
export GITHUB_TOKEN=$(vault read -field=token secret/github)
fi
Machine-Specific Configuration
Using Conditionals
# ~/.zshrc — Detect machine and apply config
# Machine-specific Git config
if [ -f "$HOME/.gitconfig-local" ]; then
git config --file "$HOME/.gitconfig-local" --list
fi
# OS-specific settings
case "$OSTYPE" in
darwin*)
alias flushdns='dscacheutil -flushcache'
alias brewup='brew update && brew upgrade && brew cleanup'
;;
linux*)
alias open='xdg-open'
alias pbcopy='xclip -selection clipboard'
alias pbpaste='xclip -selection clipboard -o'
;;
esac
# Host-specific settings
case "$HOSTNAME" in
work-laptop)
export JAVA_HOME="/usr/lib/jvm/java-17-openjdk"
export DOCKER_HOST="unix:///var/run/docker.sock"
;;
personal-mbp)
export JAVA_HOME="/usr/local/opt/openjdk@17"
export DOCKER_HOST="unix:///Users/$USER/.docker/run/docker.sock"
;;
esac
Automatic Sync Triggers
Git Post-Merge Hook
# .git/hooks/post-merge (in dotfiles repo)
#!/bin/bash
# Auto-reapply configs after pull
$HOME/.dotfiles/bootstrap.sh
echo "Configs updated after merge."
Cron Job
# Check for updates daily
0 9 * * * cd ~/.dotfiles && git pull --ff-only && bash Bootstrap.sh >> ~/.dotfiles/sync.log 2>&1
Team Config Sharing
# Shared team config in a company dotfiles repo
dotfiles/
├── team/
│ ├── .editorconfig
│ ├── .eslintrc.json
│ ├── .prettierrc
│ └── .gitconfig-team
├── personal/ # Individual overrides (gitignored)
│ └── .env.local
# ~/.zshrc
source ~/.dotfiles/team/team-aliases.zsh
Common Errors
| Problem | Cause | Fix |
|---|---|---|
| Symlink conflict | File already exists | Backup and replace: mv ~/.zshrc ~/.zshrc.bak |
| Git pull fails with local changes | Uncommitted modifications | Use git stash before pulling, then git stash pop |
| Secret accidentally committed | .env in Git tracking | Use git filter-branch or bfg to remove; add to .gitignore |
| Machine-specific config not applied | Wrong hostname detection | Add a manual MACHINE_TYPE environment variable |
| Symlinks break after directory move | Relative symlinks | Use absolute paths in symlink targets |
Practice Questions
1. What is the recommended approach for syncing dotfiles?
A Git Repository with a Bootstrap script that creates symlinks.
2. How do you handle secrets in a public dotfiles Repository?
Use .env.local files (not tracked), git-crypt for encrypted files, or a password manager like pass.
3. How do you manage OS-specific settings in shared config files?
Use conditional blocks based on $OSTYPE or $HOSTNAME.
4. What is the purpose of a Bootstrap script?
It automates the setup of a new machine: cloning the repo, creating symlinks, and installing dependencies.
5. How do you auto-apply config changes after a git pull?
Use a post-merge Git hook or a cron job that runs git pull && Bootstrap.
Challenge
Create a multi-machine config sync system that: uses a Git Repository with a Bootstrap script, includes machine-specific overrides for work and personal environments, uses .env.local for secrets, has OS-specific settings, and includes an automatic sync trigger (post-merge hook or cron job). Deploy it on at least two machines.
Real-World Task
Audit all the configuration files across your machines. Identify which files differ between machines and which are identical. Create a single Git Repository with your shared configs. Add machine-specific overrides for work vs personal settings. Implement a Bootstrap script and deploy it on a new machine (or VM). Verify that the new machine is fully configured with the same environment in under 10 minutes.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro