Back to articles
TIL

Keeping Empty Directories in Git with .gitkeep

Why Git does not track empty directories, what .gitkeep actually does, and when a nested .gitignore is the clearer option.

In this essay 3 sections
  1. The problem it solves
  2. It has no special meaning
  3. .gitkeep vs .gitignore

Keeping Empty Directories in Git with .gitkeep

TL;DR: Git tracks files, not directories, so an empty folder never gets committed. .gitkeep is not a Git feature. It is a community-created naming convention: a tiny placeholder file that gives Git something in the folder to hold on to.

Here is the whole trick:

some-new-empty-folder
.gitkeep tracked placeholder file
Git can store this directory
The file is what Git tracks. The directory appears because it contains that file.

some-new-empty-folder on its own, being empty, will not be committed. Add one file inside it and Git now has something to track, so the directory shows up when someone clones the repo.

To create it:

mkdir some-new-empty-folder
touch some-new-empty-folder/.gitkeep
git add some-new-empty-folder/.gitkeep

The problem it solves

Sometimes your application expects a folder structure to exist. Some of the directories may be empty, but the code assumes they are there, like uploads, tmp, and generated. .gitkeep is how you ship the structure without any contents.

It has no special meaning

This is the part people miss. Git has never heard of .gitkeep. You could call the file anything and Git would behave the same. The secret sauce is not the name but a folder that is no longer empty.

.gitkeep vs .gitignore

Do not confuse the two:

FilePurposeWhat Git tracks
.gitkeepA convention for preserving a directory that would otherwise be empty.The placeholder file.
.gitignoreA set of ignore rules for untracked files.The .gitignore file itself, if you add it.

One important distinction: .gitignore does not stop Git tracking a file that has already been committed. You must remove that file from Git’s index separately if you want the ignore rule to take effect.

Sometimes you want both ideas at once. Say you want a logs/ directory to exist in the repo, but you do not want to commit the actual log files that land in it. Put a .gitignore inside the folder:

logs
.gitignore tracked
app.log ignored
error.log ignored
Commit the nested .gitignore, not the generated log files.

With:

*
!.gitignore

That reads as: ignore everything in logs/, except the .gitignore file itself. The folder gets preserved, and the file also documents that the generated contents are meant to stay untracked. That is often cleaner than a bare .gitkeep, which preserves the folder but explains nothing.

Join the discussion

Thoughts, questions, or a different perspective?

React to this essay or continue the conversation. Comments are powered by GitHub Discussions.