GitTracking Empty Folders (.gitkeep)

Tracking Empty Folders (.gitkeep)

Git tracks files, not folders. A directory that contains no tracked files is invisible to Git — when you commit, it does not appear in the repository, and when you clone, it does not get created. To preserve a meaningful empty folder, you put a placeholder file inside. The community convention is to call that file .gitkeep.

The problem

Empty folders disappear

Bash
mkdir uploads
git status
# (nothing to commit — Git does not see the empty folder)

# After cloning your repo elsewhere:
ls uploads
# ls: uploads: No such file or directory

Many projects need an empty directory to exist at runtime — for uploads, cache, logs, temp files, etc. Git itself does not care, but the application will crash if the path does not exist.

The solution: a placeholder file

Add a tiny file inside the folder

Bash
touch uploads/.gitkeep
git add uploads/.gitkeep
git commit -m "Preserve uploads/ directory"

Now uploads/ contains one tracked file (.gitkeep) — so Git sees the folder and includes it in clones. The file itself is empty and just exists for its name.

.gitkeep is just a convention

Important detail: .gitkeep is not a special Git filename. It is just a community-chosen name for a placeholder. You could call it .keep, placeholder, __init__.py, or anything else. Git has no idea this file is “for” keeping a folder.

Other common names
`.gitkeep` is the most popular. Python projects often use `__init__.py` for the same purpose. Some teams use plain `.keep`. Pick one, be consistent.
Combining with .gitignore

A common pattern: ignore everything inside an upload folder except the .gitkeep.

uploads/.gitignore

Text
# Ignore everything in this folder…
*

# …but track this file so the folder is preserved
!.gitkeep

# And keep this .gitignore itself
!.gitignore

Now the folder is preserved in the repo, but the files dropped into it at runtime are not tracked.

A README.md works just as well

Some teams prefer to put an actual README.md in the folder explaining what it is for. That is better than .gitkeep because it documents intent, and any future developer reading the repo learns why the folder exists.

uploads/README.md

Text
# Uploads folder

This directory holds user-uploaded files at runtime.
It is intentionally empty in version control.
```bash
$ ls uploads/
```
Files placed here at runtime are ignored by .gitignore.
Practical scenarios
  • Upload directories — user-generated files; the folder must exist before runtime.

  • Cache or temp folders — the app expects to write here.

  • Log directorieslogs/, tmp/, etc.

  • Build output placeholders — e.g., dist/ with a .gitkeep so deploy scripts have somewhere to copy files.

  • Convention foldersmigrations/, seeders/, scripts/ that begin empty in a fresh project.

Verifying it works

Bash
git ls-files | grep uploads
# uploads/.gitkeep   ← Git is tracking the folder

# After cloning the repo to a new machine:
git clone <url> fresh-copy
ls fresh-copy/uploads
# uploads/ exists, containing .gitkeep
Removing a .gitkeep later

Once the folder starts holding real tracked files, the placeholder becomes unnecessary. You can remove it:

Bash
git rm uploads/.gitkeep
git commit -m "Remove placeholder; folder now has real files"
Tip
For application code, prefer adding a meaningful README.md instead of a blank .gitkeep. A future contributor can read it; an empty file just makes them wonder why it exists.