When you think about remote Git repositories, it's usually something
like GitHub or GitLab. But sometimes you just need a simple local remote, a private
backup on your machine, a shared repo across computers on your local
network, or a central copy that works completely offline.
Git already gives you everything you need with git init --bare. No extra services, no setup overhead.
#
What is a bare repository?
A bare repository is a Git repository that contains only the
internal Git data, commits, branches, tags, and configuration, with
no working directory. There are no project files to check out; it
exists purely as a storage location for the repository history.
#
Creating a bare repository
Creating a bare repository is straightforward and only takes a
single command:
git init --bare my-project.git
If you look inside the directory, you'll find the raw Git internals:
HEAD, config,
objects/, refs/, and a
hooks/ directory, which we'll use later.
Tip
It's common practice to name bare repositories with a .git suffix:
project.git. This makes it immediately
clear that the folder is a repository storage location, not a
working project.
#
Connecting a local project
There are two ways to connect to the bare repository depending on
whether you have an existing project or are starting fresh.
If you already have a local project, add the bare repo as a remote
and push:
git remote add origin ~/my-project.git
git push -u origin main
If you're starting fresh, clone it directly:
git clone ~/my-project.git
The path can be absolute (/home/user/repos/my-project.git) or use ~ for your home directory. On a shared
machine or NAS, use the actual filesystem path to the bare repo.
Some other useful commands for working with remotes
git remote -v # verify the remote is configured
git remote show origin # inspect the remote in detail
git fetch origin # fetch latest without merging
git log origin/main..HEAD # see commits not yet pushed
#
Protecting the main branch with hooks
One feature people often miss when using a plain Git server instead
of platforms like GitHub or GitLab is branch protection. Luckily Git
already supports this through hooks.
Since a bare repository acts as the central remote, we can install a
pre-receive hook that runs whenever someone pushes
to the repository. The hook can inspect the pushed references and reject
the push if it targets a protected branch like
main or master.
Inside the bare repository create the hook:
cd my-project.git/hooks
touch pre-receive
chmod +x pre-receive
Then add the following script to block direct pushes to protected
branches:
#!/bin/sh
while read oldrev newrev refname
do
if [ "$refname" = "refs/heads/main" ] || [ "$refname" = "refs/heads/master" ]; then
echo "Direct pushes to main/master are not allowed."
exit 1
fi
done
exit 0
The pre-receive hook runs once for every push
and receives the updated references through standard input. Each line
contains the old commit hash, the new commit hash, and the reference
name. By checking the reference name we can selectively reject pushes
to protected branches while allowing everything.
#
Conclusion
With just a few lines of shell script you get a simple form of
branch protection similar to what hosted Git platforms provide. This
setup works well for personal backups, small teams on a shared
server, or any situation where you want a central Git remote without
the overhead of running a full platform. The hooks/ directory gives you room to grow, you can add post-receive hooks for notifications or automated deployments down the line.