Git

What Is a Bare Repository? When and Why to Use One

What Is a Bare Repository? When and Why to Use One

Open a bare repository in a file browser and you won’t find a single project file. No index.html, no src folder, nothing to edit. Just HEAD, objects, refs and a few other items.

That’s the whole idea. The full commit history and refs live there with no checked-out working tree, which lets it act as a central remote that takes pushes and answers fetches.

Most teams keep one on an SSH server or a hosting service, and each developer pushes to it from a normal local clone.

It isn’t an empty shell either. Per the git-init and gitrepository-layout manuals, the directory still holds HEAD, objects and refs. Only the index file is usually missing.

What Is a Bare Repository?

YouTube player

It keeps the full commit history and refs and skips the working tree entirely, so it can receive pushes and serve fetches as a shared remote.

Nothing inside can be edited in place. No git add, no git commit, no project files to open.

The object database matches the one in a regular Git repository, which is why clones, fetches and pushes treat both types the same way.

It also isn’t a development copy, since nobody edits files in it. And it isn’t quite a mirror. A mirror clone is bare too, but it copies every ref and overwrites them on update.

Atlassian’s git init tutorial describes the standard setup. The shared remote repository is bare, and each developer’s local copy is non-bare.

Hosting services follow the same pattern. The CodeRefinery Git course notes that GitHub and GitLab store bare repositories.

Why is GitHub the heart of open source?

Uncover GitHub statistics: developer community growth, repository trends, collaboration patterns, and the platform that powers modern software development.

Explore GitHub Data →

How a Bare Repository Differs From a Non-Bare Repository

YouTube player

A non-bare repository keeps its Git data in a .git subfolder next to the project files. A bare one puts those same contents at the top level of its directory, with no project files beside them.

AspectNon-bare repositoryBare repository
Working treePresentAbsent
git add and git commitRun in placeNot possible in place
LayoutData inside .gitData at the directory root
core.barefalsetrue
Usual roleDeveloper copyShared central remote
Push to the checked-out branchRefused by defaultNo checked-out branch, so accepted

The gitrepository-layout page in the Git documentation names the two flavours directly. One is a .git directory at the root of a working tree, the other is a project.git directory that is typically used for exchanging histories by pushing into it and fetching from it.

What the directory holds

Every valid repository needs HEAD, and it records the default branch. The objects folder stores each commit, tree and blob.

Branch and tag pointers sit under refs/heads and refs/tags with the default files backend. The git-init manual for Git 2.54 (April 2026) also accepts --ref-format=reftable.

Settings go in config, core.bare included. The hooks folder ships sample scripts that do nothing until you remove the .sample suffix. And index is usually absent, since there’s no working tree to stage from.

Looking to sharpen your Git skills? Branching, merging, rebasing, and everything else you need - including git rebase, git stash, and commit workflows - is on one page in the Git Cheat Sheet.

A popular tutorial on TheServerSide (2026) lists refs among the folders a bare repository lacks. That is wrong. The git-init manual describes every new repository as containing objects, refs/heads and refs/tags, and the git-clone manual says a bare clone copies the remote’s branch heads straight into local branch heads, which live under refs/heads.

The core.bare flag

Inside the git config file of a bare repository you’ll find core.bare = true. Any command that needs a working tree stops with an error.

Naming convention

The directory name usually ends in .git, as in my\_project.git (Pro Git, 2nd edition). Git doesn’t enforce it.

How to Create a Bare Repository

YouTube player

Starting from nothing, use git init --bare. If a repository already exists, git clone --bare does the job.

The git init command with the –bare flag builds the repository in the current directory when GIT\_DIR is not set (git-init manual, Git 2.54, April 2026).

▶ GIT EXPLAINED

If you commit, branch, or merge code every day, it helps to know how Git came to be. This video covers how Linus Torvalds created Git in just 10 days after a license dispute and explains how its data model actually works.

Linus Torvalds Built Git in Days Out of Spite
Built in 10 Days Out of Spite. Git and its data model explained in plain language.

If several users share one server, add –shared. It sets core.sharedRepository so group members can push.

The same manual notes that receive.denyNonFastForwards is turned on by default in shared repositories, so force pushes get rejected. Wrong group permissions are the usual reason a teammate’s push fails.

  1. On the server, create the repository with git init --bare --shared /srv/git/project.git
  2. In the local project, add it as a remote with git remote add origin user@git.example.com:/srv/git/project.git
  3. Push the branch with git push origin main (swap in your own branch name)
  4. Confirm it worked by cloning with git clone user@git.example.com:/srv/git/project.git

Anyone with SSH write access to the project.git directory also gets push access automatically (Pro Git, 2nd edition).

Bare clone vs mirror clone

YouTube player

The git-clone manual states that –mirror implies –bare, but the two copy different refs.

A bare clone copies branch heads straight to local branch heads. It creates no remote-tracking branches and no related configuration.

A mirror clone maps every ref, remote-tracking branches and notes included, and a later git remote update overwrites all of them to match the source.

For a collaboration remote, pick –bare. Use –mirror only for a copy that has to stay identical to its source, because the next update wipes out any push made to the mirror.

Converting between bare and non-bare

Both directions go through git clone.

Cloning a normal repository with –bare produces a bare copy. Pro Git notes that copying the .git folder by hand gets you a close result, apart from a couple of minor configuration differences.

Running the clone command without –bare on a bare repository gives you a normal working copy.

Why Git Rejects Pushes to a Non-Bare Repository

Git refuses a push that targets the branch checked out in a non-bare repository, because the update would leave the index and working tree out of sync with HEAD.

The rule is narrower than “no pushes”. The receive.denyCurrentBranch option, as described in the Git documentation, only blocks updates to the currently checked-out branch.

Git warned users before it enforced this. Commit acd2a45 (2009-02-11) made refuse the default.

ValuePush to checked-out branchConsequence
refuse (or true)RejectedDefault. Nothing changes.
ignore (or false)AcceptedWorking tree stays old, so git reset –hard is needed to match HEAD
warnAccepted with a warningSame drift as ignore
updateInsteadAcceptedWorking tree is updated, but a dirty tree blocks the push

updateInstead suits a production checkout where files must exist on disk. Even there, a hand-edited deployment folder rejects every push until it’s clean.

A git push into a bare repository has no checked-out branch to protect, so none of these settings matter. Atlassian’s tutorial puts it plainly. Central repositories should be bare because pushing branches into a non-bare one can overwrite changes.

For a shared remote, I’d take a bare repository over any of the four values.

Hosting a Bare Repository as a Remote

YouTube player

A bare repository becomes a remote once some transport can reach its directory. Git offers four of them: a local path, SSH, HTTP(S) and git://.

ProtocolAuthenticationPushTypical use
Local pathFilesystem permissionsYesShared mount, same host
SSHEncrypted, account requiredYesSmall teams, self-hosting
Smart HTTP(S)Web server loginYes, when authenticatedOne URL for read and write
git://NoneRareAnonymous read-only

On a plain SSH setup, filesystem permissions are the entire access model. Pro Git (2nd edition) suggests the shortcut of a single git account holding every developer’s public key in authorized\_keys.

Gitolite layers per-repository permissions on top of bare repositories reached over SSH. Gitea and GitLab bring accounts and a web interface to self-hosted setups.

Smart HTTP and dumb HTTP

Smart HTTP arrived in Git 1.6.6 (Pro Git, 2nd edition). Before it, HTTP was generally read-only and served the repository as static files.

The program behind it is git-http-backend, a CGI that serves a bare repository over http:// and https://. By default only upload-pack (fetch, clone) is on, while receive-pack (push) turns on for users the web server has authenticated, per the Git 2.49 manual (March 2025).

Setting http.receivepack to false blocks pushes. Setting it to true allows them for everyone, anonymous users included.

One more catch. A directory without a git-daemon-export-ok file is refused unless GIT\_HTTP\_EXPORT\_ALL is set.

The git daemon

Port 9418, no authentication, no encryption.

The daemon won’t serve a repository that lacks git-daemon-export-ok, but nothing else restricts it. Pro Git warns that anyone controlling the network path can alter a repository cloned over git://, so the protocol suits public read-only mirrors and little else.

Server-side hooks

Four hooks matter on the server side.

  • pre-receive runs once before any ref changes. A non-zero exit rejects the whole push and skips every later hook.
  • update runs once per ref, so it can reject a single ref and let the rest through.
  • post-receive fires after all refs are updated, and its exit code is ignored.
  • post-update handles repository-wide cleanup, like git update-server-info for dumb HTTP.

Incoming objects sit in a quarantine directory until pre-receive completes successfully (git-receive-pack manual, Git 2.50). A pre-receive hook must not point any ref at quarantined objects, and Git automatically rejects any ref update made from inside pre-receive.

Deploying with post-receive

Use post-receive for deployment, never update.

The Git manual itself steers notices away from the update hook, because a passing update hook doesn’t guarantee the ref actually moves. Teams that push to a bare repository to run an app deployment put the script here, usually a git checkout -f with –work-tree pointed at the web root.

Two things can bite you. A failing script doesn’t undo the push, since the hook’s exit code only produces an error message.

And read the new commit from the hook’s standard input, not from the ref. Another push can move the ref before your script runs.

Other Uses: Worktrees and Dotfiles

YouTube player

A bare repository can sit at the center of several checked-out branches, or hold the history of files that live somewhere else entirely.

Branch-per-directory with git worktree

The git-worktree manual (Git 2.56, September 2026) describes a repository as one main worktree, unless it is bare, plus any number of linked worktrees. Put a bare repository in the middle and every directory beside it becomes a linked worktree on its own branch.

  1. Clone bare into a hidden folder with git clone --bare repository-url .bare
  2. Point the project root at it with a .git text file containing gitdir: ./.bare
  3. Add a worktree per branch with git worktree add main main

A bare clone sets no fetch refspec, so add remote.origin.fetch as +refs/heads/*:refs/remotes/origin/* before the first fetch.

Moving things breaks links. Relocate the bare repository or a linked worktree by hand and the connection is lost, which git worktree repair reestablishes.

Config needs care too. Once extensions.worktreeConfig is on, the manual advises keeping core.bare=true out of the shared config.

Tracking dotfiles

The history sits in a bare repository, and the files stay in the home directory.

Create it with git init --bare $HOME/.dotfiles, then define an alias such as alias dotfiles='/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'. Every git command routed through the alias uses –git-dir and –work-tree to join the two halves.

No project folder ends up with a .git directory. Plain git outside the alias ignores the setup entirely.

Cloning the repository onto a new machine with –bare restores the history, and the same alias then checks the files out.

When a Bare Repository Is the Wrong Choice

A bare repository suits a shared remote. A few situations call for something else.

Solo work on one machine doesn’t need it. Nothing is shared, so a normal repository covers everything and a bare one only adds a clone step.

A deploy target that must hold files on disk is trickier. A post-receive checkout overwrites whatever sits in the folder, while a non-bare repository with updateInstead refuses to push over local changes. Pick updateInstead when manual edits on the target should stop a deploy, and the hook when the target is disposable.

Quick fixes in place are painful too. Without a working tree, every typo costs a clone, a commit and a push.

Then there are backups. Pro Git notes that keeping every repository instance on one computer makes catastrophic loss much more likely, so a bare repository on the same disk as the working copies protects nothing.

On the plus side, you get one shared source of truth for every branch, hosting that needs only an SSH account and a directory, and the same object database as any other repository.

The downsides are real, though. You can’t edit or commit in place, permissions, transport and backups all become the host’s job, and there’s no web interface, issue tracker or pull requests unless you add a service.

Pick a bare repository when more than one person pushes. Skip it when nobody else does.

Bare Repository FAQ

How do you check whether a repository is bare?

Run git rev-parse –is-bare-repository inside the directory. It prints true or false.

The config file gives the same answer, since a bare repository carries core.bare = true.

Why are my project files missing from a bare repository folder?

They never existed there. Git keeps content as compressed objects under the objects directory, so nothing in the folder opens as a normal project file.

Clone the repository to see the files.

Can you run git log or git branch in a bare repository?

YouTube player

Yes. History commands such as git log, git branch and git show need only objects and refs, so they work.

Commands that need a working tree, such as git status and git add, stop with an error.

Is it safe to run git init –bare in an existing repository?

Yes. Rerunning git init doesn’t overwrite existing contents, according to the git-init manual (Git 2.54, April 2026).

Pro Git relies on this to add the –shared option to a live bare repository without touching commits or refs.

Does cloning from a local path copy the files or fetch them?

A plain path makes Git use hardlinks or direct file copies, which is fast. A file:// URL runs the full network transfer instead and produces a cleaner copy (Pro Git, 2nd edition).

Either way, the clone is non-bare unless –bare is added.

How do you back up a bare repository?

Copy the whole directory to another machine, for example with scp -r, or run git clone –mirror against it so every ref comes along.

Keep the copy off the original disk. A second copy on the same drive won’t survive a disk failure.

What happens to existing clones if the bare repository moves?

Fetch and push fail, because each clone still points at the old URL. The commits are intact.

Running git remote set-url origin with the new location in every clone fixes it.

Keeping a Self-Hosted Bare Repository Running

Order matters here. Set group permissions through –shared before the first push, match the access method to each reader, and store a backup somewhere off the host.

Permissions come first because a wrong group blocks every push on day one, while a missing backup only shows itself after a loss.

Plain SSH with filesystem permissions stops fitting when some users need read-only access and others read-write, which Pro Git calls harder to arrange. That’s the point to bring in Gitolite or a hosted service.

New bare repositories also point HEAD at master, and the Git 2.54 manual (April 2026) moves the default to main only with Git 3.0. Set init.defaultBranch now.

Once the remote is stable, the team needs a shared Git workflow for branches and merges.

Bogdan Sandu

Stay sharp. Ship better code.

Every week: one curated article, one tool worth knowing, one tip you can use tomorrow. No noise, no padding.