Ctrl + K
Git20 min read

.gitignore Best Practices

A practical guide to writing effective .gitignore files, including patterns, wildcards, environment variables, dependencies, build artifacts, IDE files, monorepos, and common mistakes.

Published: 2026-10-05

The .gitignore file tells Git which files and directories should be ignored when tracking changes. A well-designed .gitignore prevents generated files, local configuration, operating system files, IDE metadata, dependencies, and other unnecessary files from appearing in a repository.

A good .gitignore is more than a list copied from the internet. Its patterns should reflect the actual project, avoid hiding important source files, and make it clear which files are intentionally excluded. This is especially important for environment files and other files that may contain credentials or sensitive configuration.

This guide explains how .gitignore works and covers practical best practices for JavaScript and TypeScript projects, backend applications, Docker-based projects, monorepos, IDEs, operating systems, and team repositories.

What Is .gitignore?

A .gitignore file contains patterns that tell Git which untracked files should be ignored. It is normally placed in the root directory of a repository, although Git also supports .gitignore files in subdirectories.

node_modules/
dist/
.env
.DS_Store

In this example, Git ignores the Node.js dependency directory, a common build directory, a local environment file, and a macOS metadata file.

The purpose of .gitignore is primarily to prevent untracked files from being considered for addition. It is not a general-purpose security mechanism and does not automatically remove files that are already tracked.

Why a Good .gitignore Matters

Repositories contain many files that should not be committed. Some are generated automatically, some are specific to a developer's machine, and others may contain local configuration or secrets.

  • Keep generated build output out of the repository.
  • Avoid committing dependency directories such as node_modules.
  • Prevent local environment configuration from being committed accidentally.
  • Exclude operating system metadata files.
  • Ignore IDE-specific files when they are not part of the project's shared configuration.
  • Reduce unnecessary changes in Git status.
  • Keep pull requests focused on source and configuration that actually belongs in version control.
💡 A useful .gitignore should exclude files that are generated, machine-specific, temporary, or intentionally local while leaving source code and required project configuration visible to Git.

How Gitignore Patterns Work

Gitignore uses pattern matching to determine which paths should be ignored. Patterns can refer to individual files, directories, file extensions, and more complex path structures.

temp.txt
*.log
build/
config/*.local.json

The exact behavior of a pattern depends on its position, slashes, wildcards, and whether it ends with a directory separator. Understanding a few basic pattern rules is enough for most projects.

Ignoring a Specific File

A simple filename can be used when a particular file should be ignored.

debug.log

Depending on where the pattern appears and whether it contains a slash, Git can match the name at different levels of the directory hierarchy. If you need to make the intended location explicit, use a path.

/debug.log

The leading slash makes the pattern relative to the directory containing that .gitignore file.

Ignoring a Directory

To ignore a directory and its contents, add a trailing slash.

node_modules/
dist/
coverage/
.tmp/

Using the trailing slash communicates that the pattern is intended to match a directory rather than a file with the same name.

Ignoring File Extensions with *

The asterisk wildcard can match characters within a path component. It is commonly used to ignore all files with a particular extension.

*.log
*.tmp
*.cache

This can be useful for generated logs and temporary files, but broad extension patterns should be used carefully. A file extension might be valid source material in some parts of a project.

Using ** for Nested Paths

The double-asterisk pattern can match across directory levels. It is useful when the same type of directory or file can appear at multiple depths.

**/node_modules/
**/*.log

In many projects, a simpler pattern such as node_modules/ is sufficient because Gitignore matching already handles directory names without requiring a recursive wildcard. Use ** when the path structure actually benefits from it.

Negation with !

The exclamation mark can be used to re-include a path that was previously excluded by another pattern.

*.log
!important.log

Here, log files are ignored except for important.log.

Negation becomes more complicated when parent directories are ignored. Git must be able to traverse the parent path before a file inside it can be re-included.

logs/
!logs/important.log

This pattern may not produce the intended result because the logs directory itself is excluded. When using negation, test the exact path with Git rather than assuming the pattern behaves as expected.

Use Specific Patterns Instead of Overly Broad Ones

One of the most important .gitignore practices is avoiding patterns that can hide files unintentionally. For example, ignoring every file with a particular extension may be convenient but dangerous if the project legitimately tracks some files using that extension.

# Potentially too broad
*.json

# More specific
/local-config/*.json

The second pattern communicates a much narrower intention. In general, ignore the smallest set of paths necessary to solve the problem.

Ignore Dependencies

JavaScript and TypeScript projects normally should not commit node_modules. Dependencies are installed from package manifests and lockfiles rather than tracked as thousands of generated files.

node_modules/

The package.json and the project's appropriate lockfile should generally remain tracked. They provide the information required to reproduce the dependency installation.

node_modules/

# Usually tracked:
# package.json
# package-lock.json
# pnpm-lock.yaml
# yarn.lock
💡 Ignoring node_modules does not mean ignoring dependency definitions. Keep the package manifest and the lockfile appropriate for the project's package manager under version control.

Ignore Build Output

Build tools often generate files that can be recreated from source code. These files usually do not belong in Git unless the project's deployment or distribution process explicitly requires them.

dist/
build/
out/
coverage/

The correct directories depend on the framework and build system. Do not blindly add every possible build directory to a project. Keep the file relevant to the tools actually used by the repository.

Next.js and Frontend Projects

A Next.js project commonly needs to ignore generated directories and local environment files.

.next/
out/
node_modules/
.env.local
.env.development.local
.env.test.local
.env.production.local

The exact environment files depend on the project. If an environment file contains only non-sensitive defaults and the team intentionally tracks it, it should not automatically be added to .gitignore.

Environment Variables and .env Files

Environment files are one of the most important categories to handle carefully. They often contain API keys, database credentials, tokens, private endpoints, and other local configuration.

.env
.env.local
.env.*.local

Many projects use a tracked example file to document the required variables without committing real credentials.

.env.example

DATABASE_URL=
API_KEY=
PUBLIC_API_URL=

The example file should contain placeholders or safe example values rather than real credentials.

⚠️ A .gitignore rule does not protect a secret that has already been committed. If a credential is accidentally committed, treat it as exposed and rotate or revoke it as appropriate, then remove it from Git history if necessary.

Should .env Be Ignored Completely?

Not every environment file needs to be treated identically. A project may intentionally track a safe configuration file containing non-sensitive defaults while ignoring local and secret-bearing variants.

FileTypical approachReason
.envOften ignoredMay contain local or sensitive configuration
.env.localUsually ignoredMachine-specific local configuration
.env.exampleUsually trackedDocuments required variables without real secrets
.env.productionDepends on projectCan contain configuration or sensitive values
.env.production.localUsually ignoredLocal production-specific configuration

The correct rule depends on what the files contain and how the project manages configuration. Do not rely on the filename alone when deciding whether something belongs in Git.

Operating System Files

Operating systems can create metadata files that are irrelevant to the project. Common examples include .DS_Store on macOS and Thumbs.db on Windows.

.DS_Store
Thumbs.db
Desktop.ini

These patterns are often useful in shared repositories because they prevent machine-specific metadata from appearing in commits.

IDE and Editor Files

Editors and IDEs may create project-specific metadata, caches, workspace files, or temporary files. Some should be ignored, while shared project configuration can be worth committing.

.idea/
*.iml
.vscode/*

Be careful with broad editor patterns. For example, VS Code settings may contain useful shared configuration such as formatting rules, recommended extensions, or debugging settings.

.vscode/*
!.vscode/settings.json
!.vscode/extensions.json

Whether these files should be committed is a team decision. The important point is to distinguish personal workspace state from configuration that benefits every contributor.

.gitignore vs .editorconfig

.gitignore and .editorconfig solve different problems. .gitignore controls which files Git should ignore, while .editorconfig helps editors apply consistent formatting and text-file settings.

FilePurposeExample
.gitignoreGit file exclusionnode_modules/
.editorconfigEditor and formatting behaviorindent_size = 2

A project can and often should use both. They complement each other rather than replace one another.

.gitignore vs .dockerignore

.gitignore controls what Git considers ignored, while .dockerignore controls which files are excluded from the Docker build context.

.gitignore
node_modules/
.env
.next/

.dockerignore
node_modules/
.git/
.next/
.env

The files can contain overlapping patterns, but they serve different systems. A file being ignored by Git does not automatically mean it will be excluded from a Docker build context.

Keep .gitignore in Version Control

The .gitignore file itself should normally be committed to the repository. It is part of the project's development configuration and communicates to every contributor which generated or local files should be ignored.

git add .gitignore
git commit -m "chore: add gitignore rules"

A shared .gitignore avoids situations where each developer maintains a different local list of ignored files.

Use Comments to Explain Non-obvious Rules

Comments can make a .gitignore easier to maintain, especially when the file contains project-specific exceptions.

# Dependencies
node_modules/

# Next.js build output
.next/
out/

# Local environment files
.env
.env.local

# Test coverage
coverage/

Avoid comments for every obvious rule. Grouping related patterns into small sections is usually enough.

Organize Rules by Category

As a project grows, an organized .gitignore becomes easier to review. Grouping dependencies, build output, environment files, operating system files, and IDE metadata makes the purpose of each rule clearer.

# Dependencies
node_modules/

# Build output
dist/
.next/
coverage/

# Environment
.env
.env.local

# OS files
.DS_Store
Thumbs.db

# IDE
.idea/
.vscode/*.log

Do Not Ignore Files Just to Hide Git Status

A common mistake is adding a file to .gitignore simply because it appears in git status. Before ignoring it, determine why the file exists and whether it should actually be tracked.

For example, a generated configuration file might be intentionally committed because deployment depends on it. Ignoring it without understanding its role can create missing files for other developers or CI systems.

The Difference Between Ignored and Untracked

An untracked file is a file Git sees but has not yet added to the repository. An ignored file matches an ignore rule and is normally hidden from standard status output.

git status
git status --ignored

The second command is useful when troubleshooting .gitignore because it can show ignored files as well.

Why .gitignore Does Not Affect Already Tracked Files

One of the most common misunderstandings is expecting a new .gitignore rule to stop Git from tracking a file that has already been committed.

echo ".env" >> .gitignore
git status

If .env was already tracked, adding it to .gitignore does not remove it from the index. You need to remove the tracked copy from Git's index while optionally keeping the local file.

git rm --cached .env
git commit -m "chore: stop tracking local environment file"
⚠️ If the tracked file contained credentials, removing it from the current version is not enough to make the credentials safe. Previously committed values can remain in repository history and may need to be revoked and removed from history.

Check Which Rule Is Ignoring a File

Git provides a useful command for debugging ignore behavior.

git check-ignore -v path/to/file

The -v option can show the ignore rule responsible for the match. This is especially useful when several .gitignore files, global ignore rules, or nested patterns are involved.

Global Gitignore Rules

Git can use a global excludes file for machine-specific patterns. This can be useful for operating system or personal editor files that should never be committed from a particular development environment.

For example, a developer might configure global rules for local IDE metadata instead of adding every personal file to every repository's .gitignore.

Project-specific rules should generally remain in the repository's .gitignore so that every contributor receives the same behavior.

Nested .gitignore Files

A repository can contain .gitignore files in subdirectories. This can be useful when a particular part of a monorepo or project has its own generated files.

repository/
  .gitignore
  apps/
    frontend/
      .gitignore
  packages/
    shared/
      .gitignore

Nested rules can make a repository flexible, but too many separate ignore files can also make behavior harder to understand. Keep rules close to the files they specifically govern when that improves clarity, but avoid unnecessary fragmentation.

.gitignore in Monorepos

Monorepos often contain several applications and packages with different build tools. A root .gitignore can handle common exclusions, while package-specific .gitignore files can handle unusual local artifacts.

# Root .gitignore
node_modules/
.env
.DS_Store
coverage/

# Application-specific examples
apps/*/.next/
apps/*/dist/

Before adding package-specific patterns, check whether a simpler root-level rule already handles the files. Avoid creating duplicate rules unless they make the repository easier to understand.

Generated Files That Should Be Committed

Not every generated file should automatically be ignored. Some projects intentionally commit generated files because consumers need them, deployment requires them, or the repository treats them as part of its published source.

Examples can include generated documentation, static assets, code generated for distribution, or files required by a particular publishing workflow. The correct decision depends on the project's architecture and release process.

Lockfiles Should Not Be Ignored Automatically

Package manager lockfiles are an important example of a file that developers sometimes mistakenly ignore. In applications, lockfiles are commonly committed because they record resolved dependency versions and help make installations reproducible.

# Usually do not add these to .gitignore
package-lock.json
pnpm-lock.yaml
yarn.lock

The exact policy can differ for libraries and some specialized workflows, but ignoring lockfiles simply because they are generated by a package manager is not a good general rule.

Avoid Committing Credentials Even If Gitignore Is Missing

A .gitignore file is helpful, but developers should not rely on it as the only protection against accidental secret exposure. Credentials should be handled through appropriate secret-management and environment-configuration practices.

  • Do not place production credentials in source code.
  • Do not commit private API keys or access tokens.
  • Use safe example values in .env.example files.
  • Review staged changes before committing.
  • Rotate credentials immediately if they are accidentally exposed.
  • Use secret scanning or repository security tools where appropriate.

Review the Staged Files Before Committing

A .gitignore rule reduces the chance of accidentally adding unwanted files, but it should not replace reviewing the actual staged changes.

git status
git diff --staged
git ls-files

This is particularly important before committing configuration changes or working with projects that contain sensitive data.

A Practical .gitignore for a Node.js Project

A typical Node.js application can start with a relatively small set of rules and expand only when the project actually needs additional exclusions.

# Dependencies
node_modules/

# Build output
dist/
build/
.next/
out/

# Test coverage
coverage/

# Environment files
.env
.env.local
.env.*.local

# Logs
*.log

# OS files
.DS_Store
Thumbs.db

# IDE
.idea/
.vscode/*.log

This is a starting point rather than a universal template. A Vite application, Next.js application, Node.js API, library, or monorepo may need different rules.

A Practical .gitignore Workflow

Creating a good .gitignore is easiest when it is treated as part of the project's initial setup rather than as a file that is fixed only after unwanted files appear in Git.

  • Identify the project's package manager and framework.
  • List generated directories and build artifacts.
  • Identify local environment files.
  • Identify IDE and operating system metadata that should not be shared.
  • Add only the patterns relevant to the project.
  • Commit the .gitignore file.
  • Test the rules with git status and git check-ignore.
  • Update the file when the project's tooling changes.

Common .gitignore Mistakes

MistakeProblemBetter approach
Ignoring everything generatedSome generated files may intentionally belong in the repositoryDecide based on the project's build and release workflow
Ignoring all JSON filesImportant configuration and source data can disappear from Git statusIgnore specific local JSON paths
Ignoring .env after it was committedThe file remains trackedRemove it from the index and handle exposed credentials
Ignoring lockfilesDependency resolution may become less reproducibleFollow the project's package manager policy
Copying a huge templateThe repository gets unrelated rulesKeep only relevant patterns
Using too many exceptionsThe file becomes difficult to reason aboutPrefer simpler patterns where possible
Ignoring IDE configuration blindlyUseful shared settings may be lostSeparate personal metadata from shared configuration

How to Keep .gitignore Maintainable

A .gitignore file should evolve with the project. When a new build tool, framework, IDE, or deployment system is introduced, review whether it creates files that should be ignored.

  • Remove obsolete rules when tools are no longer used.
  • Avoid duplicate patterns.
  • Group related rules together.
  • Use comments for non-obvious project-specific behavior.
  • Prefer precise rules over unnecessarily broad wildcards.
  • Review the file during major tooling changes.
  • Keep project-specific rules in version control.

Should You Use a .gitignore Generator?

A .gitignore generator can be useful when starting a new project or combining several technologies. It can quickly produce a baseline for a language, framework, operating system, or development environment.

However, generated output should be reviewed before committing. A template may contain patterns for tools that your project does not use or rules that are too broad for your repository.

Gitignore Best Practices Checklist

  • Keep .gitignore in the repository.
  • Ignore dependencies such as node_modules when appropriate.
  • Ignore build and generated output that does not belong in source control.
  • Handle environment and secret-bearing files carefully.
  • Track safe example configuration such as .env.example when useful.
  • Do not automatically ignore package lockfiles.
  • Exclude irrelevant operating system metadata.
  • Separate personal IDE state from useful shared editor configuration.
  • Prefer specific patterns over unnecessarily broad wildcards.
  • Use comments and sections to keep larger files understandable.
  • Use git check-ignore when debugging unexpected behavior.
  • Remember that .gitignore does not remove already tracked files.
  • Review staged changes before committing.
  • Update the file when project tooling changes.

Helpful Project Configuration Tools

Several developer tools can simplify project configuration. A Gitignore builder can help generate a starting set of ignore rules for a project's technologies. An .editorconfig generator can help establish consistent editor settings across contributors.

Docker-based projects can also benefit from a Docker ignore generator, while environment variable generators can help create safe configuration templates. A package.json formatter can keep Node.js project metadata consistently formatted.

What is the purpose of a .gitignore file?

A .gitignore file defines patterns for files and directories that Git should ignore. It is commonly used for dependencies, generated build output, local configuration, environment files, IDE metadata, and operating system files.

Does .gitignore remove files that are already committed?

No. .gitignore primarily affects files that are not already tracked. If a file is already tracked, you generally need to remove it from the Git index separately before the ignore rule can prevent it from being tracked again.

Should .env be included in .gitignore?

Environment files containing local configuration or secrets are commonly ignored. A safe .env.example file is often committed to document required variables without exposing real credentials. The exact policy depends on what each environment file contains.

Should node_modules be in .gitignore?

Yes, Node.js projects normally ignore node_modules because dependencies can be installed from the project's package manifest and lockfile. Committing node_modules creates a large amount of unnecessary repository data.

Should package-lock.json be ignored?

For many applications, package-lock.json, pnpm-lock.yaml, or yarn.lock should be committed because lockfiles record resolved dependency versions and help make installations reproducible. The exact policy depends on the project type and package manager workflow.

How can I find out why Git is ignoring a file?

Use git check-ignore -v path/to/file. The verbose output can identify the ignore rule responsible for matching the path, which is useful when debugging complex or nested .gitignore configurations.

Can I have more than one .gitignore file?

Yes. Git supports .gitignore files in subdirectories, and projects can also use global excludes for machine-specific patterns. Keep shared project rules in version-controlled .gitignore files and avoid unnecessary fragmentation.

Conclusion

.gitignore is a small but important part of a Git repository. A well-designed file keeps dependencies, generated artifacts, local configuration, operating system metadata, and other unnecessary files out of the project's history while leaving important source files and shared configuration under version control.

The best approach is to start with the project's actual requirements instead of blindly copying a large template. Use precise patterns, organize rules into logical sections, handle environment files carefully, and test unexpected behavior with Git's ignore-related commands.

Remember that .gitignore is not a security boundary and does not remove files that are already tracked. Combine sensible ignore rules with careful review of staged changes and proper secret-management practices to keep a repository clean, predictable, and safer to maintain.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.