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

Summarize this article with:
Every time you push code to GitHub, GitLab, or Bitbucket, it lands in a bare repository. Most developers interact with one daily without knowing it.
So what is a bare repository, and why does Git need two types of repos in the first place? The short version: it’s a Git repository with no working directory, built specifically to receive pushes and serve as a shared coordination point for teams.
This article breaks down how bare repositories work, how they differ from regular repos, and when you’d actually create one yourself. You’ll also learn how to set one up on a server, use Git hooks for deployment automation, and avoid the common errors that trip people up when pushing to the wrong repository type.
What is a Bare Repository

A bare repository is a Git repository that has no working directory. It stores only the version control data, the stuff normally hidden inside the .git folder, without any checked-out files you can edit.
Think of it this way. A regular repo has two things: the project files you work on and the Git metadata tracking changes. A bare repo strips away the first part entirely.
You create one with git init --bare. The naming convention typically appends .git to the directory name, like project.git. This signals to anyone poking around the file system that it’s not a regular working copy.
There’s no place to open files, write code, or run builds inside a bare repository. It exists purely to receive, store, and distribute commits. Every hosted Git platform, including GitHub, GitLab, and Bitbucket, stores repositories this way on their servers.
Version control adoption keeps climbing. The Stack Overflow blog reports that 93% of developers use Git. And the global version control systems market was valued at over $1 billion in 2024, according to Grand View Research, with distributed systems like Git holding a 51.4% market share.
Every one of those distributed workflows depends on bare repositories working quietly in the background. You just rarely see them because platforms like GitHub handle the setup for you.
How a Bare Repository Differs from a Regular Repository

The difference comes down to structure. A regular (non-bare) repository has a .git subdirectory sitting alongside your actual project files. A bare repository flattens that. The contents of .git become the top-level directory. No project files exist next to them.
This means you can’t cd into a bare repo and start editing source code. There’s nothing to edit. The HEAD, refs, objects, and config files all exist, but they’re exposed at the root level instead of tucked away.
Internal Directory Structure Comparison
| Component | Regular Repository | Bare Repository |
|---|---|---|
| Working tree | Present (editable files) | Absent |
.git directory | Subdirectory of project root | Contents are the project root |
HEAD file | Inside .git/ | At top level |
objects, refs, config | Inside .git/ | At top level |
| Supports commits locally | Yes | No |
| Accepts pushes | Problematic | Designed for it |
In a standard repo, you run git add, commit changes, create branches, and merge freely. A bare repo only receives and stores pushed data. That’s the whole point of removing the working tree.
The git config file in a bare repository will contain the line bare = true. In a regular repo, that value is false or simply absent. That single flag tells Git how to handle incoming operations.
Why Bare Repositories Exist
Git’s push mechanism has a problem. If you push commits to a repository that has a working directory, the checked-out files fall out of sync with what was just pushed. The index doesn’t match. The working tree becomes stale. And the developer sitting at that machine gets confused by phantom changes they never made.
Bare repositories solve this by removing the working tree entirely.
When there’s no working directory to conflict with, pushed commits land cleanly in the object database every time. No sync issues. No corrupted state. No angry coworker wondering why their files just changed.
GitHub alone hosts over 630 million repositories, according to CoinLaw’s 2025 data. The GitHub Octoverse 2025 report shows developers pushed nearly 1 billion commits that year, a 25% increase over the previous period. Every single one of those pushes landed in a bare repository on GitHub’s servers.
GitLab and Bitbucket work the same way. So does Gitea, Gogs, and any self-hosted Git server you spin up on a Linux box. The bare repository is the standard server-side format because it was designed specifically for this coordination role.
Took me a while to really understand why pushing to a non-bare repo causes problems. The explanation that finally clicked: imagine two people trying to edit the same document at the same desk, but one of them is invisible. That’s what happens when Git updates the object database while someone has files checked out.
When to Use a Bare Repository

Central team repository: Any shared repo that multiple developers clone from and push to should be bare. This is the most common use case by far.
Self-hosted Git servers: If you’re running your own Git server over SSH or on a local network, bare repositories are the correct format. Tools like Gitea store all web-created repos as bare repos internally.
CI/CD source repositories: Build pipelines that pull code from a central location expect bare repos on the receiving end. GitHub Actions consumed 11.5 billion minutes in public projects during 2024-2025, per the Octoverse report, up 35% year-over-year. That entire automation layer starts with pushes landing in bare repositories.
Mirror and backup repositories: When you need a full copy of a project’s Git history without the working files, a bare clone is the cleanest option.
But you would not use a bare repo for local development. If you need to write code, edit files, and make commits, you need a regular repository with a working tree. Single-developer projects where you’re the only one touching the codebase don’t need a bare repo either, unless you’re also setting up a separate remote to push to.
How to Create a Bare Repository

Two commands cover the most common scenarios.
From scratch:
git init --bare project.git
This creates a new empty bare repository. The --bare flag tells Git to skip the working tree and lay out the internal objects, refs, and config at the top level of the directory.
From an existing remote:
git clone --bare https://github.com/user/project.git
This pulls the full history, all branches, and all tags into a bare repository. It’s useful when you want a local copy of a remote repository for backup or mirroring purposes without needing a checkout.
For shared server setups, file permissions matter. If multiple users on the same Linux machine need to push, you’ll want to run:
git init --bare --shared project.git
The --shared flag sets group write permissions on the repository, so team members with the right Unix group can push without running into access errors.
Converting a Regular Repository to a Bare Repository
This comes up more than you’d expect. Someone starts a project as a normal repo, then later decides they want it to act as a central server.
The quick approach:
- Clone the existing repo as bare:
git clone --bare /path/to/regular-repo project.git - Use the new
project.gitdirectory as your remote
The manual approach involves copying the contents of the .git folder to a new location, updating the config to set bare = true, and removing any leftover working tree references. It works, but cloning is simpler and less error-prone.
One thing that trips people up: git remote configurations don’t carry over cleanly during conversion. You’ll want to verify your remotes after the switch, especially if you’re pushing to GitHub or another hosted platform alongside the bare repo.
How to Work with a Bare Repository

You don’t work inside a bare repository. You work against it.
The standard pattern is straightforward. Developers clone the bare repo to get a regular working copy on their machine. They write code, commit locally, and push changes back. Other developers fetch or pull those changes into their own working copies.
To point an existing local repo at a bare repo, you’d run something like:
git remote add origin user@server:/path/to/project.git
That’s it. From there, git push origin main sends your commits to the bare repo, and git pull origin main retrieves what others have pushed. SSH key authentication handles the access layer if you’re connecting to a remote server.
The Octoverse 2025 report notes that more than 180 million developers now build on GitHub. Monthly pull request merges averaged 43.2 million, a 23% increase year-over-year. All of that activity flows through bare repositories sitting on GitHub’s infrastructure.
If you need to inspect a bare repo directly (check logs, view branches, look at tags), you can pass the --bare flag or set the GIT_DIR environment variable to point at it. For example:
git --git-dir=/path/to/project.git log --oneline
You’ll see the full commit history without needing a working tree. It’s occasionally useful for server-side scripts, debugging, or quick checks during a code review.
Bare Repositories in Team and Server Workflows

The standard centralized Git workflow looks like this: a bare repository sits on a server, developers clone it, work locally, and push changes back. That’s the pattern behind almost every team using Git today.
GitHub holds 67.8% of the VCS platform market, according to Command Linux. GitLab and Bitbucket split most of the rest. All three store repositories as bare repos on their servers.
But not every team uses a hosted platform. Banking, financial services, and insurance account for roughly 30% of global VCS demand per the same source, and those environments run almost entirely on self-hosted Linux servers. Bare repositories on private infrastructure, accessed over SSH, are still the backbone of source control management in regulated industries.
Setting up a bare repo on a Linux server with SSH access is pretty minimal. Create a user, initialize the bare repo, add public keys for your developers, and you’re done. Gitea and Gogs wrap this exact pattern in a web interface, but under the hood, every repository they create is just a bare repo in a directory.
GitLab reported over 30,000 paying customers as of fiscal year 2024, with more than half of the Fortune 100 using the platform, per Electroiq. Many of those run self-managed installations where bare repositories sit on company-owned servers rather than in the cloud.
Using Git Hooks with a Bare Repository
Git hooks are scripts that run automatically when specific events happen in a repository. In a bare repo, server-side hooks are what matter.
post-receive: Fires after a push completes. The most common hook for triggering deployments, CI builds, or notifications.
pre-receive: Runs before accepting a push. Useful for enforcing branch protection rules or rejecting commits that don’t meet standards.
update: Similar to pre-receive, but runs once per branch being updated instead of once per push.
The Octoverse 2025 report shows developers used 11.5 billion GitHub Actions minutes in public projects. GitHub Actions are the modern equivalent of what post-receive hooks do on a self-hosted bare repo: detect a push, then run automated tasks.
For teams running their own Git servers, a post-receive hook on a bare repository can trigger a continuous deployment pipeline with just a few lines of shell script. DigitalOcean’s tutorials on Git hooks show this as one of the most practical uses for a bare repository outside of hosted platforms.
Common Errors and Misconceptions
The single most common error people hit with bare repositories (well, because of not using them) is the receive.denyCurrentBranch rejection.
You push to a non-bare repo. Git refuses. The error message looks like this:
remote: error: refusing to update checked out branch: refs/heads/master
This happens because Git won’t update the working directory of a checked-out branch via push. The index and working tree would go out of sync, leaving the repository in a confusing state. The fix is almost always to use a bare repository as the remote instead.
The receive.denyCurrentBranch config option controls this behavior. It accepts four values:
| Value | Behavior | Risk Level |
|---|---|---|
refuse (default) | Blocks the push entirely | Safe |
warn | Allows push, prints warning | Risky |
ignore | Allows push, says nothing | Dangerous |
updateInstead | Pushes and updates working tree if clean | Moderate |
Setting it to updateInstead is the safest non-default option if you really need to push to a non-bare repo (like a production environment where files must be checked out). But for most cases, just use a bare repo. That’s what it’s for.
Other common mistakes:
- Trying to
cdinto a bare repo and edit files. There are no files to edit. You need to clone it first. - Confusing a bare repo with a shallow clone. A shallow clone (
git clone --depth 1) has a working tree but limited history. Completely different concept. - Assuming git checkout works in a bare repo. It doesn’t, because there’s no working tree to check files out into.
Hutte research shows that nearly 90% of developers experience merge conflicts during collaborative work. Many of those conflicts trace back to misconfigured remotes or pushing to the wrong type of repository. Getting the bare vs. non-bare distinction right from the start avoids a whole category of these problems.
Bare Repository vs. Mirror Clone

These two get mixed up constantly, and at first glance it makes sense. Both create repositories without a working directory. Both are created with git clone flags. But what they copy and how they behave after cloning are different.
The Git documentation states it plainly: --mirror implies --bare, but goes further.
| Feature | git clone --bare | git clone --mirror |
|---|---|---|
| Working directory | No | No |
| Local branches | Copied | Copied |
| Tags | Copied | Copied |
| Remote-tracking branches | Not copied | Copied |
| Notes, other refs | Not copied | Copied |
| Remote configured for re-fetch | No | Yes |
A bare clone gives you branches and tags. That’s enough for setting up a new shared repository where developers will push and pull.
A mirror clone copies everything: all refs, all remote-tracking branches, all notes, all of it. And it sets up the remote so you can run git remote update and overwrite all local refs with whatever the source has. That makes it a true, sync-able copy.
When to use a bare clone:
- Setting up a new team repository on a self-hosted server
- Creating a push target for collaborative development
When to use a mirror clone:
- Full repository backup (you want every single ref preserved)
- Migrating a repository between hosting platforms
- Maintaining an exact replica that stays in sync over time
GitHub’s own documentation on cloning repositories uses this exact distinction. To duplicate a repo between GitHub accounts, they recommend git clone --bare followed by git push --mirror. For maintaining an ongoing mirror, they recommend git clone --mirror with periodic fetches.
One tricky detail worth flagging. If you push from a mirror to a remote, it overwrites all refs. Branches and tags that exist on the remote but not in the mirror will be deleted. That’s fine for a dedicated backup target. Terrible if someone accidentally pushes a mirror to a shared team repo.
The difference between Git and GitHub matters here too. Git provides both --bare and --mirror as low-level tools. GitHub, GitLab, and Bitbucket all build higher-level features (like forking) on top of these same bare repository structures.
For most software development teams, bare clones cover daily needs. Mirror clones are specialist tools. You’ll know when you need one, and the scenario usually involves the word “migration” or “disaster recovery.”
FAQ on What Is A Bare Repository
What is a bare repository in Git?
A bare repository is a Git repository without a working directory. It contains only version control data (objects, refs, HEAD, config) and is designed to act as a central remote that developers push to and pull from.
How do you create a bare repository?
Run git init --bare project.git to create one from scratch. You can also clone an existing repo as bare using git clone --bare. Both produce a repository with no checked-out files.
What is the difference between a bare and non-bare repository?
A non-bare repo has a working tree where you edit files and a hidden .git folder. A bare repo has no working tree. The Git metadata sits at the top level instead of inside a subdirectory.
Can you commit to a bare repository?
No. Since there’s no working directory, you can’t stage or commit files directly. Developers clone the bare repo, commit locally in their working copy, then push those commits back to it.
Why can’t I push to a non-bare repository?
Git blocks pushes to a checked-out branch in a non-bare repo because it would make the index and working tree inconsistent. The receive.denyCurrentBranch setting controls this. Using a bare repository as the remote avoids this entirely.
Where are bare repositories used?
Platforms like GitHub, GitLab, and Bitbucket store all repositories as bare repos on their servers. Self-hosted Git servers over SSH use them too. Any shared remote meant to receive pushes should be bare.
What is the difference between git clone –bare and git clone –mirror?
A bare clone copies branches and tags. A mirror clone copies all refs, including remote-tracking branches and notes, and configures the remote for easy re-syncing. Mirrors are best for backups and migrations.
Can you convert a regular repository to a bare repository?
Yes. The simplest method is git clone --bare /path/to/regular-repo. You can also manually move the .git contents to a new directory and set bare = true in the config file.
How do Git hooks work in a bare repository?
Server-side hooks like post-receive and pre-receive run automatically when pushes arrive. Teams use post-receive hooks in bare repos to trigger deployments, CI builds, or notification scripts on their servers.
Do I need a bare repository for local development?
No. Local development requires a working tree to edit files and make commits. Bare repositories are for shared remotes and servers. For writing code on your own machine, use a standard non-bare repository.
Conclusion
A bare repository is one of those Git concepts that stays invisible until something breaks. Understanding what it is, and when to use it, saves you from confusing push errors and misconfigured remotes down the line.
Whether you’re running a self-hosted Git server over SSH, setting up post-receive hooks for automated deployments, or just trying to figure out why your push got rejected, the bare vs. non-bare distinction matters.
For team collaboration, bare repos are the correct remote format. For local development, stick with a standard repository that gives you a working tree.
If you’re managing your own software configuration, take time to structure your repository setup properly from day one. Clone bare for shared servers. Clone mirror for backups. And keep your staging workflow clean by pushing only to repositories designed to accept it.
- Ai Pair Programming: The New Way To Write Code - April 12, 2026
- What Is Agentic Coding? The Next AI Dev Workflow - April 10, 2026
- From Setup To Monitoring: Why A DMARC Service Matters - April 10, 2026







