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
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
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.
Combining with .gitignore
A common pattern: ignore everything inside an upload folder except the .gitkeep.
uploads/.gitignore
# 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
# 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 directories —
logs/,tmp/, etc.Build output placeholders — e.g.,
dist/with a.gitkeepso deploy scripts have somewhere to copy files.Convention folders —
migrations/,seeders/,scripts/that begin empty in a fresh project.
Verifying it works
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:
git rm uploads/.gitkeep git commit -m "Remove placeholder; folder now has real files"
README.md instead of a blank .gitkeep. A future contributor can read it; an empty file just makes them wonder why it exists.