What Is a Bare Git Repository? (And When Push-to-Deploy Needs One)
CodexFuse Team · 2026-11-04T05:36:43.969Z · 11 min read min read
What Is a Bare Git Repository, Exactly?
A bare Git repository is a Git repository that stores your project's full history with no working files to edit. In the simplest terms, a bare repository is the contents of your project's .git directory and nothing else — as the Pro Git book puts it, "a remote repository is generally a bare repository — a Git repository that has no working directory." Git's own glossary phrases it this way: "a bare repository is normally an appropriately named directory with a .git suffix that does not have a locally checked-out copy of any of the files under revision control." If you are still getting comfortable with Git itself, our Git beginner guide walks through repositories, commits, and branches from zero.
Your first bare repository takes about one minute:
- Create it with one flag. Run
git init --bare my-project.git— the--bareflag tells Git there will be no working directory. - Inspect what you got. Open the folder and you will see only Git's own data:
HEAD,config,objects/,refs/, and a few support directories — zero project files. - Confirm it is bare. Run
git rev-parse --is-bare-repositoryinside it; Git answerstrue. - Push into it. From your working project, add it as a remote (
git remote add hub ~/my-project.git) andgit push hub main— the push succeeds because there is no checked-out branch to disturb. - Clone from it. Anyone who runs
git clone ~/my-project.gitgets a normal working repository with a full copy of the history — the bare repo acted as the hub.
What Does a Bare Repository Actually Contain?
Nothing you can edit — and that is the whole point. A bare repository holds only the machinery Git needs to record and share history, identical to the hidden .git folder inside every project you have ever committed to. Here is what a fresh git init --bare demo.git really produces, verified on Git 2.43 this run:
demo.git/ HEAD - symbolic ref to the default branch (ref: refs/heads/main) branches/ - branch tracking helpers (mostly legacy) config - repo settings; contains "bare = true" under [core] description - short repo description, used by some web front-ends hooks/ - scripts Git runs on events (push, commit, checkout) info/ - exclude rules and other auxiliary data objects/ - every commit, tree, and blob in the project's history refs/ - branch and tag pointers (refs/heads/*, refs/tags/*)
| Folder | What it stores | Do you edit it by hand? |
|---|---|---|
objects/ | Compressed content of every commit, file, and tree — the immutable history | No — Git writes it; your push fills it |
refs/ | Branch and tag names pointing at commit hashes — the mutable pointers | No — git push and git branch manage them |
config | Repository settings, including bare = true | Rarely — git config when you need it |
HEAD | Which branch is the default when someone clones | Via git symbolic-ref HEAD refs/heads/main |
hooks/ | Scripts that fire on events — the engine of push-to-deploy | Yes — this is where you add your deploy hook |
branches/, info/, description | Helpers and metadata carried over from Git's template | No |
Notice what is missing: your code. There is no index.html, no README.md, nothing to open in an editor. That absence is not a limitation of the tool — it is the design that lets a bare repository safely accept pushes from anywhere.
Bare vs Non-Bare Repositories: What's the Difference?
The only structural difference is the working tree. A non-bare repository is the one you work in every day — project files plus a hidden .git folder. A bare repository drops the project files and promotes the .git contents to the top level. Everything else — commits, branches, tags, remotes — behaves the same.
| Non-bare (your laptop) | Bare (your server) | |
|---|---|---|
| Working files you can edit | Yes — the whole project | No — none exist |
| Where Git's data lives | .git/ subdirectory | The directory itself is the data |
| Created with | git init or git clone | git init --bare or git clone --bare |
| Directory naming | Anything (my-project/) | By convention ends in .git (my-project.git) |
Can you git commit in it? | Yes | No — there is nothing to stage |
Accepts git push to its active branch? | No — refused by default | Yes — that is its job |
| Typical location | Your machine | A shared server or VPS |
Think of it like a library's archive room: patrons read and annotate in the reading room (your non-bare repo), but the master copies live in the archive where nobody scribbles in the margins (the bare repo). The archive exists so every reader shares one source of truth. If you came here searching for the "bare vs non-bare git" distinction, that table above is the complete answer.
Why Does Git Refuse Pushes to a Non-Bare Repository?
Why does a push to a non-bare repository fail, in one sentence? Pushing to the checked-out branch would silently desynchronize the branch pointer from the files on disk, so Git blocks it. The setting responsible is receive.denyCurrentBranch, and its default is refuse — a behavior Git has enforced since 2009. Git's documentation states it plainly: "If set to true or 'refuse', git-receive-pack will deny a ref update to the currently checked out branch of a non-bare repository. Such a push is potentially dangerous because it brings the HEAD out of sync with the index and working tree."
Try it and the failure is loud and specific:
$ git push origin main remote: error: refusing to update checked out branch: refs/heads/main remote: error: By default, updating the current branch in a non-bare remote: repository is denied, because it will make the index and work tree remote: inconsistent with what you pushed, and will require 'git reset --hard' remote: to match the work tree to HEAD. ! [remote rejected] main -> main (branch is currently checked out)
This is exactly why the bare repository exists. With no working tree and no checked-out branch, there is nothing to fall out of sync — the push simply moves branch pointers and stores the new objects, and the repository stays consistent by construction. Whenever you see "branch is currently checked out" on a server, you have found the use case bare repos were built for.
How Do You Create a Bare Repository?
Two commands, depending on whether you are starting fresh or converting an existing project. For a brand-new hub on your server, create it directly: git init --bare ~/repos/website.git. The directory name ending in .git is a convention rather than a rule — Pro Git notes that "by convention, bare repository directory names end with the suffix .git," and following it keeps your server's repos instantly recognizable.
To turn an existing project into a bare hub, clone it with the flag: git clone --bare my-project my-project.git. Pro Git describes this as roughly equivalent to copying the project's .git folder into a new directory — same history, no files. Either way, cloning from the bare repository gives collaborators a perfectly normal working repo: git clone user@your-server:/srv/git/website.git produces project files plus a .git folder, ready to edit and commit. If you have not used hosted Git services before, our GitHub beginner guide shows how this same push-and-pull rhythm works on a public platform.
One detail that surprises beginners: a bare repo's default branch is decided by its HEAD file, and you change it with git symbolic-ref HEAD refs/heads/main run inside the bare directory. When you first create the repo, git init will hint that the initial branch name defaults to master and can be changed globally with git config --global init.defaultBranch main — set this before creating repos if you want main everywhere.
When Do You Need One? Push-to-Deploy on Your VPS
You need a bare repository whenever you want git push to deliver code somewhere — and the classic somewhere is a small VPS running your website. The pattern is simple: a bare repo on the server receives your pushes, and a hook checks the files out into the web root. If you are weighing server options, our VPS hosting guide explains the model; if your deploys travel over SSH keys, the SSH config guide makes the connection one word long.
Here is the full arc, server first:
- Make the bare repo on the server. SSH in and run
mkdir -p ~/repos && git init --bare ~/repos/website.git. - Add a deploy hook. Create
~/repos/website.git/hooks/post-receivecontaininggit --work-tree=/var/www/website --git-dir=$HOME/repos/website.git checkout -f, thenchmod +xit. Git's docs confirm the trigger: "This hook is invoked by git-receive-pack when it reacts togit pushand updates reference(s) in its repository. It executes on the remote repository once after all the refs have been updated." - Create the web root first. Run
mkdir -p /var/www/website— the checkout fails if the target directory does not already exist. - Wire up your laptop. From your project, run
git remote add live ssh://deploy@your-server/~/repos/website.git. - Deploy by pushing.
git push live mainsends the commits, the hook fires, and the latest files land in/var/www/website— deploy complete, no FTP, no manual upload.
This is the moment the concept clicks: without the bare repository, step five would hit the "branch is currently checked out" wall. With it, your server is just a Git remote with a small script attached — every push to a bare repository from your laptop becomes a deploy, and each deploy is a push you can undo with git revert like any other change.
Can You Work Inside a Bare Repository?
No — and you should not try. There are no files to open, no working tree to stage from, and git commit has nothing to commit. All editing happens in clones; the bare repo only receives and serves history. If you find yourself wanting to edit on the server, clone from the bare repo into a working directory next to it instead of touching the hub.
One related flag worth knowing: git clone --mirror implies --bare but additionally maps all refs — including remote-tracking branches and notes — and configures forced updates on every fetch. A mirror is for exact backups of another repository, not for a deploy target you never fetch from. For push-to-deploy, plain --bare is the right tool; --mirror is overkill.
Sources
Every technical claim in this article was verified against a fetched source during this run. Git's official glossary (git-scm.com/docs/gitglossary): the bare-repository definition. The Pro Git book, "Git on the Server" chapters (git-scm.com/book/en/v2): the "simplest terms" definition, the .git naming convention, and the git clone --bare recipe. The git-config documentation (kernel.googlesource.com): receive.denyCurrentBranch defaults and semantics. The git-clone documentation (git-scm.com/docs/git-clone): --bare and --mirror semantics. The githooks documentation (git-scm.com/docs/githooks): the post-receive trigger. Command outputs (directory listing, bare = true config, push-refusal error) were reproduced locally on Git 2.43. Full research notes: ~/workspace/new-blog-program/research/bare-git-research.md.
Frequently Asked Questions
Is a bare repository just a .git folder with a different name?
Essentially, yes — a bare repository is the contents of a project's .git directory placed directly at the top level, with no working files around it. Pro Git's one-line version: "a bare repository is the contents of your project's .git directory and nothing else." That is why git clone --bare my-project my-project.git is roughly equivalent to copying my-project/.git to my-project.git.
Can I edit files directly in a bare repository?
No. There are no files to edit — only Git's internal data (objects, refs, config, hooks). If you need to make changes, clone the bare repository, edit in the clone, commit there, and push back. The bare repo accepts the push; your clone holds the files.
Why does my push say "branch is currently checked out"?
You are pushing to the active branch of a non-bare repository, and Git refuses this by default via the receive.denyCurrentBranch setting (default refuse since 2009), because it would desynchronize the branch from the files on disk. Fix it the right way: make the receiving repository bare with git init --bare instead of overriding the safety check.
Does a bare repository have to end in .git?
No — the .git suffix is a convention, not a requirement. Git works exactly the same if you name the directory website-deploy. Following the convention is still worth it: anyone who has managed servers instantly recognizes website.git as a push target rather than a working project.
Should I use git clone --mirror for my deploy server?
Almost certainly not. --mirror implies --bare but additionally maps every ref (including remote-tracking branches and notes) and forces updates on each fetch — machinery for exact backups of another repository. A deploy target is never fetched from, so plain --bare gives you everything you need with none of the extra behavior.

