The term usually refers to a (shareable) collection of all sorts of user configuration files, often through a VCS such as git.

A collection of dotfiles might help to set up a fresh system, incorporate ideas & changes from different (but similar) systems, or to share these files with other Linux users.

Thoughts and Recommendations for Collecting Dotfiles in a Git Repository-up-

  • You should backup things regularly, but this is not the same as a backup.
  • Do not try to automate the process; add files manually, one by one, sparingly. Consider if you really need to track that particular file. Once a file is added there's very little work involved.
  • Do not add whole directories, even if that means much more initial work.
  • Do not add constantly changing files - they will mess up your repo.
  • Do not add sensitive data (e.g. anything from ~/.ssh) - especially not if you're sharing these files publicly!
  • I have not found a way to track the files directly (unless one wants to turn their whole home folder into a git repository, which I don't), so it will be necessary to replace chosen config files with symlinks to files inside the repository

Create the Local Repository-up-

Create a dedicated directory, e.g. ~/.config/.dotfiles and git init it.

Start copying files into the repo, replacing the sources with symlinks - I use this script:


   tput setaf 9
   echo -e "$@"
   tput sgr0
   red "$@"
   exit 1
red -e "$@"
cat <<EOF

dotfile (verb, transitive)

Takes a list of files on the command line, separates them into readables and
writeables, and adds them to a git repo accordingly:
 - read only: file is copied to repo, no further action
 - read/write: file is copied to repo, original file is moved to a backup in the
   same directory, then replaced with a symlink to the repo file

Only regular files are eligible, no directories, no symlinks, no funny stuff.
exit 1
(( $# == 0 )) && usage Must supply at least one file

sep="$(for ((i=0 ; i<$(tput cols); i++)); do printf ':'; done)"


id="${0##*/}.$(date +%Y%m%d-%H%M%S)"

# two arrays to be filled with files that are writeable
# - those will be added to the repo and replaced by symlinks
# and files that are read-only
# - those will be added to the repo as copies


echo "Dotfile repo: $repo"
[ -d "$repo/.git" ] && git -C "$repo" status || echo_exit_1 "Directory \"$repo\" does not appear to be a git repository's root, or something else is wrong."
echo "$sep"

if [ -d "$repo.bak" ]; then
   while [[ "$answer" != [yn] ]]; do read -rp "Do you want to replace the previous backup $repo.bak? [y/n] " answer; done
   [[ "$answer" == y ]] && rm -rf "$repo.bak" && cp -ra "$repo" "$repo.bak"
   cp -ra "$repo" "$repo.bak"
   echo "First backup of whole repo created at $repo.bak"
echo "$sep"

echo -e "$# files to check/add:\n$@\n$sep"

for file in "$@"; do
   # if file is a symlink, or NOT a regular file (not a directory or socket etc.), or NOT (at least) readable, then move on
   if [ -L "$file" ] || ! [ -f "$file" ] || ! [ -r "$file" ]; then
      not=( "${not[@]}" "$file" )
      # is it also writeable?
      if [ -w "$file" ]; then
         writeables=( "${writeables[@]}" "$file" )
         readables=( "${readables[@]}" "$file" )

(( ${#not[@]} > 0 )) && red "${#not[@]} files were not eligible:\n${not[@]}" && echo "$sep"

if (( ${#writeables[@]} > 0 )); then
   echo -e "${#writeables[@]} files are writeable and will be replaced by symlinks:\n${writeables[@]}\n$sep"

   for file in "${writeables[@]}"; do
      file="$(realpath "$file")"
      [ -d "${file%/*}" ] && mkdir -vp "$repo${file%/*}"

      echo -n "cp: " && cp -aiv "$file" "$repo$file"

      # only when files are identical, move original to backup and create symlink instead
      if cmp --quiet "$file" "$repo$file"; then
         echo -n "mv: " && mv -v "$file" "$file.$id" && \
         echo -n "ln: " && ln -sv "$repo$file" "$file" && \
         backup=( "${backup[@]}" "$file.$id" ) && commit=1 || red \
         "Something went wrong moving and/or symlinking $file"
         red "Files $file and $repo$file are not identical"

   echo "$sep"
   if (( ${#backup[@]} > 0 )); then
      echo -e "${#backup[@]} backups created:\n${backup[@]}"
      while [[ "$answer" != [yn] ]]; do read -rp "Do you want to delete ALL these backups? [y/n] " answer; done
      [[ "$answer" == "y" ]] && rm -I "${backup[@]}"
      echo "$sep"

if (( ${#readables[@]} > 0 )); then
   echo -e "${#readables[@]} files are readable only and will be copied to the repo:\n${readables[@]}\n$sep"

   for file in "${readables[@]}"; do
      file="$(realpath "$file")"
      [ -d "${file%/*}" ] && mkdir -p "$repo${file%/*}"
      echo -n "cp: "; cp -aiv "$file" "$repo$file"

   echo "$sep"

if [[ "$commit" == 1 ]]; then
   git -C "$repo" status
   echo "$sep"
   read -rp "Do you want to add, commit & push now? " answer
   if [[ "$answer" = [yY]* ]]; then
      git -C "$repo" add .
      git -C "$repo" status
      echo "$sep"
      read -rp "Commit message: " answer
      [[ "$answer" == "" ]] && answer="$id" || answer="$answer - $id"
      git -C "$repo" commit -m "$answer"
      git -C "$repo" push origin master

Upload the Repo so that it can be Pushed & Pulled-up-

There's various way to do it, but they all feel clunky to me. Here's how I did it this time:

  • scp the whole local dotfile repo to its central destination
  • Make the remote a bare repo by executing this in place: git config --bool core.bare true
  • Add the remote to your local repo: git remote add origin git:dotfiles.git (this works because "git" is a configured Host in ~/.ssh/config)
  • After making a few changes to the local repo, git push origin master should work. Subsequently, git push should be enough.

Clone the Repo onto another Machine-up-

Just git clone + whatever remote you defined.
You should create a new branch immediately, and switch to it, e.g.:
git checkout -b laptop
Changes made to it can be pushed too:
git push origin laptop
If something changes in the master branch, and you want to incorporate it into your laptop branch:

git checkout master
git pull
git checkout laptop
git rebase master
# or maybe rather
git merge master