30 30. .gitignore and Keeping Secrets Out of Git
Some files should never go into a repository: clutter your computer creates, folders you can always re-create, and above all secrets such as passwords and API keys. This page shows you how to make Git ignore files and what to do if a secret slips through anyway.
30.1 What should not be committed
| Kind of file | Examples | Why not |
|---|---|---|
| Operating system and editor clutter | .DS_Store (Mac), Thumbs.db (Windows), *.swp, *~ |
Useless to anyone else |
| Files you can re-create | *.log, build/, dist/, node_modules/ |
They’re big and change constantly; a command rebuilds or re-downloads them |
| Secrets | .env files with API keys and passwords |
Anyone who sees the repository could use them |
| Personal files | your to-do notes, personal settings | Not part of the project |
node_modules/ is the folder where npm downloads the packages a JavaScript project uses; it can hold thousands of files (see Web Frameworks and Meta-Frameworks).
30.2 The .gitignore file
A .gitignore file is a plain text file, usually in the top folder of the repository, that lists patterns of file and folder names Git should ignore. The practice project from the previous pages already has one:
cat .gitignoreOutput:
# Operating system files
.DS_Store
Thumbs.db
# Editor files
*.swp
*.swo
*~
# Build artifacts
build/
dist/
*.log
The most useful pattern rules:
| Pattern | What it ignores |
|---|---|
.DS_Store |
every file with exactly this name, in any folder |
*.log |
every file whose name ends in .log (* matches any characters) |
build/ |
every folder named build, and everything inside it (the trailing / means “folders only”) |
node_modules/ |
the node_modules folder (and everything inside it) |
.env |
every file named exactly .env |
# Secrets |
nothing: a line starting with # is a comment; blank lines are ignored too |
!.env.example |
an exception: a ! re-includes a file that an earlier pattern ignored |
Commit the .gitignore file itself, so the rules travel with the project to every computer that gets a copy.
30.3 Checking that it works
Create a log file and ask for the status:
touch debug.log
git statusOutput:
On branch main
nothing to commit, working tree clean
debug.log doesn’t appear at all, because it matches *.log. Two commands help when you’re not sure why a file is (or isn’t) ignored:
git check-ignore -v debug.logOutput:
.gitignore:13:*.log debug.log
This reads: “line 13 of .gitignore, the pattern *.log, ignores debug.log”. If a file is not ignored, git check-ignore prints nothing. git status --ignored adds an Ignored files section to the normal status.
30.4 Keeping a .env file out of Git
Maria wants to add a weather widget to her site, and the weather service gave her an API key (a secret code that identifies her account; see APIs, API Keys, and Third-Party Services). Instead of typing the key into her code, she stores it in a .env file, a plain text file of NAME=value lines that programs load as environment variables (see Environment Variables):
echo "WEATHER_API_KEY=abc123-fake-key-for-practice" > .env
git statusOutput:
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env
nothing added to commit but untracked files present (use "git add" to track)
This is dangerous: her next git add . would put the key into the history. So she opens .gitignore in her text editor and adds an empty line and then these two lines at the end:
# Secrets: never commit these
.env
Now git status lists only modified: .gitignore. The .env file has disappeared from the status, so git add . can no longer pick it up.
Next she creates a .env.example file: a template with the same variable names but fake values. It’s safe to commit, and it tells anyone who copies the project (including Maria on another computer) which values they need to fill in:
echo "WEATHER_API_KEY=your-key-goes-here" > .env.example
git add .gitignore .env.example
git commit -m "Ignore .env and add .env.example template"Output:
[main 736e88f] Ignore .env and add .env.example template
2 files changed, 4 insertions(+)
create mode 100644 .env.example
30.5 Ignoring a file that is already tracked
.gitignore only affects untracked files. If a file was committed before you added it to .gitignore, Git keeps tracking it and keeps showing its changes.
Sam made exactly this mistake: his first commit in his weather-app project included his .env file. To stop tracking it without deleting it from his computer:
echo ".env" >> .gitignore
git rm --cached .env
git statusOutput:
rm '.env'
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: .env
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
git rm --cached removes the file from the repository (the staging area, and so from the next commit) but leaves it on disk; “deleted” here means “deleted from Git’s tracking”. He then commits:
git add .gitignore
git commit -m "Stop tracking .env"New commits no longer contain .env, but the old commit still does. Anyone with a copy of the repository can see it, for example with git show HEAD~1:.env, which prints the old file’s contents. If that commit was ever pushed to GitHub, treat the key as leaked (next section).
30.6 If you leak a secret
- Revoke or rotate the key immediately. Log in to the service that issued it, delete the old key, and create a new one. This is the only fix that really works: once revoked, the leaked key is useless.
- Then clean up your project. Put the new key in
.env(which is ignored), and remove the secret from any tracked file. - Don’t count on deleting it from GitHub. Removing the file, the commit, or even the repository doesn’t remove copies that already exist in clones and forks, and anyone (or any automated tool) may already have copied it. GitHub’s docs recommend rotating the secret first; rewriting the history is a lot of work and often unnecessary once the key is revoked.
GitHub also has two safety nets on github.com:
- Push protection for your account is turned on by default. It blocks a push to a public repository if the push contains a secret in a format GitHub recognizes.
- Secret scanning runs automatically on public repositories. For keys from GitHub’s partner services, GitHub notifies the service, which can then revoke the key.
These only catch known kinds of secrets, so they don’t replace good habits. The best protection is that the secret never gets committed in the first place.
30.7 Secrets and Claude Code
As of Oct 6, 2026 · Official docs: Settings · Permissions · API key best practices
- Anthropic’s help center warns about this exact mistake. It says one of the most frequent causes of leaked API keys is accidental exposure in public code repositories, such as keys committed to public GitHub repositories. It compares an API key to a credit card number: whoever has it can run up charges on your account. It also says to add
.envfiles to.gitignore. - Keep secrets out of your prompts too. Don’t paste a real key into a chat with Claude Code (or any AI tool). Put it in
.env, and ask Claude to write code that reads it from an environment variable. - Claude Code can read files in your project, including
.env. If you don’t want it to, a deny rule such asRead(./.env)in Claude Code’s settings blocks its file tools from reading that file (see The .claude Folders and Settings Files). - Claude Code’s personal settings file stays out of Git.
.claude/settings.local.jsonholds your personal settings for one project. The first time Claude Code writes this file in a Git repository, it adds it to your global Git ignore file (see the deeper dive below), so it stays out of your commits. If you create the file by hand, add it to.gitignoreyourself.
- Your global ignore file, by default
~/.config/git/ignore, applies to every repository on your computer. It’s a good place for your own clutter, such as.DS_Storeon a Mac. It’s not shared with anyone. (A different file can be chosen with thecore.excludesFilesetting.) .git/info/excludeworks like.gitignorebut only in this one repository, and is never committed.- Templates: GitHub maintains a collection of ready-made
.gitignorefiles for many languages and tools at github.com/github/gitignore.
- A pattern without a slash (like
*.logor.env) matches in any folder. A leading slash anchors it to the folder of the.gitignorefile:/build/ignores only the top-levelbuildfolder. **matches any number of folders:logs/**/*.txtmatches.txtfiles anywhere underlogs.- A
!exception can’t rescue a file whose folder is ignored: withbuild/ignored,!build/keep.txthas no effect. - A common pair for environment files:
.env*(ignore.env,.env.local, …) followed by!.env.example(but keep the template).
Practice Questions
Easy
Question 1 · Short answer
What is a .gitignore file?
Question 2 · Multiple choice
Which of these files should not be committed?
README.md.envcontainingSTRIPE_SECRET_KEY=...src/index.html.gitignore
Question 3 · Matching
Match each .gitignore line with its meaning.
| Line | Meaning | |
|---|---|---|
1. *.log |
A. a comment; Git ignores this line | |
2. build/ |
B. every file whose name ends in .log |
|
3. # Secrets |
C. don’t ignore this file, even if an earlier pattern matched it | |
4. !.env.example |
D. every file named exactly .DS_Store |
|
5. .DS_Store |
E. every folder named build and everything inside it |
Question 4 · True or false
As soon as you add a file’s name to .gitignore, Git stops tracking it, even if it was committed earlier.
Question 5 · Short answer
What is a .env.example file, and why is it safe (and useful) to commit it?
Question 6 · Short answer
You accidentally pushed a real API key to GitHub. What is the first thing you should do?
Medium
Question 7 · Predict the output
The practice project’s .gitignore contains *.log, and the working tree is clean. Maria runs:
touch error.log
git statusWhat does git status print? What would git check-ignore -v error.log print?
Question 8 · Write the file
Write a .gitignore for a small JavaScript project that ignores: the node_modules folder, all .log files, Mac .DS_Store files, and the .env file. Include a comment above the secrets line.
Question 9 · Write the command
config/secrets.json was committed by mistake. Write the commands to stop tracking it (keeping the file on disk), make sure it stays ignored, and commit the change.
Question 10 · Multiple choice
What does git rm --cached .env do?
- Deletes
.envfrom your computer and from Git - Removes
.envfrom Git’s tracking (starting with the next commit) but keeps the file on your computer - Removes
.envfrom every old commit in the history - Adds
.envto.gitignore
Question 11 · Predict the output
Sam’s .env was committed in his first commit. Later he adds .env to .gitignore and commits that, then changes the key inside .env. What does git status show, and why?
Question 12 · Scenario
Maria copies her project to her lab computer and finds that .env is missing, so the weather widget doesn’t work. Why is it missing? What should she do?
Question 13 · Read the output
Explain this output:
$ git check-ignore -v .env
.gitignore:16:.env .env
Hard
Question 14 · Scenario
An hour ago Sam pushed a commit containing his API key to a public GitHub repository. He has since deleted the key from the file, committed, and pushed again. Is he safe? What should he do, in which order?
Question 15 · True or false
“GitHub’s push protection means I can never accidentally leak a secret, so I don’t need .gitignore for .env.”
Question 16 · Find the bug
Sam created his ignore file in Windows Notepad and saved it in his project folder, but git status still lists .env as untracked. ls -a shows:
. .. .env .git .gitignore.txt app.py
What went wrong, and how can he fix it?
Question 17 · Long answer
Explain why deleting a commit (or even the whole repository) on GitHub is not enough once a secret has been pushed, and what the real fix is.
Question 18 · Scenario
Maria asks Claude Code to add the weather widget to her site. List three things she should do (or avoid) to keep her API key safe during this work.
Question 19 · Short answer
Maria notices that .claude/settings.local.json never shows up in git status, even though it isn’t listed in her project’s .gitignore. Why?
Question 20 · Short answer
A teammate’s .gitignore contains build/ followed by !build/keep.txt, but build/keep.txt is still ignored. Why?