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:

  1. Create it with one flag. Run git init --bare my-project.git — the --bare flag tells Git there will be no working directory.
  2. 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.
  3. Confirm it is bare. Run git rev-parse --is-bare-repository inside it; Git answers true.
  4. Push into it. From your working project, add it as a remote (git remote add hub ~/my-project.git) and git push hub main — the push succeeds because there is no checked-out branch to disturb.
  5. Clone from it. Anyone who runs git clone ~/my-project.git gets 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/*)
FolderWhat it storesDo you edit it by hand?
objects/Compressed content of every commit, file, and tree — the immutable historyNo — Git writes it; your push fills it
refs/Branch and tag names pointing at commit hashes — the mutable pointersNo — git push and git branch manage them
configRepository settings, including bare = trueRarely — git config when you need it
HEADWhich branch is the default when someone clonesVia git symbolic-ref HEAD refs/heads/main
hooks/Scripts that fire on events — the engine of push-to-deployYes — this is where you add your deploy hook
branches/, info/, descriptionHelpers and metadata carried over from Git's templateNo

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 editYes — the whole projectNo — none exist
Where Git's data lives.git/ subdirectoryThe directory itself is the data
Created withgit init or git clonegit init --bare or git clone --bare
Directory namingAnything (my-project/)By convention ends in .git (my-project.git)
Can you git commit in it?YesNo — there is nothing to stage
Accepts git push to its active branch?No — refused by defaultYes — that is its job
Typical locationYour machineA 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:

  1. Make the bare repo on the server. SSH in and run mkdir -p ~/repos && git init --bare ~/repos/website.git.
  2. Add a deploy hook. Create ~/repos/website.git/hooks/post-receive containing git --work-tree=/var/www/website --git-dir=$HOME/repos/website.git checkout -f, then chmod +x it. Git's docs confirm the trigger: "This hook is invoked by git-receive-pack when it reacts to git push and updates reference(s) in its repository. It executes on the remote repository once after all the refs have been updated."
  3. Create the web root first. Run mkdir -p /var/www/website — the checkout fails if the target directory does not already exist.
  4. Wire up your laptop. From your project, run git remote add live ssh://deploy@your-server/~/repos/website.git.
  5. Deploy by pushing. git push live main sends 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.

Featured on Twelve Tools Hunt for Tools Featured on LaunchBuff Launch Llama Tools Launch your MCP Top Web Development Companies in the US - OnToplist.com Designer Listings Listed on SeeWhatNewAI