The Complete Guide to .gitignore Files

What .gitignore actually does, how its pattern syntax works, and how to fix it when a file gets tracked anyway.

What a .gitignore file does

A .gitignore file tells Git which files and directories to leave out of version control. It doesn't hide files from your filesystem or from other tools — it only affects git status, git add, and git commit. Anything matching a pattern in .gitignore is skipped when Git decides what's "untracked."

The file itself is just plain text, one pattern per line, placed at the root of your repository (or in any subdirectory, where its rules apply from that point down). Git reads it automatically — there's nothing to configure.

Why it matters

Without a .gitignore, three categories of files routinely end up committed by accident:

  • Build output — dist/, build/, compiled binaries — regeneratable, and bloats the repo with every rebuild.
  • Dependencies — node_modules/, vendor/ — can be thousands of files and is reinstallable from a lockfile.
  • Secrets and local config — .env, API keys, IDE workspace settings — the most damaging category, since a committed secret stays in Git history even after you delete it later.

Pattern syntax, with examples

The rules are simple but have a few sharp edges worth knowing:

  • node_modules/ — the trailing slash matches only directories, not a file named node_modules.
  • *.log — the asterisk matches any characters within one path segment. Matches debug.log, not logs/debug.log.
  • **/*.log — the double-asterisk matches across directory boundaries, so this catches .log files at any depth.
  • /config.json — a leading slash anchors the pattern to the directory containing the .gitignore file, rather than matching a config.json anywhere in the tree.
  • !important.log — a leading ! negates a pattern, re-including a file that a broader rule above it excluded. Order matters: you can't re-include something inside an already-ignored directory.
  • # comment — lines starting with # are comments and ignored by Git.

"I added it to .gitignore, but Git is still tracking it"

This is the single most common .gitignore problem, and it happens because .gitignore only affects untracked files. If a file was already committed before you added the rule, Git keeps tracking it regardless of what the pattern says. To fix it, remove the file from Git's index without deleting it from disk:

git rm --cached path/to/file
git commit -m "Stop tracking path/to/file"

For an entire directory that should never have been committed (a classic case: node_modules/ got pushed before anyone added a .gitignore), the same idea applies recursively:

git rm -r --cached node_modules
git commit -m "Remove node_modules from version control"

Note that this doesn't remove the file from your Git history — anyone who clones the repo can still find it in an old commit. If a real secret was committed, rotate the credential; removing it from history afterward (via tools like git filter-repo) is a separate, more involved step.

One file per stack, or combine them?

Most real projects need more than one template — a Node.js backend developed in VS Code on a Mac needs the Node, VS Code, and macOS rules simultaneously (build artifacts, editor settings, and .DS_Store respectively). GitHub's own template collection ships these as separate files for exactly this reason — you're expected to combine the ones relevant to your setup into a single .gitignore.

Try it yourself

.gitignore Generator →