Resources / Guide
.gitignore for .NET
A repo should hold your source code and nothing else — not build output, not secrets, not editor scratch files. .gitignore is how you keep them out. Here's what a .NET repo should ignore, and the catch nobody mentions: it only works on files git isn't already tracking.
What .gitignore is
It's a plain text file named .gitignore, living at the root of your repo and committed like any other file. Each line is a pattern; anything matching it is invisible to git — it won't show in git status, won't get staged, won't get committed. Because it's committed, everyone who clones the repo gets the same ignore rules automatically.
What a .NET repo should ignore
The big one is build output. Every time you build, the compiler regenerates bin/ and obj/ from your source. They're derived files — large, constantly changing, and pointless to track, because anyone can recreate them by building. These two lines handle the bulk of it:
bin/
obj/
Then there's everything else that isn't source:
# Build output
bin/
obj/
# User-specific IDE files
.vs/
*.user
# Local environment / secrets — NEVER commit these
appsettings.Development.json
appsettings.*.local.json
*.env
# Logs and temp
*.log
The good news: you rarely write this by hand. When you create a .NET project, Visual Studio and dotnet new can generate a comprehensive .gitignore for you, and GitHub maintains an official, exhaustive VisualStudio.gitignore you can drop in. Start from one of those rather than a blank file.
The one that actually matters: secrets
Ignoring bin/ and obj/ keeps your repo tidy. Ignoring secrets keeps you out of real trouble. Connection strings, API keys, and tokens that land in appsettings.Development.json or a .env file must never reach the remote — once a secret is in git history, it's effectively public to anyone with repo access, and the only true fix is to rotate it.
Keep secrets out of the repo entirely. A correct .gitignore is the baseline safety net, but the cleaner pattern for .NET is to never put secrets in tracked files at all — use the Secret Manager (dotnet user-secrets) in development, and environment variables or a vault in production.
Fixing it: untracking files you already committed
Here's the catch that surprises people: .gitignore only affects untracked files. If you already committed bin/ or obj/ before adding the ignore rules, git keeps tracking them — the ignore file is closing the gate after they're already inside. Adding the pattern does nothing for files git is already watching.
To stop tracking something that's already committed, remove it from git's index while keeping it on your disk, using --cached:
# stop tracking a folder, but don't delete it locally
git rm -r --cached bin obj
# or one file
git rm --cached appsettings.Development.json
The --cached flag is the important part: it removes the file from git's tracking but leaves the actual file alone on your machine. Without it, git rm would delete the file too. After that, commit the removal:
git commit -m "Stop tracking build output and local config"
From now on, with the matching .gitignore rules in place, those files stay out of git for good. (Note this removes them from future commits, not from past history — for a leaked secret you still have to rotate it, because it's recoverable from old commits.)
Get it in early
The cleanest path is to add a proper .gitignore as the very first thing in a new repo, before the first git add. Then build output and secrets never enter history in the first place, and you never have to do the untracking dance. If you've inherited a repo that's already tracking junk, the git rm --cached cleanup above gets you back to a tidy state in one commit — and pairs naturally with a committed .gitattributes to keep line endings sane while you tidy the repo's plumbing.
Worried a secret's already in your history?
If a key or connection string got committed, ignoring it now isn't enough — it has to be scrubbed and rotated. I help teams find what's exposed and lock it down properly. If you want a check, let's talk.
Work with me