.gitignore — The Missing File That Exposed Production Secrets
A developer forgot to add .env to .gitignore.
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
- ✓Git 2.x installed, basic command line familiarity, a GitHub or GitLab account for practice, and a text editor.
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.
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.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
.env Committed to GitHub — API Keys Compromised in 2 Hours
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.git added explicitly, it won't be committed. They didn't realize that git add . stages all untracked files in the directory, including .env..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..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.- Always create
.gitignoreBEFORE 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
.gitignorefor common patterns. - Run
git statusbefore every commit to verify what's staged.
*.log to .gitignore — ignores all .log files.env to .gitignore — ignores .env in any directorynode_modules/ to .gitignore — ignores the entire directorygit check-ignore -v <file> — shows which pattern matchesgit config --global core.excludesFile ~/.gitignore_global| File | Command / Code | Purpose |
|---|---|---|
| example-commit.sh | echo "API_KEY=sk-12345" > .env | Why .gitignore Matters |
| .gitignore | .env | Anatomy of a .gitignore File |
| .gitignore | __pycache__/ | Setting Up .gitignore for Different Languages and Frameworks |
| setup-global-gitignore.sh | echo ".DS_Store" >> ~/.gitignore_global | Global .gitignore for Personal Preferences |
| .gitignore | node_modules/ | Ignoring Build Artifacts and Dependencies |
| frontend | !important.log | Handling Nested .gitignore Files |
| test-gitignore.sh | git check-ignore -v .env | Testing Your .gitignore with git check-ignore |
| remove-tracked.sh | git rm --cached .env | Removing Files Already Tracked by Git |
| recover.sh | git add -f important.config | Recovering from .gitignore Mistakes |
Key takeaways
.gitignore BEFORE the first commitgit add . without running git status first.gitignore via core.excludesFile for universal patternsgit check-ignore -v debugs why a file is or isn't ignoredgit filter-branch or git filter-repo to remove secrets from history (last resort)Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
That's Git. Mark it forged?
3 min read · try the examples if you haven't