configfile / dotfiles from git

Dotfiles, from a git repository you own

Your dotfiles,
on every machine.

configfile links each file of your dotfiles repository where your tools expect it, runs your setup scripts, and keeps every machine in sync with the repository.

Nothing is lost on the way. A file already in place is moved aside, and every deployment can be undone.

brew install mindsers/tap/configfile

macOS and Linux · Homebrew brings Node.js and git

Open sourceApache-2.0v1.1.0

Where your dotfiles live
  1. ~/dotfiles

    Your working copy. You edit, commit and push here, as in any repository.

  2. git push

    github.com/me/dotfiles

    The remote: the reference every machine follows.

  3. configfile update

    ~/.configfile/dotfiles

    configfile’s own copy, made identical to the remote.

  4. configfile modules deploy

    A link into that copy, made once. Each update arrives through it.

Why a copy, and not your working copy?

Your repository, your files

One folder per tool.
Each one says where its files go.

configfile doesn’t store your configuration: it stays in a git repository you own. A module is a folder of files/, and its settings.json lists the module’s files and where each one goes.

~/dotfiles layout
files/
    editorconfig/
    git/
    tmux/
    zsh/
        settings.json
        zshrc
scripts/
    setup.sh
    tmux-plugins.sh
files/zsh/settings.json
{
  "$schema": "https://docs.configfile.sh/schemas/settings.json",
  "files": [
    { "source_path": "zshrc", "target_path": "~/.zshrc", "deploy": "global" }
  ]
}
"global"
Linked where it goes, usually in your home folder. When the repository changes, the link already points to the new version.
"local"
Copied into the folder you’re in, for files a project keeps and may change, such as an .editorconfig.
"none"
Kept in the repository, not deployed.
Modules and settings.json

A new machine

From a fresh install to your setup, in four commands.

Captured from a real run, on a machine that already had its own .zshrc and .gitconfig. Replace the repository URL with yours.

  1. 01 / Connect

    Point it at your repository.

    configfile clones it into its own folder and saves the URL in ~/.configfilerc, readable by you only. Any URL git can clone works.

    configfile init --repo git@github.com:me/dotfiles.git
    ~ init
    $ configfile init --repo git@github.com:me/dotfiles.git
     Info  Cloning git@github.com:me/dotfiles.git into /Users/me/.configfile/dotfiles…
    Cloning into '/Users/me/.configfile/dotfiles'...
    done.
     Done  configfile is ready. Configuration saved to /Users/me/.configfilerc.
    
  2. 02 / Deploy

    Link every module.

    Files in the way are moved aside to .old. The tmux module names a post_deploy script, which runs once its file is in place. Add --dry-run to see all of it first.

    configfile modules deploy --all
    ~ modules deploy
    $ configfile modules deploy --all
     Info  Deploying editorconfig, git, tmux, zsh…
    - /Users/me/.gitconfig (deployed, previous file moved to /Users/me/.gitconfig.old)
    - /Users/me/.gitignore (deployed)
    - /Users/me/.tmux.conf (deployed)
     Info  Running "tmux-plugins", the post_deploy script of tmux…
    Already installed "tpm"
    Installing "tmux-sensible"
      "tmux-sensible" download success
    - /Users/me/.zshrc (deployed, previous file moved to /Users/me/.zshrc.old)
     Done  Deployment finished.
    
  3. 03 / Set up

    Run your setup script.

    Install your tools, set system defaults: whatever files alone can’t do. Scripts live in scripts/, in any language, and know where the repository is.

    configfile scripts run setup
    ~ scripts run
    $ configfile scripts run setup
     Info  Running "setup"…
    Setting up macos from /Users/me/.configfile/dotfiles
     Done  Script "setup" finished.
    
  4. 04 / Later

    Bring in what you pushed.

    Linked files are current as soon as the sync ends. Unpushed work and conflicts in your working copy never block it.

    configfile update
    ~ update
    $ configfile update
     Info  Syncing /Users/me/.configfile/dotfiles…
     Done  Synced with origin/main (cb2db8b). Global files are links, so they are up to date; run "configfile modules deploy --all" for new files.
    

No repository yet? Your first dotfiles repository builds one in ten minutes.

Safe on a machine you care about

Every file it replaces is kept.
Every deployment can be undone.

  • .old

    A file already where yours goes is moved to ~/.zshrc.old first. A link that’s already right is left alone, so deploying twice is safe.

  • --dry-run

    Shows what a deployment would change and which scripts would run, without changing anything.

  • undeploy

    Removes the links and copies configfile made, and puts your previous file back. Your own files stay where they are.

  • target_path

    Your home folder, the repository and configfile’s own files can’t be a target, whatever path reaches them.

~ dry run
$ configfile modules deploy --all --dry-run
 Info  Dry run, nothing is changed. Deploying editorconfig, git, tmux, zsh would do:
- /Users/me/.gitconfig (would be linked, the existing file moved to /Users/me/.gitconfig.old)
- /Users/me/.gitignore (would be linked)
- /Users/me/.tmux.conf (would be linked)
- script "tmux-plugins" (post_deploy of tmux: would run)
- /Users/me/.zshrc (would be linked, the existing file moved to /Users/me/.zshrc.old)
 Done  Dry run finished.
~ undeploy
$ configfile modules undeploy zsh
 Info  Undeploying zsh…
- /Users/me/.zshrc (removed, /Users/me/.zshrc.old restored)
 Done  Undeployment finished.
Safety and trust
See what configfile history recorded for the walkthrough
~ history
$ configfile history
2026-10-14 22:55  init --repo git@github.com:me/dotfiles.git  ok
  cloned    git@github.com:me/dotfiles.git into ~/.configfile/dotfiles
  saved     configuration ~/.configfilerc

2026-10-14 22:55  modules deploy --all  ok
  linked    ~/.gitconfig  (previous file moved to ~/.gitconfig.old)
  linked    ~/.gitignore
  linked    ~/.tmux.conf
  script    tmux-plugins  exit 0  (post_deploy of tmux)
  linked    ~/.zshrc  (previous file moved to ~/.zshrc.old)

2026-10-14 22:55  scripts run setup  ok
  script    setup  exit 0

2026-10-14 22:55  update  ok
  synced    ~/.configfile/dotfiles  with origin/main (21fae97 → cb2db8b)

Script arguments, environment variables and file contents are never recorded, and passwords in URLs are hidden.

Beyond linking files

The rest of a machine’s setup.

post_deploy

Steps around a module.

Install tmux’s plugins once .tmux.conf is linked, remove them before it goes. Each script is named in the module’s settings.json, and one that fails stops the run.

setup.macos.sh

A version of a script for each system.

Next to setup.sh, add setup.macos.sh or setup.linux.sh, and each machine runs its own. Any language with a shebang line.

--local

Project files, copied in.

Copy an .editorconfig into the project you’re in. A copy you changed is only replaced after you say yes, and kept as .old.

$schema

Completion in your editor.

Point a settings.json at its JSON Schema, and VS Code, Zed or a JetBrains IDE suggests the keys and flags a typo as you type.

update

A sync that can’t conflict.

configfile resets its copy to the remote. Anything changed inside it, such as an edit made through a link, is first saved as a patch in ~/.configfile/saved/.

history --json

A record you can query.

One line of JSON per run: what was linked, copied or moved aside, which commits were synced, and each script’s exit code.

See the tmux module from the walkthrough
files/tmux/settings.json
{
  "$schema": "https://docs.configfile.sh/schemas/settings.json",
  "files": [
    { "source_path": "tmux.conf", "target_path": "~/.tmux.conf", "deploy": "global" }
  ],
  "post_deploy": "tmux-plugins"
}
scripts/tmux-plugins.sh
#!/bin/sh
set -e

tpm="$HOME/.tmux/plugins/tpm"
[ -d "$tpm" ] || git clone --quiet https://github.com/tmux-plugins/tpm "$tpm"
"$tpm/bin/install_plugins"

No one at the keyboard

Ready for bootstrap scripts and CI.

Without a terminal, configfile asks nothing: a command that needs an answer fails and names the option to pass instead. It exits non-zero when something fails, so set -e stops at the first problem.

In CI, a dry run in a throwaway home folder reads every module and checks every target: a broken settings.json fails the push, not your next new machine.

Use configfile in scripts and CI
~ without a terminal
$ configfile modules deploy < /dev/null
 Error  No module given. Pass module names, or --all to deploy every module.
See a workflow that checks your dotfiles on every push
.github/workflows/check.yml in your dotfiles repository
name: Check dotfiles

on: [push, pull_request]

permissions:
  contents: read

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
      - run: npm install --global https://github.com/mindsers/configfile/releases/download/1.1.0/configfile-1.1.0.tgz
      # The following steps use a throwaway home folder, not the runner's.
      - run: |
          mkdir -p "$RUNNER_TEMP/home"
          echo "HOME=$RUNNER_TEMP/home" >> "$GITHUB_ENV"
      - run: configfile init --repo "$GITHUB_WORKSPACE"
      - run: configfile modules deploy --all --dry-run

Before you install

Questions worth asking first.

configfile changes files in your home folder. Here is what it does with them, and what it won’t do.

How it works
Does configfile store my dotfiles?

No. They stay in a git repository you own, on GitHub, GitLab or any host git can clone from. configfile keeps a copy of it in ~/.configfile/dotfiles and deploys from there.

Why a copy, and not my working copy?

Deploying from your working copy would let uncommitted edits, a branch in progress or a conflict block every sync, and send changes to this machine before you pushed them. The copy only follows the remote. Don’t edit it: what changes in it, including edits made through a link, is saved as a patch in ~/.configfile/saved/ at the next update, ready for git am in your working copy. How it works.

What happens to the files I already have?

A file, folder or link where a global file goes is moved to .old (then .old.1, .old.2…) before the link is made. A local copy that differs is only replaced after you answer yes, or with --force. configfile modules undeploy puts your previous file back.

Is it safe to deploy someone else’s dotfiles?

Treat it like running their code. The files end up in your shell’s and tools’ configuration, and the scripts run with your permissions, including those a module runs when it is deployed. Read the repository first, and use --dry-run to see which scripts would run, or --no-hooks to run none. Safety and trust.

Which systems does it run on?

macOS and Linux, on x64 or arm64, with git. Installed with Homebrew or npm, it runs on Node.js 24.11 or later, which Homebrew installs with it. The standalone binary brings its own Node.js and needs macOS 13.5 or later, or Linux with the GNU C library 2.28 or later; on Alpine, install with npm. Windows is not supported.

I use configfile 0.3. What changes?

Your repository’s layout and settings.json files work as before, and links made by 0.3 are recognised. If your configuration points at your own working copy, give configfile its own copy first. Upgrading from 0.3.

Is it free?

Yes: free and open source, under the Apache-2.0 license, and built in its author’s free time. If it saves you a setup, you can sponsor its development.

Try it on your dotfiles

Set up your first machine.

Install configfile, point it at your dotfiles repository, and check what it would change before it changes anything.

You’ll need macOS or Linux, git and a dotfiles repository. Installed with Homebrew or npm, configfile also needs Node.js 24.11 or later, which Homebrew installs; the standalone binary brings its own.

1. Install configfile

With Homebrew

brew install mindsers/tap/configfile

Update later with brew upgrade configfile.

On a new machine, without Node.js

curl -fsSL https://docs.configfile.sh/install.sh | sh

Installs the standalone binary for your system in ~/.local/bin, once checked against the release’s SHA256SUMS. Needs macOS 13.5 or later, or Linux with the GNU C library 2.28 or later. Run it again to update.

With npm
npm install --global https://github.com/mindsers/configfile/releases/download/1.1.0/configfile-1.1.0.tgz

Install one way only: two copies would compete in your PATH. Each release comes with its SHA256SUMS and a build provenance attestation: verify a download.

2. Connect your repository

configfile init --repo git@github.com:me/dotfiles.git
configfile modules deploy --all --dry-run
configfile modules deploy --all

Replace the URL with your repository’s. Then run your setup script, if you have one, with configfile scripts run setup.

No repository yet? The ten-minute tutorial