Home DevOps .gitignore — The Missing File That Exposed Production Secrets
Beginner 3 min · July 07, 2026
.gitignore: Prevent Secrets and Bloat From Entering Your Repo

.gitignore — The Missing File That Exposed Production Secrets

A developer forgot to add .env to .gitignore.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 12, 2026
last updated
2,406
articles · all by Naren
Before you start⏱ 15 min
  • Git 2.x installed, basic command line familiarity, a GitHub or GitLab account for practice, and a text editor.
 ● Production Incident
Quick Answer

Create a .gitignore file in your repo root. Add patterns: *.log (all log files), .env (environment file), node_modules/ (directory). Use git check-ignore -v to debug why a file is being ignored.

✦ Definition~90s read
What is .gitignore?

.gitignore tells Git which files and directories to ignore — they won't be tracked, staged, or committed. It prevents sensitive or unnecessary files from entering the repository.

Gitignore is like a 'do not pack' list when moving houses.
Plain-English First

Gitignore is like a 'do not pack' list when moving houses. You tell the movers (Git) which boxes to ignore: 'Don't pack the trash (log files), don't pack the safe (secrets files), don't pack the pantry (generated files).' Without this list, Git tries to pack everything it finds, including things you never wanted in the moving truck.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

The most common Git mistake is committing something sensitive — API keys, database passwords, .env files, or large binary files. .gitignore is the first line of defense. But it's often added reactively, after the damage is done. A single commit with a .env file means the secrets are in the repository history forever, even if you delete the file in a later commit. This guide covers gitignore patterns, the global gitignore, and the incident where a missing .env entry exposed production API keys within hours of the commit.

Why .gitignore Matters

Every developer has accidentally committed secrets, build artifacts, or large binary files. A .gitignore file tells Git which files to ignore, preventing sensitive data and repository bloat. Without it, you risk exposing API keys, database credentials, or configuration files. Production teams enforce .gitignore to maintain clean, secure repositories. This section explains the consequences of ignoring .gitignore: leaked secrets on public repos, bloated clone times, and merge conflicts from generated files. A well-crafted .gitignore is your first line of defense against these issues.

example-commit.shBASH
1
2
3
4
5
6
7
#!/bin/bash
# Simulate accidental secret commit
echo "API_KEY=sk-12345" > .env
git add .env
git commit -m "Add config"
git push origin main
# Now secret is in history forever unless purged
Output
Enumerating objects: 4, done.
Counting objects: 100% (4/4)
Delta compression using up to 8 threads
Compressing objects: 100% (2/2)
Writing objects: 100% (3/3), 300 bytes | 300.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:user/repo.git
a1b2c3d..e4f5g6h main -> main
⚠ Secrets in Git History
Once a secret is committed, it's in the history forever. Even if you delete the file later, the secret remains accessible via git log. Use tools like git-filter-repo to purge secrets, but prevention is far easier.
📊 Production Insight
In production, a single leaked AWS key in a public repo can lead to cryptomining bills of $50k+ overnight. Always add .gitignore before the first commit.
🎯 Key Takeaway
A .gitignore prevents accidental commits of secrets and bloat, keeping your repo secure and lean.
gitignore THECODEFORGE.IO Gitignore Security Layer Stack Hierarchical defense against secret exposure Global Gitignore core.excludesFile | OS-specific files Repository .gitignore Project-specific patterns | Build artifacts Directory .gitignore Submodule patterns | Local overrides Exclude via .git/info/exclude Local-only rules | Sensitive local config Audit and Enforcement git-secrets | pre-commit hooks | CI checks THECODEFORGE.IO
thecodeforge.io
Gitignore

Anatomy of a .gitignore File

A .gitignore file uses glob patterns to match files and directories. Lines starting with # are comments. Patterns can be absolute (starting with /) or relative. Use to match any string, ? for a single character, and [abc] for character sets. Negate patterns with ! to re-include files. Directories are matched by appending /. For example, /build/ ignores the build directory at the root, while .log ignores all log files. Understanding these patterns is crucial to avoid accidentally ignoring important files or missing dangerous ones.

.gitignoreGITIGNORE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Ignore all .env files
.env

# Ignore build output
/build/

# Ignore all log files
*.log

# But keep important.log (negation)
!important.log

# Ignore files in any directory named 'temp'
temp/
🔥Pattern Matching
Patterns are case-sensitive on Linux but not on macOS/Windows by default. Use consistent casing to avoid cross-platform issues.
📊 Production Insight
A common mistake is using /.log instead of */.log to match logs in subdirectories. The double asterisk ** matches any number of directories.
🎯 Key Takeaway
Master glob patterns to precisely control what Git ignores.

Setting Up .gitignore for Different Languages and Frameworks

Different projects have different artifacts. Python projects ignore __pycache__, *.pyc, and virtual environments. Node.js ignores node_modules and package-lock.json (though some teams track lockfiles). Java projects ignore .class files and target/ directories. Use language-specific templates from GitHub's gitignore repository. Never blindly copy-paste; understand each pattern. For example, ignoring .env is universal, but ignoring .vscode/ might be team-specific. Tailor your .gitignore to your exact build process.

.gitignoreGITIGNORE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Python
__pycache__/
*.py[cod]
*.so
.Python
env/
venv/

# Node
node_modules/
npm-debug.log*

# Java
*.class
*.jar
*.war
*.nar
target/

# IDE
.idea/
.vscode/
*.swp
*.swo
💡Use Templates
Start with a template from https://github.com/github/gitignore and customize. This saves time and covers common patterns.
📊 Production Insight
A team once ignored package-lock.json, causing inconsistent builds across machines. Always track lockfiles for reproducible builds.
🎯 Key Takeaway
Use language-specific .gitignore templates but customize for your project's unique artifacts.
Gitignore Patterns: Inclusive vs Exclusive Trade-offs in ignoring files by inclusion or exclusion Inclusive Patterns Exclusive Patterns Default Behavior Ignore everything, then unignore needed Track everything, then ignore specific f Security Risk Lower risk of missing secrets Higher risk of accidental exposure Maintenance Effort Higher initial setup, easier updates Lower initial setup, frequent pattern up Use Case Projects with many generated files Simple projects with few sensitive files Example Pattern * !src/ !config/ *.log secrets.env build/ THECODEFORGE.IO
thecodeforge.io
Gitignore

Global .gitignore for Personal Preferences

Sometimes you want to ignore files globally across all repos, like editor backups or OS files. Use a global .gitignore file configured via git config --global core.excludesFile. This keeps your repo-specific .gitignore clean. Common global ignores: .DS_Store (macOS), Thumbs.db (Windows), *.swp (vim), and .idea/ (JetBrains). However, be cautious: global ignores apply to all repos on your machine, so don't add project-specific patterns there.

setup-global-gitignore.shBASH
1
2
3
4
5
6
7
#!/bin/bash
# Create global gitignore
echo ".DS_Store" >> ~/.gitignore_global
echo "Thumbs.db" >> ~/.gitignore_global
echo "*.swp" >> ~/.gitignore_global
# Configure git to use it
git config --global core.excludesFile ~/.gitignore_global
🔥Global vs Local
Global ignores are for your machine only. They don't affect other team members. Use them for OS-specific or editor-specific files.
📊 Production Insight
Forcing a global .gitignore in CI can cause unexpected ignores. CI environments should rely only on repo-level .gitignore.
🎯 Key Takeaway
Use a global .gitignore for personal preferences to keep repo-specific files clean.

Ignoring Secrets: .env, Credentials, and Config Files

Secrets like API keys, database passwords, and tokens must never be committed. The most common pattern is to ignore .env files. But also ignore files like config/secrets.yml, *.pem, or any file containing credentials. Use .env.example as a template committed to the repo. For production, use secret management tools like Vault or AWS Secrets Manager. Never hardcode secrets in code. If a secret is committed, rotate it immediately and purge it from history.

.gitignoreGITIGNORE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Secrets
.env
.env.local
.env.*.local

# Config files with secrets
config/database.yml
config/secrets.yml

# SSH keys
*.pem
*.key

# Service account JSONs
*-service-account.json
⚠ Rotate Compromised Secrets
If a secret is committed, assume it's compromised. Rotate it immediately. Use git-filter-repo to remove it from history, but rotation is mandatory.
📊 Production Insight
A startup committed their production database password to a public repo. Within hours, their database was dumped and held for ransom. Prevention is cheap.
🎯 Key Takeaway
Always ignore files containing secrets and use .env.example as a template.

Ignoring Build Artifacts and Dependencies

Build artifacts like compiled binaries, minified files, and dependency directories bloat your repo. Ignore node_modules/, vendor/, __pycache__/, and build output directories like dist/ or build/. These can be regenerated from source. Ignoring them keeps your repo small and diffs meaningful. However, some teams track lockfiles (package-lock.json, Gemfile.lock) for deterministic builds. Decide as a team what to track.

.gitignoreGITIGNORE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Dependencies
node_modules/
vendor/

# Build output
dist/
build/
*.bundle

# Compiled files
*.pyc
*.class
*.o

# Logs
*.log
npm-debug.log*
💡Track Lockfiles
Always commit lockfiles (e.g., package-lock.json) to ensure reproducible builds across environments.
📊 Production Insight
A team committed node_modules/ by mistake, making their repo 500MB. Cloning took 10 minutes. Removing it reduced clone time to 10 seconds.
🎯 Key Takeaway
Ignore regeneratable build artifacts and dependencies to keep your repo lean.

Handling Nested .gitignore Files

You can have multiple .gitignore files in subdirectories. Patterns in a child .gitignore override parent patterns. This is useful for monorepos where different subprojects have different ignore rules. For example, a frontend directory might ignore node_modules, while a backend directory ignores vendor. However, too many nested files can become confusing. Prefer a single root .gitignore unless you have a clear need for per-directory rules.

frontend/.gitignoreGITIGNORE
1
2
3
4
5
6
# Override root ignore for this directory
!important.log

# Additional ignores for frontend
node_modules/
.next/
🔥Pattern Precedence
A pattern in a child .gitignore overrides the parent. Use negation (!) carefully as it can re-include files ignored by parent.
📊 Production Insight
In a monorepo, a nested .gitignore accidentally re-included a sensitive config file. Always test with git check-ignore before committing.
🎯 Key Takeaway
Use nested .gitignore files sparingly for monorepos with distinct subproject needs.

Testing Your .gitignore with git check-ignore

Before committing, verify that your .gitignore works as expected. Use git check-ignore -v <file> to see if a file is ignored and which pattern matches. Also use git status to check for untracked files. For CI, add a step to ensure no secrets are staged. Tools like git-secrets or truffleHog can scan for secrets. Never assume your .gitignore is correct; test it.

test-gitignore.shBASH
1
2
3
4
5
6
7
#!/bin/bash
# Check if .env is ignored
git check-ignore -v .env
# Output: .gitignore:2:.env	.env

# List all ignored files
git ls-files --others --ignored --exclude-standard
Output
.gitignore:2:.env .env
node_modules/
build/
*.log
💡Automate Checks
Add a pre-commit hook that runs git check-ignore on staged files to catch mistakes early.
📊 Production Insight
A developer thought they ignored .env, but a typo ('.en v') caused the file to be committed. Automated checks in CI would have caught it.
🎯 Key Takeaway
Always test your .gitignore with git check-ignore and git status before committing.

Removing Files Already Tracked by Git

If you added a file to .gitignore after it was already tracked, Git still tracks it. You must remove it from the index with git rm --cached <file>. This keeps the file locally but stops tracking changes. For directories, use git rm -r --cached. Then commit the removal. This is common when retrofitting .gitignore to an existing project. Be careful: this will delete the file from other developers' working copies on pull.

remove-tracked.shBASH
1
2
3
4
5
6
7
#!/bin/bash
# Remove .env from tracking but keep locally
git rm --cached .env
# Commit the removal
git commit -m "Stop tracking .env file"
# Push to remote
git push origin main
Output
rm '.env'
[main a1b2c3d] Stop tracking .env file
1 file changed, 0 insertions(+), 5 deletions(-)
delete mode 100644 .env
⚠ Cascading Deletions
After pushing, other developers will have their .env deleted on git pull. They must recreate it from .env.example.
📊 Production Insight
A team removed a tracked config file without communicating. Developers lost their local config and had to restore from backup. Always announce such changes.
🎯 Key Takeaway
Use git rm --cached to stop tracking files that should be ignored.

Best Practices for Team .gitignore Management

Establish team conventions: commit .gitignore early, review changes in PRs, and use a standard template. Avoid ignoring files that are essential for building (like lockfiles). Document why certain patterns are ignored. Use .gitkeep files to track empty directories. For monorepos, consider a root .gitignore with per-project overrides. Regularly audit your .gitignore to remove stale patterns. Treat .gitignore as code: version it, review it, and test it.

.gitignoreGITIGNORE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Team .gitignore conventions
# Do NOT ignore lockfiles
# package-lock.json is tracked

# Ignore all .env files except .env.example
.env
.env.*
!.env.example

# Ignore IDE files
.idea/
.vscode/

# Ignore OS files
.DS_Store
Thumbs.db
🔥PR Review
Treat .gitignore changes like any other code change. Review them for correctness and impact.
📊 Production Insight
A team ignored .gitignore changes in PRs, leading to a gradual accumulation of ignored files that bloated the repo. Regular audits prevented this.
🎯 Key Takeaway
Treat .gitignore as code: version it, review it, and maintain it as a team.

Advanced: .gitignore Patterns for Monorepos and CI

In monorepos, use patterns like !/project-a/node_modules to unignore specific directories. For CI, ensure your .gitignore doesn't ignore files needed for build (like lockfiles). Use .gitignore per environment? Not recommended; instead, use environment variables. For CI artifacts, consider a separate .gitignore in the CI workspace. Also, be aware of .gitignore in submodules: each submodule has its own .gitignore.

.gitignoreGITIGNORE
1
2
3
4
5
6
7
# Monorepo: ignore all node_modules except project-a
node_modules/
!/project-a/node_modules

# CI: ignore temporary files
*.tmp
*.swp
💡CI Artifacts
In CI, use a separate .gitignore for the workspace to avoid ignoring build outputs that need to be cached.
📊 Production Insight
A monorepo with 50 microservices used a single .gitignore that ignored all node_modules. A developer accidentally committed a node_modules folder in a subdirectory because the pattern didn't match. Use **/node_modules to catch all.
🎯 Key Takeaway
Advanced patterns like negation and per-directory rules help manage complex monorepos and CI pipelines.

Recovering from .gitignore Mistakes

Mistakes happen: you accidentally ignored a needed file or committed a secret. To recover, first fix the .gitignore. If a file is ignored but should be tracked, remove the pattern and use git add -f to force add. If a secret is committed, rotate it and use git filter-repo to purge it from history. For large files accidentally committed, use BFG Repo-Cleaner. Always communicate with your team before destructive history rewrites.

recover.shBASH
1
2
3
4
5
6
7
8
9
#!/bin/bash
# Force add a file that is ignored
git add -f important.config

# Remove a file from history (use with caution)
git filter-repo --path .env --invert-paths

# Then force push
git push origin --force --all
Output
Parsed 100 commits
Deleted file .env in 5 commits
Rewrote history
Force pushing...
⚠ Force Push Dangers
Rewriting history with force push can break other developers' clones. Coordinate with your team and use protected branches.
📊 Production Insight
A team force-pushed a history rewrite without warning, causing all other developers to have divergent histories. Always communicate and use --force-with-lease.
🎯 Key Takeaway
Fix .gitignore mistakes promptly, and use tools like git filter-repo for history cleanup.
● Production incidentPOST-MORTEMseverity: high

.env Committed to GitHub — API Keys Compromised in 2 Hours

Symptom
A developer created a new project, ran git init, wrote code, and committed. The .env file with production API keys was not in .gitignore. The entire file was committed and pushed to a public GitHub repository. Within 2 hours, automated bots had scanned the repo, extracted the keys, and used them to provision cloud resources on the company's account.
Assumption
The developer assumed that if a file is not git added explicitly, it won't be committed. They didn't realize that git add . stages all untracked files in the directory, including .env.
Root cause
1. Developer set up a new project and created a .env file with production AWS and Stripe API keys. 2. They ran git init, then git add . — which staged everything including .env. 3. They committed with git commit -m 'Initial commit'. 4. Pushed to a public GitHub repository. 5. Within 2 hours, automated credential-scanning bots detected the keys. 6. The bots provisioned EC2 instances for cryptocurrency mining — $15,000 in AWS charges before the keys were rotated. 7. The keys were also used to access production data through the exposed API keys.
Fix
1. Immediately: invalidated all exposed API keys (AWS, Stripe). 2. Removed the .env file from the current commit (not enough — history still had it). 3. Used git filter-branch to remove .env from all history: git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch .env' --prune-empty --tag-name-filter cat -- --all. 4. Force-pushed the cleaned history (coordinated with team to reset local clones). 5. Added .env to .gitignore immediately. 6. Added a pre-commit hook that checks for patterns like .env, .pem, secret, password in staged files and aborts the commit. 7. Implemented a global .gitignore via core.excludesFile that excludes .env by default for all repos. 8. Added CI step: git diff --name-only --cached | grep -E '\.env|secret|\.pem' and fail if matched.
Key lesson
  • Always create .gitignore BEFORE the first commit.
  • Never use git add . without checking what will be staged.
  • Use pre-commit hooks to scan for sensitive files.
  • Set up a global .gitignore for common patterns.
  • Run git status before every commit to verify what's staged.
Production debug guideUse this when production is on fire5 entries
Symptom · 01
Prevent a file type from being tracked
Fix
Add *.log to .gitignore — ignores all .log files
Symptom · 02
Ignore a specific file anywhere in the repo
Fix
Add .env to .gitignore — ignores .env in any directory
Symptom · 03
Ignore a specific directory
Fix
Add node_modules/ to .gitignore — ignores the entire directory
Symptom · 04
Debug why a file is (not) being ignored
Fix
Run git check-ignore -v <file> — shows which pattern matches
Symptom · 05
Set global ignores for all repos
Fix
Run git config --global core.excludesFile ~/.gitignore_global
⚙ Quick Reference
9 commands from this guide
FileCommand / CodePurpose
example-commit.shecho "API_KEY=sk-12345" > .envWhy .gitignore Matters
.gitignore.envAnatomy of a .gitignore File
.gitignore__pycache__/Setting Up .gitignore for Different Languages and Frameworks
setup-global-gitignore.shecho ".DS_Store" >> ~/.gitignore_globalGlobal .gitignore for Personal Preferences
.gitignorenode_modules/Ignoring Build Artifacts and Dependencies
frontend.gitignore!important.logHandling Nested .gitignore Files
test-gitignore.shgit check-ignore -v .envTesting Your .gitignore with git check-ignore
remove-tracked.shgit rm --cached .envRemoving Files Already Tracked by Git
recover.shgit add -f important.configRecovering from .gitignore Mistakes

Key takeaways

1
Create .gitignore BEFORE the first commit
not after the accident
2
Never use git add . without running git status first
3
Use global .gitignore via core.excludesFile for universal patterns
4
git check-ignore -v debugs why a file is or isn't ignored
5
Pre-commit hooks prevent sensitive files from being committed
6
If a secret is committed, assume it's compromised
rotate immediately
7
Use git filter-branch or git filter-repo to remove secrets from history (last resort)
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is a .gitignore file?
02
How do I ignore a file that is already tracked?
03
Can I have multiple .gitignore files in a repository?
04
How do I test if a file is ignored by .gitignore?
05
What should I do if I accidentally committed a secret?
06
Should I ignore lockfiles like package-lock.json?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 12, 2026
last updated
2,406
articles · all by Naren
🔥

That's Git. Mark it forged?

3 min read · try the examples if you haven't

Previous
Git Show: Inspect Commits, Tags, and Files in Detail
47 / 47 · Git
Next
Introduction to Docker