Through the years, I’ve constructed up a set of aliases, shell capabilities, and CLI instruments that make my terminal really feel like house. All of it lives in a single repository: my dotfiles.
It is a backup of each terminal instrument and configuration I depend on, and it means I can arrange a model new Mac from scratch in about 5 minutes. Colleagues at Spatie use it as a place to begin for their very own setups too.
Let me stroll you thru what’s in there. I will cowl the instruments and tips first, with set up and setup additional down.
What are dotfiles?
Dotfiles are configuration information that sometimes stay in your house listing and begin with a dot: .zshrc, .gitconfig, .vimrc, and so forth. They management how your terminal, shell, editor, and numerous command-line instruments behave. The dot prefix makes them hidden by default on macOS and Linux, which is the place the identify comes from.
The concept behind a dotfiles repository is straightforward. You set all these configuration information in a git repo, then symlink them to the place they should be in your machine. In case you get a brand new laptop computer or have to arrange a recent setting, you clone the repo, run the installer, and every part is strictly the way in which you prefer it inside minutes.
I’ve steadily changed most conventional Unix instruments with sooner, extra trendy alternate options. My aliases deal with the switchover transparently:
if command -v eza &> /dev/null; then alias ls="eza --icons --group-directories-first" alias l="eza -la --icons --group-directories-first --hyperlink" alias lt="eza --tree --level=2 --icons" fi if command -v bat &> /dev/null; then alias cat="bat --style=plain" fi if command -v rg &> /dev/null; then alias grep="rg" fi
This is what ls seems like with eza: colorized output, file kind icons, and directories grouped first. A lot simpler to scan than a plain wall of filenames:

The command -v checks imply these aliases solely activate when the instrument is definitely put in. On a machine with out them, I nonetheless get the usual variations.
This is what every instrument does:
eza replaces ls with colorized output, file icons, and a tree view. bat replaces cat with syntax highlighting. ripgrep replaces grep and is extremely quick at looking out via giant codebases. fd replaces discover with a a lot friendlier syntax. zoxide replaces cd with sensible listing leaping, which I will get to in a minute. And delta makes git diffs really readable with side-by-side views and syntax highlighting.
Delta is the one which stunned me most. This is a plain git diff with it configured because the pager:
Discover that it highlights the components of a line that modified, not simply the road itself. On the signature it marks the added : void, and additional down it marks precisely the place now()->addSeconds(20) grew to become $socialDelay. Plain git diff would paint these entire traces purple and inexperienced and depart you to identify the distinction your self.
All of those are Rust-based, which implies they’re quick. Noticeably quick, even on giant initiatives.
Leaping between initiatives with z
Of every part on this submit, zoxide is my favorite. It offers you a z command that remembers each listing you’ve got visited. As a substitute of typing a path, you kind a fraction of the listing identify, and zoxide takes you to the very best match.
On any given day I bounce between 5 – 6 initiatives. With z, that appears like this:
z ohdear z mailcoach z dotfiles z flare z freek
These instructions take me to ohdear.app, mailcoach.app, my dotfiles, flareapp.io, and again to freek.dev. It does not matter the place I’m after I kind them. No paths, no tab completion, no cd ../../.. to climb out of a subdirectory first.
Watch what occurs after I land in flareapp.io: fnm notices the undertaking’s Node model and switches to it robotically. That is the --use-on-cd flag doing its work, and it triggers on a z soar simply as it could on a cd.
Zoxide ranks directories by a mix of how usually and the way not too long ago you go to them. The initiatives I work on day by day win over those I opened as soon as final yr, so the fragments I kind can keep brief. If a fraction is ambiguous, zi opens an interactive picker as an alternative.
Wiring it up takes one line in .zshrc:
eval "$(zoxide init zsh)"
That is the entire setup. Zoxide builds its database as you go, so it will get higher the extra you employ it. For the primary day or two you will nonetheless attain for cd, and then you definately’ll by no means give it some thought once more.
Shell configuration
The center of the setup is .zshrc. It hundreds Oh My Zsh with a custom-made agnoster theme (a well-liked Powerline-style immediate that exhibits your present listing and git standing at a look), then sources three separate information: .exports for setting variables, .aliases for shortcuts, and .capabilities for extra advanced shell capabilities.
Splitting issues up like this retains every file targeted and simple to navigate. After I wish to add a brand new alias, I do know precisely the place to go.
Aliases
Aliases are the short wins that prevent keystrokes each single day. In case you work with Laravel, these are particularly helpful:
alias a="php artisan" alias mfs="php artisan migrate:recent --seed" alias nah="git reset --hard;git clear -df" alias c="claude" alias cy="claude-yolo"
a turns any artisan command into one thing brief. a make:mannequin Publish -m simply flows higher than typing out php artisan each time.
mfs rebuilds your complete database from scratch: drops all tables, runs each migration, and seeds. That is why I do not trouble with down migrations. When your workflow is “nuke it and rebuild,” rollbacks change into pointless.
nah might be my most-used alias. Modified one thing, realized it was a nasty thought? Sort nah and every part is again to the final commit. It is the “undo every part” button to your working listing.
c and cy are for Claude Code. cy is brief for claude-yolo, which runs Claude Code with --dangerously-skip-permissions. I will be trustworthy: cy is the one I take advantage of most as of late. 😅
Composer will get the identical therapy. 4 aliases cowl every part I do with it daily:
alias cu="composer replace" alias cr="composer require" alias ci="composer set up" alias cda="composer dump-autoload -o"
For git, I maintain it easy:
alias push="git push" alias pull="git pull" alias gpo="git push origin" alias uncommit="git reset --soft HEAD~1"
uncommit is a type of aliases you do not want usually, however while you do, you are glad it is there. It undoes the final commit however retains all of your modifications staged, able to be dedicated once more. Helpful while you dedicated too early, wish to reword the message, or want to separate one commit into two.
The small aliases that earn their maintain
The shortcuts that save me essentially the most time are the straightforward ones. I kind them dozens of instances a day with out pondering, and that is precisely the place the worth is.
alias o="open ." alias hostfile="sudo vi /and so on/hosts" alias sshconfig="vi ~/.ssh/config" alias flushdns="sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder" alias flush-redis="redis-cli FLUSHALL" alias ip="curl ifconfig.me/ip ; echo"
o opens the present listing in Finder. It saves possibly two seconds, however I take advantage of it always, and two seconds many instances a day is price a one-line alias. That is the entire calculation. Frequency beats sophistication.
hostfile and sshconfig exist as a result of I may by no means keep in mind whether or not it was /and so on/hosts or /and so on/hostfile, and since ~/.ssh/config is simply annoying to kind. flushdns is the macOS incantation for clearing the DNS cache, which you want roughly every year and might by no means recall while you do.
Opening the present undertaking in an editor will get an alias per editor:
alias phpstorm='open -a ~/Functions/PhpStorm.app "`pwd`"' alias vscode='code "`pwd`"' alias zed='open -a /Functions/Zed.app "`pwd`"'
For Laravel work, tunnel shares my native website on a public URL via Valet, at all times on the identical subdomain so the URL stays steady throughout restarts:
alias tunnel='valet share -subdomain=freekmurze -region=eu'
And two aliases for toggling hidden information in Finder, which is the type of factor you lookup each single time in any other case:
alias present="defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder" alias disguise="defaults write com.apple.finder AppleShowAllFiles -bool false && killall Finder"
There’s one alias in my .aliases that is not a shortcut in any respect:
alias sudo='sudo '
That trailing area is deliberate. Usually the shell will not increase an alias that seems after sudo, so sudo l fails with “command not discovered.” The trailing area tells zsh to test the following phrase for an alias too. A one-character repair for a papercut I hit for years earlier than I realized about it.
Customized capabilities
For something extra advanced than a easy alias, I take advantage of shell capabilities. My .capabilities file accommodates a handful of those.
The p operate runs assessments. It detects whether or not the undertaking makes use of Pest or PHPUnit and calls the proper one:
operate p() { if [ -f vendor/bin/pest ]; then vendor/bin/pest "$@" else vendor/bin/phpunit "$@" fi }
I may also filter assessments with pf:
operate pf() { if [ -f vendor/bin/pest ]; then vendor/bin/pest --filter "$@" else vendor/bin/phpunit --filter "$@" fi }
When a set will get sufficiently big that I discover the wait, pp runs it in parallel:
alias pp="php artisan take a look at --parallel"
The mkd operate creates a listing and strikes into it. Making a listing you do not then wish to enter is uncommon sufficient that I’ve stopped typing the 2 instructions individually:
operate mkd() { mkdir -p "$@" && cd "$@" }
git-prune-local deletes each native department whose distant counterpart is gone. After a number of months of merging pull requests, git department turns into unreadable, and this cleans it up in a single go:
operate git-prune-native() { git fetch -p && git department -vv | grep ': gone]' | awk '{print $1}' | xargs git department -D }
clone is a two-line operate that saves me from ever typing a GitHub URL once more. It palms off to the GitHub CLI, which accepts the proprietor/repo shorthand:
operate clone() { gh repo clone "$1" "${@:2}" }
So clone spatie/laravel-permission is all I would like.
digga prints the DNS information for a site in a format I can really learn, as an alternative of the wall of textual content that plain dig offers you:
operate digga() { dig +nocmd "$1" any +multiline +noall +reply }
The db operate is a wrapper round MySQL for fast database operations:
operate db() sed 's/[
So db create myapp creates a database, db refresh myapp drops and recreates it, and db listing exhibits all databases. No want to recollect the MySQL CLI syntax.
scheduler runs Laravel’s scheduler in a loop, the way in which cron would in manufacturing. Helpful while you’re constructing one thing scheduled and wish to watch it hearth with out ready on the actual factor:
operate scheduler() { whereas :; do php artisan schedule:run echo "Sleeping 60 seconds..." sleep 60 completed }
My commit operate might be essentially the most fascinating one. I wrote about it individually, however the brief model is: it makes use of Claude to generate commit messages from the git diff. Sort commit with no arguments and also you get a descriptive message as an alternative of my outdated behavior of writing “wip” for every part.
Right here I delete composer.json and kind commit with no arguments. The spinner runs whereas Claude reads the staged diff, and a second later the commit lands with “Delete composer.json” as its message.
That is the place uncommit earns its place. Once in a while Claude writes a message that misses the purpose of the change. Typing uncommit rewinds that commit and leaves every part staged, so I can run commit once more for a second try, or hand over and write the message myself. The 2 aliases work as a pair: one guesses, the opposite lets me take the guess again.
Git configuration
My .gitconfig is price a glance too. Essentially the most impactful setting is configuring delta because the default pager:
[core] pager = delta [interactive] diffFilter = delta --color-only [delta] navigate = true gentle = false side-by-side = true line-numbers = true
This offers me side-by-side diffs with line numbers and syntax highlighting for each git diff, git log -p, and git present command, which is what you noticed within the video close to the highest of this submit. Going again to the default diff output feels painful after utilizing this.
I even have a international gitignore to maintain .DS_Store, .thought, node_modules, and different junk out of each repository without having per-project ignore guidelines. The concept is straightforward: as an alternative of including the identical entries to each undertaking’s .gitignore, you configure one file that applies to all repos in your machine. It retains your undertaking gitignore information targeted on project-specific issues, and also you by no means by chance commit .DS_Store once more.
The set up script
My dotfiles repo has a bin/set up script that handles the complete setup. Run it on a recent Mac and about 5 minutes later, every part is strictly the way in which you prefer it.
It begins by putting in Oh My Zsh (a framework for managing your Zsh configuration with plugin assist and themes) and creating all the mandatory symlinks so your config information find yourself the place the system expects them.
Then it runs Homebrew to put in every part declared in a Brewfile, units up PHP extensions, configures Node.js by way of fnm, and installs international Composer packages like Valet and Pint. On the finish, it optionally units up Claude Code with all of your expertise and brokers.
The symlink step is the core of the installer. It connects every config file within the repo to the place the system expects it:
ln -sf ~/.dotfiles/house/.zshrc ~/.zshrc ln -sf ~/.dotfiles/house/.gitconfig ~/.gitconfig ln -sf ~/.dotfiles/config/ghostty/config ~/.config/ghostty/config
The identical sample applies to Zed, Vim, and every part else. The profit: while you edit ~/.zshrc, you are really enhancing the file inside ~/.dotfiles/, so each change is robotically tracked by git. No copying information round, no syncing, no forgetting to replace the repo after a tweak. And since it is all in git, your settings are backed up by default. Tweak an alias, push, and it is protected. Get a brand new laptop computer, clone the repo, and also you’re proper again the place you left off.
I take advantage of Ghostty as my terminal, and its configuration is managed via dotfiles too. The config is minimal: it units the working listing to my code folder, makes use of a block cursor, and tweaks one palette coloration.
The Brewfile
All my Homebrew packages are declared in a single Brewfile. That is the canonical listing of every part I would like put in on a recent Mac:
# Trendy CLI instruments brew "zoxide" brew "bat" brew "eza" brew "ripgrep" brew "fd" brew "git-delta" brew "fnm" brew "fzf" brew "jq" brew "yq" brew "backside" # Improvement brew "php" brew "composer" brew "mysql" cask "claude-code" cask "zed"
Working brew bundle --file=~/.dotfiles/config/Brewfile installs every part in a single go. Including a brand new instrument is simply appending a line and working that command once more.
This is what every of these trendy CLI instruments does:
- zoxide – A better
cd. It learns which directories you go to and allows you to soar to them by partial identify.z dotfilesfrom anyplace takes you straight to~/.dotfiles. - bat –
catwith syntax highlighting and line numbers. Makes studying information within the terminal really nice. - eza – A contemporary
lswith icons, colours, tree view, and git standing integration. - ripgrep –
grep, however quick. Respects.gitignoreby default and searches via giant codebases in milliseconds. - fd – A less complicated
discover.fd migrationbeatsdiscover . -name '*migration*'each time. - git-delta – Stunning side-by-side diffs with syntax highlighting. Configured because the default git pager (see the git part above).
- fnm – Quick Node.js model supervisor. Routinely switches Node variations while you
cdright into a undertaking with a.node-versionfile. - fzf – A fuzzy finder for every part. Press
Ctrl+Rand search via your command historical past interactively as an alternative of mashing the up arrow. - jq – Command-line JSON processor. Pipe any API response via
jqand get readable, colorized, filterable output. - yq – The identical factor as
jq, however for YAML information. - backside – A graphical course of/system monitor. I’ve it aliased as
htopandprime.


Claude Code configuration
Since I wrote about my Claude Code setup in a separate submit, I will maintain this transient. My dotfiles additionally version-control my whole Claude Code configuration: the worldwide CLAUDE.md directions, {custom} expertise, {custom} brokers, and settings. All of it’s symlinked from ~/.dotfiles/config/claude/ to ~/.claude/.
This implies after I clone my dotfiles on a brand new machine, my Claude Code setup comes alongside for the journey. Identical brokers, similar expertise, similar tips, able to go instantly.
Customization with out conflicts
One sample I like is the assist for a ~/.dotfiles-custom listing. My .zshrc sources information from this listing in the event that they exist:
for file in ~/.dotfiles-custom/shell/.{exports,aliases,capabilities,zshrc}; do [ -r "$file" ] && [ -f "$file" ] && supply "$file" completed
This implies I can have machine-specific configuration that by no means will get dedicated to the dotfiles repo. Work-specific API keys, aliases for a selected undertaking, or the rest that should not be shared publicly.
In closing
Dotfiles are a type of issues that appear like overkill till you set them up. Then you definately surprise the way you ever lived with out them. The preliminary funding pays off each time you open a terminal, and it actually pays off the day you arrange a brand new machine.
My whole setup is public at github.com/freekmurze/dotfiles. Be at liberty to fork it and make it your personal. That is the great thing about dotfiles: everybody’s are completely different, tailor-made to how they work.
If you wish to see extra about the remainder of my growth setting, together with my editor, macOS apps, and {hardware}, take a look at my makes use of web page.

