<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Laravel Git Blog - Self-hosting</title><description>Blog posts tagged Self-hosting: notes on Git, code review and shipping Laravel projects.</description><link>https://laravelgit.com/</link><language>en-gb</language><atom:link href="https://laravelgit.com/blog/tag/self-hosting.xml" rel="self" type="application/rss+xml"/><item><title>Backing Up Your Git Server So It Actually Restores</title><link>https://laravelgit.com/blog/backing-up-your-git-server/</link><guid isPermaLink="true">https://laravelgit.com/blog/backing-up-your-git-server/</guid><description>Every clone is a partial backup - but hooks, config and server state live outside the repo. Mirror, bundle, copy off-site, and rehearse the restore.</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Self-hosting your Git means the server is the canonical copy. Developers&amp;#39; clones soften the blow of losing it - every clone has the history - but a clone is not a backup of the server: hooks, server config, access rules and anything outside the refs live nowhere else. A backup plan has to cover both, and it only counts if a restore has been rehearsed.&lt;/p&gt;
&lt;h2&gt;Mirror the repositories&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone --mirror git@yourserver:app.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A mirror clone copies every ref - branches, tags, notes - and none of the working-tree state. To refresh it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git -C app.git remote update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two lines in a nightly cron and every repository on the server has a current, complete copy somewhere else. On a small server, loop over the bare repos directory and mirror each one.&lt;/p&gt;
&lt;h2&gt;Bundle for portability&lt;/h2&gt;
&lt;p&gt;For a single-file, copy-anywhere backup of one repo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git bundle create app-2026-10-21.bundle --all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A bundle is a real Git pack: &lt;code&gt;git clone app-2026-10-21.bundle app&lt;/code&gt; reconstructs the repository on any machine with Git and nothing else. Bundles are the right shape for weekly off-site copies - one dated file per repo, uploaded to object storage or another box.&lt;/p&gt;
&lt;h2&gt;Copy what Git does not track&lt;/h2&gt;
&lt;p&gt;The repositories are the obvious half. The other half lives next to them: server hooks (your deploy and protection scripts), the Git host&amp;#39;s own configuration, user and access data. A plain &lt;code&gt;rsync -a&lt;/code&gt; of those directories into the same off-site destination covers it. Losing the repos is painful; losing the hooks and config is what turns a restore into a rebuild.&lt;/p&gt;
&lt;h2&gt;Rehearse the restore&lt;/h2&gt;
&lt;p&gt;A backup you have never restored is a hope, not a plan. Once a month, pick one bundle or mirror and restore it to scratch space: clone from it, run the test suite, check the latest commits are there. Ten minutes, and the night the server actually dies becomes an inconvenience instead of an incident.&lt;/p&gt;
&lt;p&gt;The honest summary: mirror nightly, bundle weekly off-site, rsync the config, restore monthly. Four small habits, and the machine your whole team depends on stops being a single point of failure.&lt;/p&gt;
</content:encoded><category>Self-hosting</category><category>Tooling</category></item><item><title>Monitoring Your Self-Hosted Git Server: The Four Things That Matter</title><link>https://laravelgit.com/blog/monitoring-your-self-hosted-git-server/</link><guid isPermaLink="true">https://laravelgit.com/blog/monitoring-your-self-hosted-git-server/</guid><description>A Git server fails quietly: disks fill, backups rot, SSH dies at 2am. The four checks that catch every realistic failure, and how to keep them boring.</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hosted Git comes with someone else&amp;#39;s pager. Self-hosted means the pager is yours - which is fine, as long as it only rings for real problems. Four checks cover every failure mode a small team&amp;#39;s Git server actually has, and none of them requires a monitoring platform you will spend a weekend configuring.&lt;/p&gt;
&lt;h2&gt;1. Is it answering?&lt;/h2&gt;
&lt;p&gt;An external uptime check hitting the web interface every minute, from outside your network. This catches the server being down, the reverse proxy dying, and the certificate expiring. Any ping service does this for free or close to it. Alert on two consecutive failures, not one - single blips are network weather.&lt;/p&gt;
&lt;h2&gt;2. Can someone actually clone?&lt;/h2&gt;
&lt;p&gt;The web UI can be green while SSH is dead, and your team notices at 9am Monday. A synthetic check that runs &lt;code&gt;git ls-remote&lt;/code&gt; against a small repo every few minutes exercises the path that matters - SSH auth, Git daemon, disk read. A cron job on another machine with a dedicated read-only key (the &lt;a href=&quot;/blog/deploy-keys-and-machine-users-for-ci/&quot;&gt;deploy keys guide&lt;/a&gt; covers scoping it) is thirty lines of setup.&lt;/p&gt;
&lt;h2&gt;3. Is the disk filling up?&lt;/h2&gt;
&lt;p&gt;Repos only grow, and a full disk corrupts things in the order you least want. Alert at 80%, not 95% - the gap between &amp;quot;we should prune something this week&amp;quot; and &amp;quot;pushing is failing right now.&amp;quot; Watch the backup destination too; it fills up quieter.&lt;/p&gt;
&lt;h2&gt;4. Did the backup actually run - and restore?&lt;/h2&gt;
&lt;p&gt;The backup cron succeeding means a file was written, not that it restores. &lt;a href=&quot;/blog/backing-up-your-git-server/&quot;&gt;Backing up your Git server&lt;/a&gt; covers the backup half; the monitoring half is two alerts: one when the backup job misses its window, and a monthly drill where you restore to a scratch machine and clone from it. The drill is the only check on this list that cannot be automated away, and the only one that proves the rest mean anything.&lt;/p&gt;
&lt;p&gt;Four checks, one drill, and the pager stays silent for months at a time - which is exactly what &amp;quot;the server is fine&amp;quot; should feel like.&lt;/p&gt;
</content:encoded><category>Self-hosting</category></item><item><title>Moving Your Repositories to a New Git Server Without Losing Anything</title><link>https://laravelgit.com/blog/moving-your-repositories-to-a-new-git-server/</link><guid isPermaLink="true">https://laravelgit.com/blog/moving-your-repositories-to-a-new-git-server/</guid><description>Git makes migration nearly lossless - every clone carries the full history. The mirror-push recipe, what does not travel with the repo, and the order to do things in.</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Moving hosts feels risky because the repo is the crown jewels. The good news: Git is a distributed system, and every clone already contains the complete history. Migrating is mostly a matter of pushing that history somewhere new - plus remembering the things that are not in the repo.&lt;/p&gt;
&lt;h2&gt;The mirror push&lt;/h2&gt;
&lt;p&gt;The recipe for a lossless move is three commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone --mirror git@old-host:your-org/your-app.git
cd your-app.git
git push --mirror git@new-host:your-org/your-app.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--mirror&lt;/code&gt; on the clone fetches every branch, every tag, every note - not just the default branch checkout. &lt;code&gt;--mirror&lt;/code&gt; on the push replicates all of it, exactly, onto the new server. When the push finishes, the new repo is a bit-for-bit copy of the old one&amp;#39;s refs. History, tags, branches: done.&lt;/p&gt;
&lt;h2&gt;What does not travel&lt;/h2&gt;
&lt;p&gt;The repository is only part of what makes a project work. Plan separately for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pull requests and issues.&lt;/strong&gt; These live in the host&amp;#39;s database, not in Git. GitHub&amp;#39;s API can export them; some teams archive them to a document and start fresh, some use migration tooling. Decide consciously - do not discover the gap after the cutover.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI secrets and variables.&lt;/strong&gt; Re-enter them on the new side. This is also a natural moment to apply the scoping rules from &lt;a href=&quot;/blog/deploy-keys-and-machine-users-for-ci/&quot;&gt;deploy keys and machine users&lt;/a&gt; instead of copying an old personal key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Webhooks and integrations.&lt;/strong&gt; Deploy hooks, Slack notifications, status checks - make a list before you start, and tick each one off on the new server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Branch protection rules.&lt;/strong&gt; Recreate them; &lt;a href=&quot;/blog/branch-protection-rules-that-stick/&quot;&gt;branch protection rules that stick&lt;/a&gt; is the checklist.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The order that avoids pain&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Set up the new server and recreate protections&lt;/strong&gt; while the old one still serves everyone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mirror-push every repo&lt;/strong&gt;, and spot-check: clone from the new host, run the test suite, compare &lt;code&gt;git log&lt;/code&gt; on a few branches.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Move one non-critical repo first&lt;/strong&gt; as a dress rehearsal for the team&amp;#39;s workflow changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Announce a short freeze&lt;/strong&gt; on the old host, do a final mirror push to catch the last commits, then update everyone&amp;#39;s remotes: &lt;code&gt;git remote set-url origin git@new-host:your-org/your-app.git&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep the old host read-only for a month.&lt;/strong&gt; Deleting it on day one is how you find out what you forgot.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you are moving to self-hosted, the prep work is the same - plus the operational habits in &lt;a href=&quot;/blog/backing-up-your-git-server/&quot;&gt;backing up your Git server&lt;/a&gt; from day one, because the safety net is now yours to run.&lt;/p&gt;
&lt;p&gt;The history is the easy part; Git was built for this. The checklist above is the difference between a quiet migration and a week of &amp;quot;did anyone move the webhook?&amp;quot;&lt;/p&gt;
</content:encoded><category>Self-hosting</category></item><item><title>Private Composer Packages Straight From Your Own Git Server</title><link>https://laravelgit.com/blog/private-composer-packages-from-your-own-git/</link><guid isPermaLink="true">https://laravelgit.com/blog/private-composer-packages-from-your-own-git/</guid><description>You do not need Satis or Packagist for internal packages. Point Composer at a Git repo, tag properly, and your self-hosted server becomes the package registry.</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every growing Laravel team eventually extracts a package: the shared billing client, the internal logging helper, the theme. Then comes the assumption that serving it needs infrastructure - Satis, Private Packagist, a registry. For most teams it does not: Composer reads plain Git repositories directly, and if you already self-host your Git, the registry is running.&lt;/p&gt;
&lt;h2&gt;The VCS repository&lt;/h2&gt;
&lt;p&gt;In the consuming app, tell Composer where the package lives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;repositories&amp;quot;: [
    { &amp;quot;type&amp;quot;: &amp;quot;vcs&amp;quot;, &amp;quot;url&amp;quot;: &amp;quot;git@git.yourcompany.com:shared/billing-client.git&amp;quot; }
],
&amp;quot;require&amp;quot;: {
    &amp;quot;yourcompany/billing-client&amp;quot;: &amp;quot;^1.2&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The package repo itself just needs a &lt;code&gt;composer.json&lt;/code&gt; with its name and autoload config - no extra hosting. On install, Composer clones the repo over SSH and checks out the requested version. That is the whole registry.&lt;/p&gt;
&lt;h2&gt;Versions come from tags&lt;/h2&gt;
&lt;p&gt;Composer reads versions from Git tags, which makes disciplined tagging the whole game: &lt;a href=&quot;/blog/tagging-releases-properly/&quot;&gt;tagging releases properly&lt;/a&gt; covers annotated semver tags, and the contract here is simple - tag &lt;code&gt;1.2.0&lt;/code&gt; in the package repo and &lt;code&gt;^1.2&lt;/code&gt; resolves to it. No tag, no release; a branch name works for dev versions (&lt;code&gt;dev-main&lt;/code&gt;) but treat that as a development convenience, never a production dependency.&lt;/p&gt;
&lt;h2&gt;Access is the only real setup&lt;/h2&gt;
&lt;p&gt;CI and every developer need read access to the package repo. This is where teams reach for a shared key in a wiki - do not. The patterns in &lt;a href=&quot;/blog/deploy-keys-and-machine-users-for-ci/&quot;&gt;deploy keys and machine users&lt;/a&gt; apply exactly: a read-only deploy key per consumer, or a machine user with access to the package repos. Developers authenticate with their own SSH keys, which they already have.&lt;/p&gt;
&lt;h2&gt;When you outgrow it&lt;/h2&gt;
&lt;p&gt;The direct-VCS approach slows down at scale: Composer clones every package repo on every install, and metadata lookups multiply. The honest thresholds are dozens of packages or very slow CI installs, and the upgrade path is Satis (static, self-hosted, stays on-brand) generating metadata over the same repos. Until then, VCS entries plus tags cover the need with zero new infrastructure.&lt;/p&gt;
&lt;p&gt;Your Git server already hosts the code. Let it host the packages too.&lt;/p&gt;
</content:encoded><category>Self-hosting</category></item><item><title>Server-Side Git Hooks Worth Running on Your Own Server</title><link>https://laravelgit.com/blog/server-side-git-hooks-worth-running/</link><guid isPermaLink="true">https://laravelgit.com/blog/server-side-git-hooks-worth-running/</guid><description>Client hooks are a favor developers do themselves; server hooks are the rule. Three pre-receive checks that earn their place on a self-hosted Git server, and the restraint that keeps them tolerable.</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Local hooks (the kind in &lt;a href=&quot;/blog/git-hooks-for-laravel-projects/&quot;&gt;git hooks for Laravel projects&lt;/a&gt;) run on the developer&amp;#39;s machine, which means they run only when the developer kept them installed. Server-side hooks run on every push, for everyone, no exceptions. When you self-host, they are one of the best reasons to - and one of the easiest ways to annoy your team if you overreach.&lt;/p&gt;
&lt;h2&gt;Three checks that earn their place&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Block the secrets.&lt;/strong&gt; A pre-receive hook scanning pushed commits for private keys, &lt;code&gt;.env&lt;/code&gt; contents and common token shapes turns &lt;a href=&quot;/blog/a-secret-got-committed-now-what/&quot;&gt;a secret got committed&lt;/a&gt; from an incident into a rejected push. Gitleaks or a few good regexes; the point is that the secret never lands in the repo&amp;#39;s history at all, which is when cleanup is free.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Protect the default branch.&lt;/strong&gt; Even with &lt;a href=&quot;/blog/branch-protection-rules-that-stick/&quot;&gt;branch protection rules&lt;/a&gt; configured in the UI, a pre-receive or update hook rejecting direct pushes to main is the belt to the platform&amp;#39;s suspenders - it still fires if someone widens a permission by mistake.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Refuse giant blobs.&lt;/strong&gt; A 200MB binary committed once lives in every clone forever. Rejecting pushes with files over a sane limit (100MB is GitHub&amp;#39;s line; yours can be lower) pushes large assets to &lt;a href=&quot;/blog/git-lfs-when-a-laravel-repo-needs-it/&quot;&gt;Git LFS&lt;/a&gt; before they become permanent.&lt;/p&gt;
&lt;h2&gt;The restraint that keeps them tolerable&lt;/h2&gt;
&lt;p&gt;A rejected push interrupts someone mid-flow, so every hook owes them three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A fast answer.&lt;/strong&gt; A hook that scans ten thousand commits on every push will be bypassed, not loved. Check the pushed range, not all of history.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A message that says how to fix it.&lt;/strong&gt; &amp;quot;Push rejected: possible secret in src/config/legacy.php (line 41). Remove it, rewrite the commit, see &lt;link to your secret-removal doc&gt;.&amp;quot; A bare &amp;quot;rejected&amp;quot; teaches people to fear the server; a clear one teaches them the rule.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A real escape hatch.&lt;/strong&gt; False positives happen - a test fixture that looks like a key, a legitimate big file. Someone on the team should be able to bypass with a reason, and that bypass should be logged, not forbidden.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Start with the secret scanner; it has the best ratio of one-time setup to incidents prevented. Add rules the way you add tests: when the failure actually happens, not in anticipation of every imaginable one.&lt;/p&gt;
</content:encoded><category>Self-hosting</category></item><item><title>Deploying a Laravel App With Git Push</title><link>https://laravelgit.com/blog/deploy-laravel-with-git-push/</link><guid isPermaLink="true">https://laravelgit.com/blog/deploy-laravel-with-git-push/</guid><description>A bare repo and a post-receive hook are enough for a honest small-team deploy: checkout, composer, migrate, cache.</description><pubDate>Mon, 19 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Self-hosting your git and your app on the same box has a nice property: a push can be the deploy. You need a bare repository and one hook.&lt;/p&gt;
&lt;h2&gt;The bare repo&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git init --bare /srv/git/myapp.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add it as a remote on your laptop and push a branch you deploy from, for example &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;The post-receive hook&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/srv/git/myapp.git/hooks/post-receive&lt;/code&gt;, made executable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
set -e
APP=/var/www/myapp
git --work-tree=$APP --git-dir=/srv/git/myapp.git checkout -f main
cd $APP
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;checkout -f&lt;/code&gt; syncs the working tree to the pushed commit. The artisan steps are the boring, repeatable part of every Laravel release.&lt;/p&gt;
&lt;h2&gt;Keep it honest&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Run the hook as a user that owns &lt;code&gt;/var/www/myapp&lt;/code&gt; but cannot sudo.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;migrate --force&lt;/code&gt; runs unattended, so only push migrations you have tested against a copy of production data.&lt;/li&gt;
&lt;li&gt;If the hook fails, the push still lands in the repo; redeploy by pushing an empty commit or rerunning the hook on the server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is one step above &amp;quot;SSH in and pull&amp;quot;. When you outgrow it — zero-downtime releases, rollbacks, multiple servers — that is the point where a real pipeline earns its keep, not before.&lt;/p&gt;
</content:encoded><category>Releases</category><category>Self-hosting</category></item><item><title>Why Teams Self-Host Their Git</title><link>https://laravelgit.com/blog/why-self-host-your-git/</link><guid isPermaLink="true">https://laravelgit.com/blog/why-self-host-your-git/</guid><description>Control, access and where your code lives.</description><pubDate>Thu, 15 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hosted Git is convenient. Self-hosting is about control.&lt;/p&gt;
&lt;h2&gt;Know where the code lives&lt;/h2&gt;
&lt;p&gt;Repositories sit on servers you run. You decide on backups, retention and who gets in.&lt;/p&gt;
&lt;h2&gt;Set your own access rules&lt;/h2&gt;
&lt;p&gt;Give people the access their role needs and remove it when they leave.&lt;/p&gt;
&lt;h2&gt;Keep it simple&lt;/h2&gt;
&lt;p&gt;Self-hosting only pays off if it stays easy to run. Pick tools you can update without a project.&lt;/p&gt;
</content:encoded><category>Self-hosting</category></item></channel></rss>