The Problem
You committed .env last week. Your database credentials are in git history. Good luck removing that.
Or you pushed node_modules/. Or your macOS .DS_Store files. Or IDE config that breaks other developers’ workflows.
A proper .gitignore prevents 99% of this disaster. Most projects wing it. Don’t.
The Core Entries
Start with these. Add project-specific ones as you go.
# Environment files (ALWAYS).env.env.local.env.*.local.env.d.ts
# OS garbage.DS_StoreThumbs.db.vscode/settings.json.idea/*.swp*.swo*~.vscode/
# Dependenciesnode_modules//vendor/.venv/env/venv/__pycache__/*.egg-info/dist/build/
# Logs*.lognpm-debug.log*yarn-debug.log*yarn-error.log*pids/*.pid
# Test coveragecoverage/.nyc_output/
# Build artifactsdist/out/.next/.nuxt/.cache/.parcel-cache/.eslintcache
# IDE/Editor config (team-specific, not committed).vscode/.idea/*.sublime-workspace*.iml
# OS-specific.DS_Store.DS_Store?._*.AppleDouble.LSOverrideLanguage-Specific Additions
Node.js/JavaScript
node_modules/npm-debug.logyarn-error.log.npm/package-lock.json # optional: if you want strict versioningpnpm-lock.yaml # optional: if using pnpmPython
__pycache__/*.py[cod]*$py.class*.so.venv/env/venv/ENV/.pytest_cache/.coveragehtmlcov/dist/build/*.egg-info/Go
# Go binaries*.exe*.exe~*.dll*.so*.dylib/bin/
# Go test output*.test*.out.coverage.outJava
target/.classpath.project.settings/*.iml*.class.gradle/build/The “Oops” Categories
Secrets (The Dangerous Ones)
# Credentials.env.env.local.credentialscredentials.jsonkeyfile.jsonprivate_key.pemid_rsaid_rsa.pubPeople will tell you “just don’t commit secrets.” Great advice. But humans are lazy. .gitignore is your guardrail.
Generated Files (The Wasteful Ones)
# Generated codegenerated/*_pb2.py # protobuf*.generated.tsoutput/Never commit generated files. They bloat history and cause merge conflicts. Regenerate from source.
Build Output (The Confusing Ones)
dist/build/out/.build/target/*.min.js*.min.cssBuild output is environment-specific. A binary compiled on macOS won’t work on Linux. Regenerate per environment.
Pro Tips
Use a Global .gitignore
OS junk (.DS_Store, .vscode/) isn’t project-specific. Instead:
git config --global core.excludesfile ~/.gitignore_globalThen put OS stuff in ~/.gitignore_global. One-time setup, applies everywhere.
Check What You’re About to Commit
Before git commit, run:
git diff --cached --name-onlySee a .env file? Don’t commit. Use git reset HEAD <file> to unstage it.
If You Already Committed Secrets
Panic is optional. Use git filter-repo:
git filter-repo --path .env --invert-pathsThis rewrites history and removes .env from all commits. Then force-push (if you can):
git push --force-with-leaseBut seriously: don’t let it happen. A good .gitignore is cheaper than an emergency incident response.
Use a Template
GitHub’s .gitignore templates are solid:
curl -s https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore > .gitignoreCopy-paste as a starting point. Customize from there.
Common Mistakes
Mistake 1: Committing .vscode/settings.json.
Fix: .vscode/settings.json in .gitignore. (Commit .vscode/extensions.json and .vscode/launch.json if you want — those are team-wide.)
Mistake 2: Ignoring package-lock.json.
Fix: DON’T ignore it. Commit package-lock.json so everyone runs the same dependency versions.
Mistake 3: Creating a .gitignore in every subdirectory.
Fix: One .gitignore at root. Use paths: src/**/*.log.
Debugging Your .gitignore
Sometimes files still show up even though you think you ignored them. Debug it:
# Check why a specific file is or isn't ignoredgit check-ignore -v path/to/file.txt
# List all ignored filesgit ls-files --ignored --exclude-standard
# See what git thinks about a file (tracked, untracked, ignored)git status --short path/to/file.txtIf git check-ignore returns nothing, the file is being tracked (committed already). You can’t ignore an already-tracked file without removing it from the index:
# Remove from git tracking but keep the file locallygit rm --cached .envecho ".env" >> .gitignoregit commit -m "stop tracking .env"This is the move when you committed something by accident before adding it to .gitignore.
Negation Patterns
You can un-ignore a specific file after a broad ignore:
# Ignore all .log files*.log
# Except this one (it's important)!important.logOrder matters. The last matching rule wins. Negation only works if the parent directory isn’t ignored — you can’t un-ignore a file inside an ignored directory.
.git/info/exclude
There’s a per-repo ignore file that’s never committed: .git/info/exclude. Use it for personal ignores that aren’t team-wide:
echo "my-personal-notes.txt" >> .git/info/excludeYour personal temp files, local debug scripts, whatever. Gone from git status but never shows up in the shared .gitignore.
The Rule
If it’s generated, secrets, or OS-specific: ignore it. If it’s source code or config the team needs: commit it.
When in doubt, leave it out of git. Your CI/CD pipeline will regenerate it anyway.