My Dotfiles, Explained
Every developer's machine is different. Here's what I use and why.
Shell — zsh
My .zshrc is short. I don't use frameworks like oh-my-zsh — just a few lines:
export EDITOR="nvim"
export PATH="$HOME/.local/bin:$HOME/go/bin:$PATH"
# History
HISTSIZE=50000
SAVEHIST=50000
setopt SHARE_HISTORY
setopt HIST_IGNORE_DUPS
# Prompt
autoload -Uz vcs_info
precmd() { vcs_info }
zstyle ':vcs_info:git:*' formats ' %b'
PROMPT='%F{blue}%~%f%F{yellow}${vcs_info_msg_0_}%f %F{magenta}❯%f '
# Aliases
alias g="git"
alias gs="git status -sb"
alias gd="git diff"
alias gl="git log --oneline -20"
alias ll="ls -la"
alias ..="cd .."
alias ...="cd ../.."
Editor — Neovim
Minimal init.lua:
vim.g.mapleader = " "
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.tabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.smartindent = true
vim.opt.wrap = false
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.termguicolors = true
vim.opt.signcolumn = "yes"
vim.opt.clipboard = "unnamedplus"
-- Lazy.nvim bootstrap
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git", "clone", "--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup({
{ "catppuccin/nvim", name = "catppuccin", priority = 1000 },
{ "nvim-treesitter/nvim-treesitter", build = ":TSUpdate" },
{ "nvim-telescope/telescope.nvim", dependencies = { "nvim-lua/plenary.nvim" } },
{ "lewis6991/gitsigns.nvim", config = true },
})
vim.cmd.colorscheme("catppuccin")
-- Keymaps
vim.keymap.set("n", "<leader>ff", "<cmd>Telescope find_files<cr>")
vim.keymap.set("n", "<leader>fg", "<cmd>Telescope live_grep<cr>")
vim.keymap.set("n", "<leader>fb", "<cmd>Telescope buffers<cr>")
Git
.gitconfig:
[user]
name = Your Name
email = you@example.com
[init]
defaultBranch = main
[push]
autoSetupRemote = true
[pull]
rebase = true
[alias]
co = checkout
br = branch
ci = commit
st = status -sb
lg = log --graph --oneline --decorate -20
undo = reset --soft HEAD~1
amend = commit --amend --no-edit
[core]
editor = nvim
pager = delta
[delta]
navigate = true
side-by-side = true
line-numbers = true
Tmux
.tmux.conf:
set -g default-terminal "tmux-256color"
set -ag terminal-overrides ",xterm-256color:RGB"
set -g mouse on
set -g base-index 1
set -g pane-base-index 1
set -g renumber-windows on
set -g history-limit 50000
# Prefix
unbind C-b
set -g prefix C-a
bind C-a send-prefix
# Splits
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
# Vim-style pane navigation
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
# Status bar
set -g status-style "bg=default,fg=white"
set -g status-left "#[fg=blue,bold] #S "
set -g status-right "#[fg=yellow] %H:%M "
Docker
A Dockerfile I reuse for Node.js services:
FROM node:22-slim AS base
WORKDIR /app
FROM base AS deps
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM base AS build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM base AS runtime
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]
CSS I always start with
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
input, button, textarea, select {
font: inherit;
}
The philosophy
Keep it simple. Every line in a dotfile should exist for a reason you can explain. If you copied it from a blog post three years ago and don't know what it does, delete it.
Your tools should make you faster, not more complicated.