- Replace all 24 [COURSE LINK] placeholders across the 17 posts with the course URL https://git.jpaul.io/justin/ai-workflow-course. - Align the learner working-dir path in the posts to ~/ai-workflow-course (matches the modules after the repo rename). - blog/README: mark the course-link checklist item done; flag publish-time refinements (GitHub-mirror swap; repoint inline cross-post links to real jpaul.me post URLs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT
14 KiB
Your Repo Lives on One Disk. That's One Spilled Coffee From Gone.
I run my own Git forge. Not GitHub — an actual server I keep at git.jpaul.io, behind my own Cloudflare, with my own runners and my own container registry on the LAN. Most of my projects live there first and only get pushed out to GitHub when I deliberately want them public.
I'm telling you that up front not to flex, but because this post is the one where I'm most in my own wheelhouse, and I want you to know the punchline before I prove it: it does not matter where you push. GitHub, GitLab, a box in my closet — the commands are identical, and the reason they're identical is the whole lesson.
This post opens Unit 2 of The Workflow — the team layer. Up to now the course has been about getting you and your AI working safely on one machine: version control as undo, the AI editing real files, your config committed as a durable artifact. All of that lives on one disk. This module gets it off that disk. If you've been following along, this is the moment the safety net stops being local.
A remote is just another copy
Strip the branding away and a remote is one thing: a named pointer to another copy of this same repository, usually somewhere you can reach over the network. That's the entire concept.
Here's the part people miss because the marketing buries it. origin — the name you'll see everywhere — is not a GitHub thing. It's not a GitLab thing or a Gitea thing. It's a Git thing, and the copy it points at is a full, equal Git repo that just happens to live on a server. Which means git push to GitHub is byte-for-byte the same operation as git push to the forge I run myself in a locked-down rack. The provider is a logistics decision — uptime, price, who can see it, where the servers physically sit — not a Git decision.
That's why I keep saying it doesn't matter where you push. The vocabulary is small, and it's the same everywhere:
git remote add origin <URL> # register a remote named "origin" (once per repo)
git remote -v # list remotes and their URLs
git push -u origin main # send "main" up; -u links local main to origin/main
git push # after that first -u push, this is all you need
git pull # fetch the remote's changes AND merge them in
git fetch # fetch WITHOUT merging (look before you leap)
git clone <URL> # make a brand-new local copy, full history and all
origin is just the conventional name for "the place I push to." You can have more than one — a personal fork and the team's repo, one on a SaaS forge and one on a box on your LAN. Git genuinely does not care.
Getting a remote (and the three walls you'll hit first)
The one thing those commands assume is that a remote repo exists to push into. On every host the shape is identical: in the web UI, create a new, empty repository — do not let it add a README, license, or .gitignore, because you want your local history to be the first thing that lands in it. Copy the URL it hands you (HTTPS or SSH), then:
cd ~/ai-workflow-course/tasks-app
git remote add origin <URL-you-copied>
git push -u origin main
That -u is worth understanding rather than just copying — it records that your local main tracks origin/main, so afterward git status can tell you "your branch is ahead of origin/main by 2 commits," and bare git push/git pull know where to go.
[insert a screenshot referencing a host's "create new repository" page with the README/license/gitignore checkboxes left unchecked here]
Now, the first push is where everybody trips. I've watched sharp people lose an afternoon to one of these three, so let me just name them by their error text:
- Authentication fails —
Authentication failedorPermission denied (publickey). You almost certainly tried an account password (dead on every modern host) or haven't set up a token / SSH key yet. Fix: generate a personal access token and use it as your password for HTTPS, orssh-keygenand paste the public half into the host's settings for SSH. Host-specific UI, identical concept everywhere. - The remote isn't empty —
! [rejected] ... (fetch first)ornon-fast-forward. You let the host create the repo with a README, so it has a commit your history doesn't, and Git refuses to clobber it. Fix: recreate it empty, or reconcile once withgit pull --rebase origin mainand push. - Branch-name mismatch —
src refspec main does not match any. Your local default ismasterbut you're pushingmain. Fix: check withgit branch, then push what you actually have or rename it (git branch -m main).
Recognizing these by sight is the actual skill. The fix is always thirty seconds; the staring-at-it is the hour.
The everyday loop
Once the remote exists, day-to-day work adds exactly two moves to the loop you already know: git pull before you start (grab whatever the remote gained), git push after you commit (send your checkpoints up).
And one habit I want you to keep: when you want to see what's incoming before it touches your files, use git fetch instead of pull. It downloads the remote's commits into origin/main but leaves your branch alone, so you can read exactly what's coming:
git fetch # download, don't merge
git log main..origin/main # SEE what's incoming
git pull # now take it
That "look before you leap" rhythm matters more the second other contributors — human or agent — are pushing to the same place.
Choosing a host: GitHub is the default, not the only
GitHub is the titan, and I'm not going to pretend otherwise. It's the largest forge by a wide margin, it's where most open source lives, and — this is the part that matters for this course — it's where AI tooling integrates first. New coding agent ships? GitHub support is usually in the first release; everyone else trails. That makes it the sane default, which is why the course uses it as the worked example.
But "default" isn't "only," and if you're in this audience, you know exactly why. On-prem requirements. Air-gapped networks. Data-residency rules that make "someone else's hardware" a non-starter. The genuine choice is hosted (someone runs the forge, you just use it) versus self-hosted (you run it). On the hosted side you've got GitLab, Bitbucket, Azure DevOps, Codeberg, SourceHut. On the self-hosted side, the open-source forges: Forgejo and Gitea (a single Go binary that'll run happily on a 256 MB VPS — this is what I run), GitLab CE (heavy; wants 8 GB+ RAM and a whole stack to feed), Gogs, OneDev.
Two things to take away rather than memorize a price sheet that'll be stale by the time you read it:
- GitLab spans both camps — hosted SaaS and a self-hostable Community Edition from the same project. Handy if you want SaaS now and the option to bring it in-house later without changing tools.
- Self-hosting trades a per-user bill for an ops bill. The license is free; your cost is the server, the upgrades, the backups, the on-call. Forgejo/Gitea make that bill tiny. GitLab CE makes it real. That trade is the decision.
I'll say from experience: running my own forge is genuinely not the burden people assume. Gitea is one binary. It's been less maintenance than half the SaaS subscriptions I've juggled. But it is an ops commitment, and I'd be lying if I told you the backups and upgrades maintain themselves — they don't, and that's the honest cost.
The backup thesis, part one: distribution is the backup
Here's the reframe I most want you to walk away with.
A single local repo gives you recovery — you can move between checkpoints, undo the AI's mess, time-travel through your own history. What it does not give you is backup. Drop the laptop in a lake and the repo, history and all, is gone. Recovery and backup are different powers, and one local repo only has the first one.
Pushing to a remote closes that gap — and Git's design makes the win bigger than it looks. Recall the standard 3-2-1 rule: keep 3 copies of your data, on 2 different media, with 1 offsite. Now watch what a normal team ends up with without anyone running a backup tool:
- Your laptop has a full copy — complete history, not just current files.
- The remote has a full copy — offsite, on different hardware.
- Every teammate who's cloned the repo has another full copy, each with the entire history, because
clonecopies everything, not a snapshot.
A four-person team pushing to one remote is sitting on five-plus complete, independent copies of the whole project history, across multiple machines and locations. They didn't do backups. They just worked. That's the quiet superpower of a distributed version control system: distribution is the redundancy. The thing most ops shops fight to satisfy deliberately falls out of a forge and a working team almost for free.
You can watch it happen with your own eyes in the lab. Push your tasks-app, then clone it into a separate directory as if you were a teammate on a fresh machine, and count the commits in each:
cd ~/ai-workflow-course
git clone <URL> tasks-app-teammate
cd tasks-app-teammate
git log --oneline | wc -l # compare to your original repo — they match
The clone didn't get "the current files." It got the whole project's memory. That's the property that turns a working team into an accidental backup system.
[insert a screenshot referencing two terminal panes side by side, each running git log --oneline | wc -l and showing the same commit count here]
But be precise about who does what, because the course is honest about where analogies stop:
- Recovery power comes from commits. Point-in-time restore. Go back to any checkpoint.
- Backup power comes from remotes and distribution. Offsite, redundant, survives-the-dead-drive copies.
You need both. Commits without a remote survive a mistake but not a dead drive. A remote without good commits survives a dead drive but gives you a junk drawer to restore from.
The AI angle
A remote isn't only about durability — it's the substrate the AI half of this course runs on.
Most AI tooling operates on the remote, not your laptop. AI reviewers, issue-to-PR agents, the CI that catches code which merely looks right — all of it acts on the pushed repo through its API and web UI. Until your history is up there, none of that machinery has anything to grab onto. A remote is the precondition for every agent-in-the-loop module that follows.
And the AI config you committed earlier in the course? Locally it just configures your agent. Pushed, it configures everyone's — every teammate who clones, and every automated agent that later runs on the repo, inherits the same conventions instead of each drifting into a private setup. The remote is what turns "my AI config" into "the project's AI config."
One more, and it's the one I care most about: a remote is an agent's recovery insurance. When you hand an agent a branch and let it run, a pushed branch means its work survives a crashed session, a wiped worktree, or a machine that dies mid-run. An agent's output that exists only in one uncommitted, unpushed working directory is the single most fragile state in this whole course. Push early.
Where it breaks (because I like to be honest)
The backup analogy especially needs its caveats, so here they are:
- A remote backs up what you pushed — nothing else. Uncommitted edits, untracked files, and anything
.gitignoreexcludes never leave your laptop. "I pushed" means "every committed-and-pushed change is safe," not "everything is safe." The defense is the habit: commit often, and now push often too. - Git is not a backup for non-Git things. Your database, your secrets (which shouldn't be in the repo anyway), large binaries, build artifacts — pushing code does not cover any of them. The 3-2-1-by-accident win applies to your versioned source, full stop.
- One remote is one vendor. Distribution across a team is great redundancy against disk failure; it's weaker against account failure. If your whole team only ever pushes to one host and that account gets suspended or the provider has an outage, your offsite copy is temporarily out of reach (your local clones are fine). A second remote — a fork on another host, a bare repo on a USB drive, a box on your LAN — is the answer for anyone who needs it. This, by the way, is the on-ramp to the whole self-hosting argument, and it's a big part of why I run my own forge in the first place.
- "GitHub integrates first" is true today and a moving target. Don't treat the AI-ecosystem gap between hosts as permanent — it's exactly the kind of claim that ages. Re-check it for your tooling before you let it pick your host.
You're done when
Your tasks-app exists on a remote — git remote -v and the host's web page both confirm it. You've pushed at least one commit and pulled one back across two copies of the repo. And you can explain, in your own words, why a four-person team pushing to one remote roughly satisfies 3-2-1 without running a backup tool — and name two things that win doesn't cover.
When pushing feels like the natural end of "commit," and you trust that your history is no longer trapped on one disk, you've got the backup half of the backup-and-recovery thread. The course comes back later to finish the recovery half — and it's just as blunt about what Git is not a backup for.
Next up in the series: now that the repo lives somewhere shared, we start using the remote for more than storage — the issue layer, where humans and agents pick up work.
Running your own forge, or thinking about it? Tell me what's holding you back in the comments — I read them, and the on-prem/air-gapped war stories are exactly the ones I want to hear.