Last updated on 13th March 2026

Ignoring unwanted files

Why ignore files?

Your project directory will almost always contain files that don't belong in your Git repository. Build artifacts, dependency folders, secret configuration, editor settings, OS-generated junk — committing any of these creates noise for collaborators and can even leak credentials.

Common examples include:

  • Dependency directories installed by package managers (node_modules/, vendor/, venv/)
  • Build output generated by compilers or bundlers (dist/, build/, *.o, *.pyc)
  • Environment and secret files containing API keys or database credentials (.env, credentials.json)
  • OS files that your operating system creates automatically (.DS_Store, Thumbs.db)
  • Editor and IDE folders with personal workspace settings (.vscode/, .idea/, *.swp)
  • Logs and error reports generated at runtime (*.log, npm-debug.log*)

You could just avoid staging these files, but that's error-prone and they'll still clutter git status output. A better approach is to tell Git to ignore them automatically.

Creating a .gitignore file

Create a file called .gitignore in the root of your repository. Inside it, list patterns — one per line — that describe which files and directories Git should ignore.

# Create the file
touch .gitignore

# Open it in your editor and add patterns

Once you've saved your .gitignore, commit it to the repository so the rules are shared with everyone who works on the project:

git add .gitignore
git commit -m "Add .gitignore"

Tip: You should commit your .gitignore early — ideally in the first commit of a new project, before you accidentally commit files you don't want tracked.

Pattern syntax

Each line in a .gitignore file is a pattern. Here are the rules Git uses to match them:

Basic patterns

Pattern What it matches
config.yml Any file or directory named config.yml, at any depth
*.log Any file ending in .log
debug?.log debug0.log, debuga.log, etc. (? matches one character)
debug[0-9].log debug0.log through debug9.log

Directory patterns

Append a trailing slash to match directories only:

Pattern What it matches
logs/ Any directory named logs, at any depth
build/ Any directory named build, at any depth

Anchoring with a leading slash

Without a leading slash, a pattern matches at any level in the directory tree. Adding a leading / anchors the pattern to the directory where the .gitignore file lives:

Pattern What it matches
config.yml config.yml, src/config.yml, app/config.yml
/config.yml Only config.yml in the project root
/build/ Only the build/ directory in the project root

Double asterisk (**)

The ** wildcard matches across directory boundaries:

Pattern What it matches
**/logs logs, src/logs, app/src/logs
**/logs/*.log logs/debug.log, src/logs/error.log
src/** Everything inside src/, at any depth
a/**/b a/b, a/x/b, a/x/y/b

Negation (exceptions)

Prefix a pattern with ! to re-include a file that would otherwise be ignored:

# Ignore all .env files
*.env

# But keep the example file
!.env.example

Important caveat: You cannot re-include a file if its parent directory is already ignored. Git skips ignored directories entirely for performance, so it never looks inside them:

# This will NOT work as expected
build/
!build/important.js

To work around this, ignore the directory's contents instead of the directory itself:

# Ignore everything in build/ but allow exceptions
build/*
!build/important.js

Comments and blank lines

Lines starting with # are comments. Blank lines are ignored and can be used to organise your patterns into logical groups:

# Dependencies
node_modules/
vendor/

# Build output
dist/
build/

# Environment
.env
.env.local

Real-world .gitignore examples

Node.js

node_modules/
dist/
build/
.env
.env.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.yarn/cache
.yarn/unplugged
coverage/

Python

__pycache__/
*.py[cod]
*.so
venv/
.env
*.egg-info/
dist/
build/
.pytest_cache/
htmlcov/
.coverage

Ruby

vendor/bundle/
.bundle/
*.gem
Gemfile.lock
log/
tmp/
.env
coverage/

PHP / Laravel

vendor/
node_modules/
.env
storage/*.key
public/hot
public/storage
*.cache

WordPress

wp-content/uploads/
wp-content/cache/
wp-content/upgrade/
wp-config.php
.htaccess
*.log

General patterns (add to any project)

# OS files
.DS_Store
Thumbs.db
Desktop.ini

# Editors and IDEs
.vscode/
.idea/
*.swp
*.swo
*~
.project
.classpath
.settings/

# Misc
*.bak
*.tmp

Tip: The github/gitignore repository maintains a comprehensive collection of .gitignore templates for virtually every language and framework. It's a great starting point when setting up a new project.

Ignoring files that are already tracked

This is the most common gotcha with .gitignore: it only affects untracked files. If you've already committed a file, adding it to .gitignore won't make Git forget about it.

To stop tracking a file that's already been committed:

# Remove from Git's index but keep the file on disk
git rm --cached .env

# For an entire directory
git rm -r --cached node_modules/

# Then commit the removal
git commit -m "Stop tracking .env"

After this, the file stays on your local machine but Git treats it as untracked, and your .gitignore pattern will now take effect.

Warning: When other team members pull this commit, the file will be deleted from their working directory. If it's something like .env, make sure everyone knows to back up their copy first, or provide a .env.example template in the repo.

Global .gitignore

Some files — like .DS_Store on macOS or Thumbs.db on Windows — should be ignored everywhere, not just in one project. Instead of adding them to every repository's .gitignore, configure a global ignore file:

# Create the global ignore file
touch ~/.gitignore_global

# Tell Git to use it
git config --global core.excludesFile ~/.gitignore_global

A typical global .gitignore contains patterns for your OS and editor:

# macOS
.DS_Store
.AppleDouble
.LSOverride

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini

# Linux
*~
.directory

# Editors
.vscode/
.idea/
*.swp
*.swo
*.sublime-workspace
*.sublime-project

The global file applies to every repository on your machine, but it is not committed anywhere — it only affects you. Project-specific patterns should still go in the repository's .gitignore so they're shared with collaborators.

Local-only rules with .git/info/exclude

If you need to ignore files in a specific repository but don't want to modify the shared .gitignore, you can add patterns to .git/info/exclude. This file works exactly like .gitignore but is never committed.

This is useful for personal tooling or editor files that only affect your workflow:

# Add patterns directly
echo "my-scratch-notes.txt" >> .git/info/exclude
echo ".env.local.bak" >> .git/info/exclude

Nested .gitignore files

You can place a .gitignore file in any subdirectory of your repository, not just the root. Patterns in a nested .gitignore are relative to the directory where the file lives, and they only apply from that directory downward.

project/
├── .gitignore          # Project-wide rules
├── docs/
│   └── .gitignore      # Rules specific to docs/
└── src/
    └── .gitignore      # Rules specific to src/

When multiple .gitignore files exist, the rules from the most specific (deepest) file take precedence. In practice, most projects only need a single root-level .gitignore. Use nested files when a subdirectory has meaningfully different ignore requirements — for example, a docs/ folder that generates HTML output you don't want tracked.

Debugging with git check-ignore

When a file is being ignored and you can't figure out why — or it's not being ignored and you think it should be — use git check-ignore to debug:

# Check if a file is ignored and show which rule matched
git check-ignore -v path/to/file

Output example:

.gitignore:3:*.log    debug.log

This tells you that line 3 of .gitignore (the pattern *.log) is causing debug.log to be ignored.

To check multiple files at once:

git check-ignore -v *.log src/*.tmp

If the command produces no output, the file is not being ignored by any rule.

Keeping empty directories

Git only tracks files, not directories. If you need an empty directory to exist in the repository — for example, a logs/ folder that should be present but always empty — the convention is to add a .gitkeep file inside it:

mkdir logs
touch logs/.gitkeep

Then in your .gitignore:

# Ignore log files but keep the directory
logs/*
!logs/.gitkeep

Note: .gitkeep is not a Git feature — it's a community convention. The file name doesn't matter; it could be called anything. .gitkeep is simply the most widely recognised name for this purpose.

Quick reference

I want to... Pattern
Ignore all .log files *.log
Ignore node_modules/ everywhere node_modules/
Ignore .env only in the root /.env
Ignore everything in build/ except one file build/* then !build/keep.js
Ignore all .tmp files in any cache/ folder **/cache/*.tmp
Ignore a directory but keep it in the repo Add dir/* + !dir/.gitkeep
Stop tracking a file already committed git rm --cached <file>
Find out why a file is ignored git check-ignore -v <file>
Ignore files globally (all repos) git config --global core.excludesFile ~/.gitignore_global

Once your .gitignore is set up and your repository is clean, DeployHQ can deploy your code automatically — only the files that matter reach your server, while ignored files stay out of the deployment.