Manage Dotfiles with yadm
Manage, version, and sync your dotfiles across machines using yadm — covering bare repo setup, OS-specific alternates, classes, GPG encryption, and bootstrap scripts.
Before you start
- ▸Git installed and a basic understanding of git add, commit, and push
- ▸An SSH key or HTTPS credentials configured for your Git host
- ▸A GPG key in your keyring if you plan to use yadm encrypt
- ▸sudo or root access to install the yadm package
Dotfiles — .bashrc, .vimrc, .config/ — drift out of sync the moment you touch a second machine. yadm (Yet Another Dotfiles Manager) solves this without moving your files into a separate directory or forcing symlinks. Under the hood it is a thin wrapper around a bare Git repository rooted in your home directory, so every Git command you already know works exactly the same way.
Install yadm
yadm is packaged in all major distro repositories. Pick your package manager:
# Debian / Ubuntu
sudo apt install yadm
# Fedora
sudo dnf install yadm
# Arch Linux
sudo pacman -S yadm
Confirm the install:
yadm --version
Initialize a Bare Repository
yadm stores its Git objects in ~/.local/share/yadm/repo.git — a bare repo — and treats $HOME as the work tree. You never see a .git folder cluttering your home directory.
yadm init
Now add files exactly as you would with Git:
yadm add ~/.bashrc ~/.vimrc ~/.config/starship.toml
yadm commit -m "Initial dotfiles commit"
Check what is tracked at any time:
yadm status
Push to a Remote
Create an empty repository on GitHub, GitLab, or any Git host, then add it as a remote. Keep it private unless you are certain no secrets are in any tracked file.
yadm remote add origin [email protected]:youruser/dotfiles.git
yadm push -u origin main
Clone Dotfiles on a New Machine
On a fresh system, one command replaces your existing dotfiles with the tracked versions and checks out the work tree into $HOME:
yadm clone [email protected]:youruser/dotfiles.git
If local files already exist and conflict, yadm will warn you rather than silently overwrite. Review conflicts with yadm status, then resolve them the normal Git way (yadm checkout -- <file> to accept the remote version).
Alternate Files: OS- and Host-Specific Configs
A single .bashrc rarely works identically on macOS, Arch, and Ubuntu. yadm solves this with alternates: you commit multiple versions of a file with a naming suffix, and yadm symlinks the correct one at bootstrap time.
The suffix format is:
filename##OS.distro,hostname.mymachine,class.Work
Conditions are matched in order of specificity. A concrete example — two .bashrc variants, one for Arch and one for Ubuntu:
cp ~/.bashrc "$HOME/.bashrc##os.Linux,distro.Arch"
cp ~/.bashrc "$HOME/.bashrc##os.Linux,distro.Ubuntu"
yadm add "$HOME/.bashrc##os.Linux,distro.Arch" "$HOME/.bashrc##os.Linux,distro.Ubuntu"
yadm commit -m "Add distro-specific bashrc alternates"
After cloning or whenever you run yadm alt, yadm evaluates the current system and creates a symlink named ~/.bashrc pointing at the best match:
yadm alt
Available condition tokens include os, distro, hostname, user, and class. Check the full list in the man page (man yadm).
Custom Classes
Classes let you tag a machine with an arbitrary label — Work, Personal, Server — and activate matching alternates. Set the class in yadm's local config (this setting is not committed to the repo):
yadm config local.class Work
Then commit a class-specific file:
cp ~/.gitconfig "$HOME/.gitconfig##class.Work"
yadm add "$HOME/.gitconfig##class.Work"
yadm commit -m "Work gitconfig with corporate email"
yadm alt
The ##class.Work variant is linked only on machines where the class is set to Work. All others fall through to a plain ~/.gitconfig if one is tracked.
Encrypt Secrets with yadm encrypt
SSH keys, API tokens, and similar secrets should not live in plain text in a Git repository. yadm integrates with GPG to encrypt files before committing them.
Step 1 — List files to encrypt
Create (or edit) ~/.config/yadm/encrypt. Each line is a glob relative to $HOME:
cat >> ~/.config/yadm/encrypt << 'EOF'
.ssh/id_ed25519
.ssh/id_ed25519.pub
.netrc
EOF
Step 2 — Encrypt and commit
You need a GPG key already in your keyring. yadm will prompt for which key to use if you have more than one:
yadm encrypt
This creates ~/.local/share/yadm/archive — a GPG-encrypted tar archive. Add that archive to the repo:
yadm add ~/.local/share/yadm/archive
yadm add ~/.config/yadm/encrypt
yadm commit -m "Add encrypted secrets archive"
Step 3 — Decrypt on a new machine
After cloning, import your GPG private key, then:
yadm decrypt
The files listed in encrypt are restored to their original paths with their original permissions.
Bootstrap Script
yadm can run a script automatically after yadm clone. Place an executable script at ~/.config/yadm/bootstrap and commit it. Typical uses: install packages, set up symlinks, run yadm alt.
cat > ~/.config/yadm/bootstrap << 'EOF'
#!/usr/bin/env bash
set -e
yadm alt
# Install packages on Debian/Ubuntu
if command -v apt &>/dev/null; then
sudo apt install -y neovim starship tmux
fi
EOF
chmod +x ~/.config/yadm/bootstrap
yadm add ~/.config/yadm/bootstrap
yadm commit -m "Add bootstrap script"
The script fires automatically on clone. Run it manually any time with yadm bootstrap.
Day-to-Day Workflow
Once set up, working with yadm feels identical to Git:
# Stage a newly edited config
yadm add ~/.config/nvim/init.lua
yadm commit -m "Neovim: enable relative line numbers"
yadm push
# Pull updates on another machine
yadm pull
Verify Everything Is Working
A quick sanity check after setup or cloning:
# List all tracked files
yadm list -a
# Confirm alternates resolved correctly
ls -la ~/.bashrc
# Check the repo log
yadm log --oneline -5
Troubleshooting
- Alternates not switching: Run
yadm altexplicitly. Check that your condition tokens match exactly —distro.Ubuntuis case-sensitive. Verify withyadm introspect distro. - Decrypt fails with "no secret key": Import your GPG private key (
gpg --import private.asc) before runningyadm decrypt. - Clone leaves conflicting files: yadm will not overwrite existing files without confirmation. Use
yadm checkout -- <file>to accept the repo version, or delete the local file first. - SSH key permissions wrong after decrypt: yadm restores permissions stored in the archive. If they are wrong, run
chmod 600 ~/.ssh/id_ed25519and re-encrypt. - Changes to
encryptnot taking effect: Always runyadm encryptagain after editing the encrypt file, then commit the new archive.
Frequently asked questions
- Do I have to move my dotfiles into a special folder?
- No. yadm tracks files in place inside $HOME using a bare Git repository. Your files stay exactly where they are; no symlink farm or separate directory is needed.
- Is it safe to push my dotfiles to a public GitHub repo?
- Only if you are certain no file contains secrets. Keep the repository private by default, and use yadm encrypt for anything sensitive such as SSH keys or API tokens.
- How do alternates differ from templating?
- Alternates let you commit completely separate file variants selected by a condition; yadm symlinks the winner. Templates (using Jinja2 syntax inside a ##template file) generate a single file by substituting variables at runtime. Both solve OS-specific configs — alternates are simpler, templates are more flexible for small differences.
- Can I use yadm alongside an existing bare Git dotfiles setup?
- yadm is itself a bare Git repo wrapper, so you can import an existing bare repo by pointing yadm at it or by cloning it with yadm clone. All existing commits and history are preserved.
- What happens if yadm alt is not run after cloning?
- The alternate source files exist in $HOME but the symlinks are not created or updated. Your config may use a stale or missing symlink. Always run yadm alt (or include it in your bootstrap script) immediately after cloning.
Related guides
Bash Arrays and Associative Arrays
Master bash indexed and associative arrays: declaration, element access, looping, mapfile, namerefs, and practical patterns for real scripting work.
Bash Functions and Variable Scoping
Master Bash function scoping with local variables, source-based libraries, correct use of return codes, and array passing techniques including namerefs.
Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.