What to Put in a .gitignore File
What belongs in the file
Four categories cover almost everything. Dependencies that a package manager can reinstall — node_modules, vendor, a Python virtualenv — because they are large, machine-specific and fully reproducible from a lock file. Build output such as dist, build or target, since it is derived from the source and regenerating it is the point of having a build step.
Then editor and operating system noise: .DS_Store, Thumbs.db, .idea, and most of .vscode. These carry one person's local setup into everyone else's diffs. Finally, and most importantly, anything holding a secret — .env files, private keys, certificates and service account JSON.
Lock files are the common point of confusion, and they are the opposite case: package-lock.json, yarn.lock and Cargo.lock should be committed for applications, because they are what makes an install reproducible across machines.
Why your rule seems to be ignored
The single most common complaint about .gitignore is that a file keeps showing up despite an obviously correct rule. The reason is that .gitignore only applies to untracked files. Once Git is tracking a path, it keeps tracking it regardless of any pattern you add later.
The fix is to remove it from the index while leaving your working copy alone: `git rm --cached path/to/file`, then commit that removal. For a directory, add -r. From that commit onward the ignore rule takes effect.
When a rule genuinely is not matching, `git check-ignore -v path/to/file` tells you exactly which line in which file is responsible — including rules from a global ignore file or a .gitignore in a parent directory, which are easy to forget about.
The pattern syntax in brief
A pattern without a slash matches at any depth, so `build` ignores a build directory anywhere in the tree. A leading slash anchors it to the directory containing the .gitignore, so `/build` matches only the one at the root. A trailing slash restricts the match to directories, so `build/` will not accidentally ignore a file named build.
An exclamation mark negates an earlier rule, which is how the common VS Code pattern works — ignore everything in .vscode, then re-include the couple of files a team wants to share. Negation has one hard limit: you cannot re-include a file if one of its parent directories is excluded, because Git never looks inside an ignored directory. When a negation rule appears to do nothing, an over-broad directory rule above it is nearly always why.
If a secret has already been committed
Removing the file and adding an ignore rule stops it appearing in future commits, but every earlier commit still contains it, and anyone who has cloned the repository has the whole history. A secret that reached a shared branch should be treated as compromised.
Rotate the credential first — that is the step that actually protects you. Purging it from history with a tool such as git-filter-repo is worth doing afterwards, but it rewrites every affected commit, requires a force push, and cannot reach copies that already exist on other machines or in the host's caches.
Frequently asked questions
Should .gitignore itself be committed?
Can I have more than one .gitignore in a repository?
What is the difference between .gitignore and .git/info/exclude?
Try the Gitignore Generator
Pick the languages, frameworks, editors and operating systems in your project and get a combined .gitignore file.