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 .gitignore

Output:

# 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 status

Output:

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.log

Output:

.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 status

Output:

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 status

Output:

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"
The secret is still in the history

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

  1. 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.
  2. Then clean up your project. Put the new key in .env (which is ignored), and remove the secret from any tracked file.
  3. 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 .env files 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 as Read(./.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.json holds 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 .gitignore yourself.
  • 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_Store on a Mac. It’s not shared with anyone. (A different file can be chosen with the core.excludesFile setting.)
  • .git/info/exclude works like .gitignore but only in this one repository, and is never committed.
  • Templates: GitHub maintains a collection of ready-made .gitignore files for many languages and tools at github.com/github/gitignore.
  • A pattern without a slash (like *.log or .env) matches in any folder. A leading slash anchors it to the folder of the .gitignore file: /build/ ignores only the top-level build folder.
  • ** matches any number of folders: logs/**/*.txt matches .txt files anywhere under logs.
  • A ! exception can’t rescue a file whose folder is ignored: with build/ ignored, !build/keep.txt has 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?

A plain text file (usually in the top folder of a repository) that lists patterns of file and folder names Git should ignore. Ignored files don’t show up as untracked in git status, and git add . doesn’t stage them.

Question 2 · Multiple choice

Which of these files should not be committed?

  1. README.md
  2. .env containing STRIPE_SECRET_KEY=...
  3. src/index.html
  4. .gitignore

b. A .env file holds secrets, so it belongs in .gitignore. The README (a) and the web page (c) are normal project files, and the .gitignore file itself (d) should be committed so that its rules travel with the project.

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

1 → B, 2 → E, 3 → A, 4 → C, 5 → D.

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.

False. .gitignore only affects untracked files. A file that is already tracked stays tracked until you run git rm --cached <file> and commit.

Question 5 · Short answer

What is a .env.example file, and why is it safe (and useful) to commit it?

It’s a template of the .env file: the same variable names with fake values (for example WEATHER_API_KEY=your-key-goes-here). It contains no secrets, so it’s safe to commit, and it tells anyone who copies the project (including you on another computer) which values they must fill in.

Question 6 · Short answer

You accidentally pushed a real API key to GitHub. What is the first thing you should do?

Revoke or rotate the key immediately: log in to the service that issued it, delete the old key, and create a new one. Cleaning up the repository comes after that, because copies of the old key may already exist.

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 status

What does git status print? What would git check-ignore -v error.log print?

git status prints nothing to commit, working tree clean (after On branch main), because error.log is ignored. git check-ignore -v error.log prints the rule that ignores it, for example .gitignore:13:*.log error.log (file, line number, pattern, path).

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.

node_modules/
*.log
.DS_Store

# Secrets: never commit these
.env

The order of the lines doesn’t matter here, and comments are optional (but helpful).

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.

echo "config/secrets.json" >> .gitignore
git rm --cached config/secrets.json
git add .gitignore
git commit -m "Stop tracking config/secrets.json"

--cached removes the file from Git’s tracking only; the file stays on disk. (If it contains real secrets that were pushed, rotate them too.)

Question 10 · Multiple choice

What does git rm --cached .env do?

  1. Deletes .env from your computer and from Git
  2. Removes .env from Git’s tracking (starting with the next commit) but keeps the file on your computer
  3. Removes .env from every old commit in the history
  4. Adds .env to .gitignore

b. --cached means “only in Git, not on disk”. Without --cached (a), git rm would also delete the file. It does not change old commits (c): the file is still in the history. And it doesn’t edit .gitignore (d); you add that line yourself.

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?

It shows modified: .env under Changes not staged for commit. .env is still a tracked file, and .gitignore has no effect on tracked files. He needs git rm --cached .env and a commit to stop tracking it (and, since the old key is in the history, he should rotate it if it was pushed).

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?

.env is in .gitignore, so it was never committed and isn’t part of the repository. That’s exactly what she wanted. On the lab computer she should create it from the template and fill in the real key:

cp .env.example .env

then edit .env in a text editor. The key itself should be brought over in a safe way (for example, typed in again or copied from the service’s website), never through the repository.

Question 13 · Read the output

Explain this output:

$ git check-ignore -v .env
.gitignore:16:.env  .env

The file .env is ignored because of line 16 of the .gitignore file, whose pattern is .env. (If the file weren’t ignored, the command would print nothing.)

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?

He is not safe. The key is still in the earlier commit, which is part of the history on GitHub, and it may already have been copied (by clones, forks, or automated tools that scan public repositories). Order:

  1. Revoke/rotate the key now on the service’s website and create a new one. This makes the leaked key useless.
  2. Put the new key in a .env file that is listed in .gitignore (and use git rm --cached if .env was tracked).
  3. Check the service’s usage or billing page for activity he didn’t cause.

Rewriting the Git history is optional and complicated; GitHub’s docs say it is often unnecessary once the key has been revoked.

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.”

False. Push protection for your account only covers pushes to public repositories on github.com and only recognizes known secret formats. A password or a key from a less common service may not be detected, and a private repository can become public later. It’s a safety net; .gitignore and .env files are still your main protection.

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?

Notepad saved the file as .gitignore.txt, and Git only reads a file named exactly .gitignore. Rename it:

mv .gitignore.txt .gitignore
git status

Now .env disappears from git status. (In Notepad, choose “All files” as the file type when saving to avoid the extra .txt.)

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.

Once the secret has been pushed, copies can exist outside your control: in other people’s clones and forks, in GitHub’s cached views, and in the hands of anyone (or any automated tool) who saw it. Deleting the commit on GitHub can’t remove those copies, and every copy still works as long as the key is valid. The real fix is to revoke or rotate the secret, so the leaked value no longer works. Cleaning the history afterwards is optional extra work.

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.

Any three of:

  • Don’t paste the real key into the chat; keep it in .env.
  • Make sure .env is listed in .gitignore before anything is committed, and commit a .env.example with a fake value instead.
  • Ask Claude to write code that reads the key from an environment variable, not code with the key typed into it.
  • Optionally add a Read(./.env) deny rule so Claude Code’s file tools don’t read the file.
  • Review git status and git diff --staged before every commit (including commits Claude Code makes for her) to check that no key ended up in a tracked file.

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?

The first time Claude Code wrote that file in her Git repository, it added it to her global Git ignore file (by default ~/.config/git/ignore), which applies to every repository on her computer. So Git ignores it even though the project’s .gitignore doesn’t mention it. (If she had created the file by hand, she would need to add it to .gitignore herself.)

Question 20 · Short answer

A teammate’s .gitignore contains build/ followed by !build/keep.txt, but build/keep.txt is still ignored. Why?

build/ ignores the whole folder, and Git doesn’t look inside an ignored folder, so a ! exception can’t re-include a file in it. One fix is to ignore the folder’s contents instead (build/*) and then add !build/keep.txt.

© 2025-2026 Y. Rosenthal. All rights reserved.