dotf splits them. Templates go in git. Secret values stay in your password manager. On any machine, dotf sync fetches and injects them.
the problem
with dotf
The problem
You built the perfect shell setup. You want to share it, use it on every machine, clone it in 30 seconds on a new laptop. So you push it to GitHub.
Then you grep your configs and find your email in .gitconfig, an API token in .npmrc, a registry URL with credentials in .cargo/config.toml, an internal hostname in .ssh/config. The repo has to stay private, or the secrets have to come out.
The folk solution is a .localrc or .bash_profile_priv — a file you load but never commit. It works, barely. But every new machine means manually hunting down what goes in it. The one-command setup is now a two-hour session with your old laptop open next to the new one.
Problem: .localrc lives only on one machine. New machine = start from scratch. Nothing documents what secrets are needed or where they come from.
How dotf fixes this
dotf knows which secrets each config needs, where to get them, and how to put everything in place. On any machine.
01 — once per file
dotf config ~/.gitconfig
Shows you the file. You identify the secret values. dotf replaces them with {{PLACEHOLDERS}}, writes the template to your dotfiles repo, and records where each secret lives in your password manager.
02 — everyday
dotf sync
Fetches each secret from your password manager. Renders all templates. Writes the real files — gitignored, never committed. Symlinks them into place. Commits and pushes the templates.
03 — new machine
dotf init
Clones your dotfiles repo. Reads .secrets.toml to check which password manager CLIs are needed. Renders everything. Symlinks everything. Done — no manual hunting.
Install
macOS and Linux via Homebrew. Windows and other platforms via cargo install dotf. Requires whichever password manager CLI you use — install it before running dotf init.
$ brew tap chrisfentiman/dotf
$ brew install dotf
Secret backends
dotf routes each secret by URI scheme. Mix backends in the same .secrets.toml — personal secrets in 1Password, work secrets in Bitwarden, CI values from environment variables.
Proton Pass
pass item get <uri> --fields password
pass://vault/item/field
1Password
op read <uri>
op://vault/item/field
Bitwarden
bw get <field> <item> — requires BW_SESSION
bw://item-name/field
Environment variable
reads from process environment — useful in CI
env://VAR_NAME
Commands
project-local mode
dotf auto-detects scope by walking up from your current directory looking for a .dotf/ directory (like git finds .git/). Templates and URI mappings commit to your project repo. Rendered files with real values stay out.
Every project has config files with secrets: .env with database credentials, .claude/settings.json with API keys, docker-compose.override.yml with registry tokens.
The usual fix? "Ask Sarah for the env file" in Slack. No schema, no validation, no way to know if your copy is stale. New contributors waste time chasing down values that could be fetched from a password manager in seconds.
dotf local mode commits the shape of your config (the template + URI mappings) so every contributor can render their own copy from their own password manager.
~ paths are rejected. A project's .symlinks.toml cannot write outside its own directory.
vs. the alternatives
Every dotfiles tool makes trade-offs. The question is which trade-offs matter for your setup.
| Tool | Templates | Secrets | Symlinks | Project-local | Runtime | Learning curve |
|---|---|---|---|---|---|---|
| dotf | {{VAR}} syntax | Declarative .secrets.toml | .symlinks.toml | Auto-detect .dotf/ | Rust binary | Low |
| chezmoi | Go text/template | Embedded in templates | File copies | -- | Go binary | Medium-high |
| GNU Stow | -- | -- | Symlink farm | -- | Perl | Very low |
| yadm | Jinja2 (limited) | Git-crypt (whole file) | -- | -- | Bash script | Low |
| dotbot | -- | -- | YAML config | -- | Python | Low |
| home-manager | Nix expressions | agenix/sops-nix | Nix-managed | -- | Nix | Very high |
| rcm | -- | -- | Tag-based | -- | Bash | Low |
chezmoi closest competitor
chezmoi is the most feature-complete dotfiles manager available. It has secret integration, run-once scripts, external file fetching, and OS-conditional logic. If you need all of that, use chezmoi.
The trade-off is complexity. chezmoi introduces its own concepts: source state vs. target state, filename-prefix attributes (dot_, private_, run_once_, modify_), and a Go template dialect with Sprig functions. Secrets are embedded directly in templates: {{ (bitwarden "item").password }}. This couples your templates to a specific password manager — switching from Bitwarden to 1Password means editing every template that references a secret.
dotf separates secrets from templates. Your template says {{DB_PASS}}, and .secrets.toml maps it to bw://db/password. Switch password managers by changing one line, not every template. Your dotfiles directory stays a plain git repo — no source-state directory, no filename prefixes, no migration step.
GNU Stow great for simple setups
Stow is pure symlink management. It's elegant, transparent, and has zero learning curve for the core concept: mirror a directory tree as symlinks. If your configs have no secret values — no tokens, no emails, no credentials — use Stow. It's simpler than dotf.
If they do have secrets, Stow has nothing to say. You're back to the .localrc workaround or manually managing secrets outside of git. Stow also has no templating, no diffing, no sync workflow, and no way to handle machine-specific configs. dotf is Stow with a template-render-and-inject step and git sync built in.
yadm git-native approach
yadm is a thin wrapper around a bare git repo. If you think in git, yadm feels natural. Its secret handling is GPG encryption via git-crypt — whole files, all or nothing. You can't encrypt just the email field in a .gitconfig; the entire file becomes an opaque blob. git diff and git log show nothing useful for encrypted files.
Machine-specific configs use "alternate files" — full copies of a file per hostname or OS. This doesn't scale: 5 machines with slightly different .zshrc files means 5 copies to maintain, not 1 template with a variable. dotf doesn't encrypt anything. Your password manager already handles storage and access control. dotf just asks it at sync time, so the readable template stays in git and the real file stays out.
dotbot simple and YAML-driven
dotbot is a YAML-driven symlink + shell command runner. Clean config format, plugin ecosystem, cross-platform. Like Stow, it has no templating, no encryption, no secrets, and no diff/preview. If you just need to declare "these files should be symlinked here" and occasionally run a shell script, dotbot does that well. If any of those files contain values that shouldn't be in git, dotbot can't help.
home-manager different paradigm
home-manager manages dotfiles, packages, services, and system configuration through Nix expressions. It's the most powerful option — fully declarative, reproducible, with atomic rollbacks. It also requires learning the Nix language, which has a notoriously steep curve and famously unhelpful error messages. If you're already invested in the Nix ecosystem, home-manager is excellent. If you just want to manage some config files with secrets, it's like using a bulldozer to plant flowers.
git-crypt / git-secret encryption approach
These encrypt specific files in your repo. The encrypted blob is opaque — git diff shows binary noise, git log shows nothing meaningful. Key management is its own problem: rotate your GPG key and every collaborator's access breaks until you re-encrypt.
The decrypted value still has to go somewhere on the new machine — you've moved the secret-distribution problem, not solved it. dotf routes to your password manager, which you already have on every machine, handles its own access control, and doesn't require GPG key ceremony.
rcm tag-based organization
rcm from Thoughtbot uses a tag-based system to group related dotfiles and supports multiple dotfile directories. Simple shell commands (rcup, lsrc, mkrc). No templates, no secrets, no encryption. Unix only with minimal development activity. Good if you want organized symlinks across personal and work configs, but doesn't address the secret values problem.