2684095e2f
Co-authored-by: claude <claude@jpaul.io> Co-committed-by: claude <claude@jpaul.io>
454 lines
26 KiB
Markdown
454 lines
26 KiB
Markdown
# Module 8 — Remotes and Hosting: GitHub, the Alternatives, and Owning Your Repo
|
||
|
||
> **One repo on one laptop is one spilled coffee away from gone.** A remote gets your history
|
||
> off your machine and somewhere durable — and because every clone carries the full history, a
|
||
> working team backs itself up just by working.
|
||
|
||
---
|
||
|
||
## Prerequisites
|
||
|
||
- **Module 2** — you have a Git repo (`tasks-app`) with real commits, and you understand commits as
|
||
checkpoints and the repo as durable memory. This module gets that history *off the one disk it
|
||
lives on*.
|
||
- **Module 5** — you committed your agentic tool's instructions file into the repo. A remote is what
|
||
finally makes that config *shared*: push it once and every teammate (and every agent) pulls the
|
||
same setup.
|
||
- **Module 6** — you can work on branches. Pushing is per-branch, so knowing what a branch is matters
|
||
here.
|
||
|
||
Helpful but not required: **Module 7** (worktrees). Everything below works the same whether you have
|
||
one working directory or several.
|
||
|
||
---
|
||
|
||
## Learning objectives
|
||
|
||
By the end of this module you can:
|
||
|
||
1. Explain what a remote *is* — a named pointer to another copy of the same repo — and why "it's just
|
||
another copy" is the whole reason hosting is provider-neutral.
|
||
2. Add a remote, push your history to it, and pull changes back, on any forge, with the same commands.
|
||
3. Recover from the three failure modes that bite everyone on first push: authentication, a
|
||
non-empty remote, and a branch-name mismatch.
|
||
4. Choose a host deliberately — hosted vs. self-hosted — using a current, dated comparison instead of
|
||
defaulting to GitHub by reflex.
|
||
5. State precisely where "pushing to a remote" is and isn't a backup, and how a normal team workflow
|
||
accidentally satisfies most of the 3-2-1 rule.
|
||
|
||
---
|
||
|
||
## Key concepts
|
||
|
||
### A remote is just another copy
|
||
|
||
Strip the branding away and a **remote** is one thing: a named reference to *another copy of this
|
||
same repository*, usually somewhere you can reach over the network. That's it. `origin` is not a
|
||
GitHub concept, a GitLab concept, or a Gitea concept — it's a Git concept, and the copy it points at
|
||
is a full, equal Git repo that happens to live on a server.
|
||
|
||
This is the fact the entire rest of the module rests on, so sit with it: **because a remote is just
|
||
another copy, the commands you use to talk to it are identical no matter who hosts it.** `git push`
|
||
to GitHub is byte-for-byte the same operation as `git push` to a forge you run yourself in a
|
||
locked-down rack. The provider is a logistics decision — uptime, price, who can see it, where the
|
||
servers sit — not a Git decision. We lean on GitHub as the worked example below *only* because it's
|
||
the one you're most likely to hit first, not because the mechanics change anywhere else.
|
||
|
||
The local-to-remote vocabulary is small:
|
||
|
||
```bash
|
||
git remote add origin <URL> # register a remote named "origin" at this URL (once per repo)
|
||
git remote -v # list remotes and their URLs
|
||
git push -u origin main # send your "main" branch up; -u links local main to origin/main
|
||
git push # after the first -u push, this is all you need
|
||
git pull # fetch the remote's changes AND merge them into your branch
|
||
git fetch # fetch the remote's changes WITHOUT merging (look before you leap)
|
||
git clone <URL> # make a brand-new local copy from a remote (history and all)
|
||
```
|
||
|
||
`origin` is just the conventional name for "the place I push to." You can have more than one remote
|
||
(a personal fork *and* the team's repo, say), and they can live on different hosts entirely — one on
|
||
a SaaS forge, one on a box in your closet. Git doesn't care.
|
||
|
||
### Getting a remote: you create the empty repo first
|
||
|
||
The one piece the commands above assume is that a remote repo *exists* to push into. On every host
|
||
the shape is the same:
|
||
|
||
1. In the host's web UI (or its CLI/API), create a **new, empty** repository. Give it a name; do
|
||
**not** let it add a README, license, or `.gitignore` — you want it empty so your local history
|
||
is the first thing in it.
|
||
2. Copy the URL it gives you. You'll see two flavours:
|
||
- **HTTPS** — `https://host/you/tasks-app.git`. Authenticates with a username + a personal access
|
||
token (not your account password — password auth over Git is gone on essentially every modern
|
||
host).
|
||
- **SSH** — `git@host:you/tasks-app.git`. Authenticates with an SSH key you've added to your
|
||
account. More setup once, less friction forever.
|
||
3. Point your local repo at it and push:
|
||
|
||
```bash
|
||
cd ~/workflow-course/tasks-app
|
||
git remote add origin <URL-you-copied>
|
||
git push -u origin main
|
||
```
|
||
|
||
That `-u` (short for `--set-upstream`) is worth understanding, not just copying: it records that your
|
||
local `main` *tracks* `origin/main`. After it, `git status` will tell you things like "your branch is
|
||
ahead of origin/main by 2 commits" — the ahead/behind report you met in Module 2, now meaningful
|
||
because there's finally a remote to be ahead *of*. And `git push` / `git pull` with no arguments know
|
||
where to go.
|
||
|
||
### The three failure modes of a first push
|
||
|
||
Everyone hits at least one of these. Recognizing them by their error text saves an afternoon.
|
||
|
||
**1. Authentication fails.** You push and get `Authentication failed` or `Permission denied
|
||
(publickey)`. The cause is almost always that you tried to use an account password (dead) or haven't
|
||
set up a token / SSH key. Fix: for HTTPS, generate a personal access token in the host's settings and
|
||
use it as your password when prompted; for SSH, generate a key (`ssh-keygen`) and paste the public
|
||
half into the host's SSH-keys settings. This is host-specific UI but the *concept* is identical
|
||
everywhere.
|
||
|
||
**2. The remote isn't empty (non-fast-forward).** You let the host create the repo *with* a README,
|
||
then push, and get `! [rejected] ... (fetch first)` or `non-fast-forward`. The remote has a commit
|
||
your local history doesn't, so Git refuses to overwrite it. Fix: either recreate the remote empty, or
|
||
reconcile once with `git pull --rebase origin main` and then push. (This is the same "someone else
|
||
pushed before me" situation you'll hit constantly once you're collaborating — Module 11 — except here
|
||
the "someone else" was the host's auto-generated README.)
|
||
|
||
**3. Branch-name mismatch.** Your local default branch is `master` but the host expects `main` (or
|
||
vice versa). `git push -u origin main` then errors with `src refspec main does not match any`. Fix:
|
||
check what you actually have with `git branch`, and either push the branch you have
|
||
(`git push -u origin master`) or rename it first (`git branch -m main`). Worth settling early so it
|
||
doesn't confuse you for the rest of the course.
|
||
|
||
### Pull, fetch, and the everyday loop
|
||
|
||
Once the remote exists, day-to-day work adds two moves to the Module 2 loop:
|
||
|
||
- **`git pull`** before you start, to get whatever the remote gained since you last looked. It's a
|
||
`fetch` (download) plus a merge into your current branch in one step.
|
||
- **`git push`** after you've committed, to send your new checkpoints up.
|
||
|
||
When you want to *see* what the remote has before you let it touch your working files, use
|
||
**`git fetch`** instead — it downloads the remote's commits into `origin/main` but leaves your branch
|
||
untouched, so you can `git log main..origin/main` to read exactly what's incoming before merging.
|
||
That "look before you leap" habit matters more the moment other contributors — human or agent — are
|
||
pushing to the same place.
|
||
|
||
### Choosing a host: the comparison
|
||
|
||
GitHub is the titan. It is by a wide margin the largest forge, it's where most open source lives, and
|
||
it's the one AI tooling integrates with *first* — when a new coding agent or MCP server ships, GitHub
|
||
support is usually in the first release and everything else trails. That makes it the sane default for
|
||
most people, and it's why this module uses it as the worked example. But "default" is not "only," and
|
||
for a team with on-prem, air-gapped, or data-control requirements — a real and common constraint for
|
||
this audience — it may be the wrong default. The genuine choice is between **hosted** (someone runs
|
||
the forge; you just use it) and **self-hosted** (you run the forge on your own infrastructure).
|
||
|
||
> ### Hosting comparison — as of 2026-06-22
|
||
>
|
||
> Pricing and feature claims drift fast. Everything in these two tables was checked on the date above
|
||
> and must be re-verified before you rely on it — see the **Verify-before-publish** checklist at the
|
||
> end. List prices are per-user/month at the entry paid tier, billed annually, in USD; promotional
|
||
> and volume discounts are common and not shown.
|
||
|
||
**Hosted forges (someone else runs it):**
|
||
|
||
| Platform | Pricing (entry → paid) | Built-in CI/CD | AI-tooling integration | Ease of operation |
|
||
|---|---|---|---|---|
|
||
| **GitHub** | Free; Team ~$4/user; Enterprise ~$21/user | GitHub Actions, built in (Free tier includes a monthly minutes allowance for private repos; unlimited for public) | **Deepest.** Most agents, MCP servers, and AI reviewers target GitHub first | Zero ops — pure SaaS |
|
||
| **GitLab** (SaaS) | Free (capped users/namespace, small CI allowance); Premium ~$29/user; Ultimate ~$99/user | GitLab CI/CD — among the most mature, deeply integrated pipelines | Strong; first-party AI assistant plus growing agent support | Zero ops as SaaS; also self-hostable (see below) |
|
||
| **Bitbucket** (Atlassian) | Free (≤5 users); Standard ~$3/user; Premium ~$6/user | Pipelines, built in (small free monthly build-minute allowance) | Growing; tightest value is deep Jira/Atlassian tie-in | Zero ops as SaaS; Data Center edition self-hostable (enterprise pricing) |
|
||
| **Azure DevOps** | First 5 users free; Basic ~$6/user beyond; pipelines ~$40/parallel job after a free job | Azure Pipelines, built in (one free parallel job + monthly minutes) | Good within the Microsoft ecosystem; Copilot integration | Zero ops as SaaS; Azure DevOps Server self-hostable |
|
||
| **Codeberg** | Free (FOSS projects only; soft repo/storage caps) | Forgejo Actions (it runs Forgejo) | Via API/MCP; not a first-tier agent target | Zero ops; nonprofit-run, no commercial/closed-source hosting |
|
||
| **SourceHut** | Paid to host: ~$5 / $10 / $15 tiers (reduced ~$2); free to *contribute* | builds.sr.ht, built in | Minimal first-class AI tooling; reachable via API | Zero ops as SaaS; fully self-hostable (it's open source) |
|
||
|
||
**Self-hostable open-source forges (you run it):**
|
||
|
||
| Forge | License / cost | Built-in CI/CD | AI-tooling integration | Ease of operation |
|
||
|---|---|---|---|---|
|
||
| **Forgejo** | Free, open source (you pay infra + ops) | Forgejo Actions — runs GitHub-Actions-compatible workflow YAML | Full REST API; community MCP servers; agents work over git + API | **Easiest.** Single Go binary, runs on a tiny VPS (~256 MB RAM). Community/nonprofit governed |
|
||
| **Gitea** | Free, open source | Gitea Actions (GitHub-Actions-compatible YAML) | Full REST API; community MCP servers | Single Go binary, same light footprint as Forgejo; company-backed |
|
||
| **GitLab CE** | Free, open source | Full GitLab CI/CD + container registry + more, in one install | Same first-party AI direction as GitLab SaaS, self-hosted | **Heaviest.** Wants ~8 GB+ RAM (Postgres/Redis/Sidekiq/Gitaly); upgrades can't skip versions |
|
||
| **Gogs** | Free, open source | None built in | API only | Lightest of all; single binary, runs on a Raspberry Pi. Slower development; no CI |
|
||
| **OneDev** | Free, open source | Built-in CI/CD configured in the **UI** (little/no YAML) + Kanban + packages | API; less common as an agent target | Single deployment; all-in-one but a smaller ecosystem |
|
||
|
||
Two things to read out of those tables rather than memorize the numbers:
|
||
|
||
- **GitLab spans both camps.** It's a hosted SaaS *and* a self-hostable Community Edition from the
|
||
same project — useful if you want SaaS now and the *option* to bring it in-house later without
|
||
changing tools.
|
||
- **"Self-hosted" trades a per-user bill for an ops bill.** The license is free; your cost is the
|
||
server, the upgrades, the backups, and the on-call. Forgejo/Gitea make that bill small (a single
|
||
binary on a cheap box). GitLab CE makes it real (a stack to feed and water). That trade is the
|
||
whole decision.
|
||
|
||
### The self-hosted-forge track (optional)
|
||
|
||
If you're in the air-gapped/on-prem audience, you can run this module's lab against a forge you stand
|
||
up yourself instead of a SaaS account. The teaching point is precisely that **nothing changes** — you
|
||
create an empty repo on your forge, copy its URL, `git remote add origin <URL>`, and `git push`. The
|
||
lab below flags exactly where the only difference is (the URL and how you authenticate to your own
|
||
box). Standing the forge up is its own exercise — Forgejo or Gitea is a single binary and the fastest
|
||
path; the *git* half is identical to the hosted track.
|
||
|
||
### Backup thesis, part one: distribution is the backup
|
||
|
||
Module 2 left you with a sharp limitation: everything lived on one disk. Drop the laptop in a lake and
|
||
the repo, history and all, is gone. A single local repo gives you *recovery* (move between
|
||
checkpoints) but not *backup* (a copy that survives the disk dying).
|
||
|
||
Pushing to a remote is what closes that gap, and Git's design makes the win bigger than it looks.
|
||
Recall the standard **3-2-1 backup rule**: keep **3** copies of your data, on **2** different media,
|
||
with **1** offsite. Now look at what a normal team doing normal work ends up with, without anyone
|
||
"doing backups":
|
||
|
||
- Your laptop has a full copy — **complete history**, not just current files.
|
||
- The remote has a full copy — **offsite**, on someone else's hardware (or your other box).
|
||
- Every teammate who has cloned the repo has *another* full copy, each with the entire history,
|
||
because **clone copies everything**, not a snapshot.
|
||
|
||
A four-person team that pushes to one remote is sitting on five-plus complete, independent copies of
|
||
the entire project history across multiple locations and machines. They didn't run a backup tool.
|
||
They just worked. That's the quiet superpower of a *distributed* version control system: distribution
|
||
*is* the redundancy. The 3-2-1 rule, which most ops shops fight to satisfy deliberately, falls out of
|
||
a forge and a working team almost for free.
|
||
|
||
Be precise about the division of labor, because the course is honest about where analogies stop:
|
||
|
||
- **Recovery power comes from commits (Module 2, and Module 12 for the harder cases).** That's your
|
||
point-in-time restore — go back to any checkpoint.
|
||
- **Backup power comes from remotes and distribution (this module).** That's your offsite,
|
||
redundant, survives-the-disk copy.
|
||
|
||
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. Module 12 picks up the
|
||
*recovery* half in full and is just as honest about what Git is **not** a backup for — your database,
|
||
your secrets, your uncommitted work, your large binaries. We'll hold that thought there.
|
||
|
||
---
|
||
|
||
## The AI angle
|
||
|
||
A remote isn't only about durability — it's the substrate the AI parts of this course run on.
|
||
|
||
- **Most AI tooling integrates with the forge first, not your laptop.** AI reviewers, issue-to-PR
|
||
agents, and the CI that catches code which merely *looks* right (Modules 10, 14, and Unit 5) all
|
||
operate on the *remote* repo through its API and web UI. Until your history is pushed, none of that
|
||
machinery has anything to act on. A remote is the precondition for every agent-in-the-loop module
|
||
that follows.
|
||
- **GitHub's "integrates first" status is a real, current bias — name it, then decide.** Because the
|
||
largest forge is where AI tooling lands first, picking a less-common host or self-hosting can mean
|
||
thinner first-class agent support and more wiring-it-yourself over the API. That's a legitimate cost
|
||
to weigh against control and data-residency — *not* a reason to abandon the choice. The git
|
||
mechanics are identical everywhere; it's the AI ecosystem maturity that varies, and that gap is the
|
||
thing to check (it narrows constantly).
|
||
- **The committed AI config from Module 5 only pays off once it's pushed.** Locally, your agent's
|
||
instructions file just configures *your* agent. Pushed to the remote, it configures *everyone's* —
|
||
every teammate who clones, and every automated agent that later operates 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."
|
||
- **A remote is an agent's recovery insurance.** When you hand an agent a branch and let it run
|
||
(Module 6, and Unit 5 at full autonomy), a pushed branch means its work survives a crashed session,
|
||
a wiped worktree, or a machine that dies mid-run. Push early; an agent's output that only exists in
|
||
one uncommitted, unpushed working directory is the most fragile state in this whole course.
|
||
|
||
---
|
||
|
||
## Hands-on lab
|
||
|
||
**Lab language:** shell (Git commands), plus one short provided shell script. Runs on macOS, Linux,
|
||
WSL, or Git Bash on Windows. Continues the `tasks-app` repo from Module 2.
|
||
|
||
**You'll need:**
|
||
|
||
- Your `tasks-app` Git repo from Module 2 (with several commits and a `.gitignore`).
|
||
- An account on a Git host. **Hosted track:** GitHub is the worked default, but GitLab, Bitbucket,
|
||
Codeberg, or any forge works with the identical commands. **Self-hosted track:** a Forgejo/Gitea
|
||
(or other) instance you can reach, and an account on it.
|
||
- The ability to authenticate to that host — a personal access token (for HTTPS) or an SSH key added
|
||
to your account. Set this up first; failure mode #1 above is the most common first-push wall.
|
||
- Your AI assistant (still the way you've used it — this lab is about the remote, not the editor).
|
||
|
||
### Part A — Create the empty remote and push
|
||
|
||
1. On your host's web UI, create a **new, empty** repository named `tasks-app`. Do **not** add a
|
||
README, license, or `.gitignore` — leave it empty so your local history goes in clean. Copy the URL
|
||
it shows you (HTTPS or SSH).
|
||
|
||
> **Self-hosted track:** identical step, on your own forge's UI. The only thing that differs from
|
||
> the hosted track is the URL (your forge's hostname) and how you authenticate to your box.
|
||
> Everything from here on is the same commands.
|
||
|
||
2. Point your repo at the remote and push:
|
||
|
||
```bash
|
||
cd ~/workflow-course/tasks-app
|
||
git remote -v # probably empty — no remote yet
|
||
git remote add origin <URL> # paste the URL you copied
|
||
git remote -v # now origin shows, for fetch and push
|
||
git push -u origin main # send main up and link it
|
||
```
|
||
|
||
If `push` errors, match it to the three failure modes above: `Authentication failed` / `Permission
|
||
denied` → token or SSH key (#1); `non-fast-forward` / `fetch first` → the remote wasn't empty (#2);
|
||
`src refspec main does not match` → branch-name mismatch, check `git branch` (#3). Fix and re-push.
|
||
|
||
3. Confirm the offsite copy exists: refresh the host's web page for the repo. Your files and your full
|
||
commit history from Module 2 are now sitting on hardware that is not your laptop. **That is the
|
||
backup half the course promised.**
|
||
|
||
### Part B — Prove distribution is redundancy
|
||
|
||
You're going to demonstrate the 3-2-1 claim with your own eyes: that a clone is a *complete,
|
||
independent* copy, history and all — not a snapshot.
|
||
|
||
4. Make a change locally, commit it, and push it (with the AI if you like — e.g. ask for a `version`
|
||
command that prints the app version):
|
||
|
||
```bash
|
||
# apply the change, then:
|
||
git add .
|
||
git commit -m "Add version command"
|
||
git push # no args needed now, thanks to -u earlier
|
||
```
|
||
|
||
5. Now clone the remote into a *separate* directory, as if you were a teammate on a fresh machine:
|
||
|
||
```bash
|
||
cd ~/workflow-course
|
||
git clone <URL> tasks-app-teammate
|
||
cd tasks-app-teammate
|
||
git log --oneline # the ENTIRE history is here — every commit, not just the latest
|
||
```
|
||
|
||
Compare the commit count to your original repo (`git log --oneline | wc -l` in each). They match.
|
||
The clone didn't get "the current files" — it got the whole project's memory. That's the property
|
||
that makes a working team into an accidental backup system.
|
||
|
||
6. Run the provided check from this module's `lab/` to make the point mechanically:
|
||
|
||
```bash
|
||
# from your original repo:
|
||
bash ~/workflow-course/tasks-app/verify-backup.sh # (copied from lab/verify-backup.sh)
|
||
```
|
||
|
||
The script confirms (a) you have a remote configured, (b) your local branch is fully pushed
|
||
(nothing stranded only on your disk), and (c) a fresh clone of the remote carries the exact same
|
||
commit count as your local repo — i.e. the offsite copy is complete, not partial. Read its output;
|
||
the green line is your evidence that the backup is real.
|
||
|
||
### Part C — The everyday loop
|
||
|
||
7. Edit the README in your *teammate* clone, commit, and push from there:
|
||
|
||
```bash
|
||
cd ~/workflow-course/tasks-app-teammate
|
||
# edit README.md, then:
|
||
git add . && git commit -m "Note the remote in the README"
|
||
git push
|
||
```
|
||
|
||
8. Back in your *original* repo, pull it down:
|
||
|
||
```bash
|
||
cd ~/workflow-course/tasks-app
|
||
git fetch # download the new commit, but don't merge yet
|
||
git log main..origin/main # SEE exactly what's incoming before you take it
|
||
git pull # now merge it into your local main
|
||
git log --oneline # the teammate's commit is now here too
|
||
```
|
||
|
||
That fetch-then-look-then-pull rhythm is the habit to keep: you saw what was coming before you let
|
||
it touch your files. You've now pushed *and* pulled across two independent copies through one
|
||
remote — the complete remotes mechanic.
|
||
|
||
### Part D (optional) — A second remote
|
||
|
||
9. Add a *second* remote (a personal fork on another host, or even a bare repo on a USB drive or a
|
||
box on your LAN) and push to it too:
|
||
|
||
```bash
|
||
git remote add backup <SECOND-URL>
|
||
git push backup main
|
||
git remote -v # two remotes now: origin and backup
|
||
```
|
||
|
||
You now literally have the 3-2-1 rule satisfied by hand: your laptop, `origin`, and `backup` — three
|
||
copies, more than one location. Nothing about Git stopped you from pointing at as many copies as you
|
||
want.
|
||
|
||
---
|
||
|
||
## Where it breaks
|
||
|
||
The honest limits — the backup analogy especially needs them.
|
||
|
||
- **A remote backs up what you *pushed*, nothing else.** Uncommitted edits, untracked files, and
|
||
anything `.gitignore` excludes (like `tasks.json` runtime state) never leave your laptop. "I pushed"
|
||
is not "everything is safe" — it's "every *committed and pushed* change is safe." The defense is the
|
||
Module 2 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 — Module 17), large binaries, and build artifacts are not covered by pushing code. The
|
||
3-2-1-by-accident win applies to your *versioned source*, full stop. Module 12 is blunt about this.
|
||
- **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 is suspended, locked, or the provider has an outage, your offsite copy is temporarily out of
|
||
reach (your local clones are fine). Part D's second remote, or a periodic clone to storage you
|
||
control, is the answer for anyone who needs it — and it's the on-ramp to the self-hosting argument.
|
||
- **"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 decide your host.
|
||
- **The comparison tables are a snapshot, not a fact of nature.** Every price and tier above was true
|
||
on 2026-06-22 and will drift. Use them to learn the *dimensions* that matter (per-user cost vs. ops
|
||
cost, built-in CI or not, footprint, AI-ecosystem maturity), then check current numbers yourself.
|
||
|
||
---
|
||
|
||
## Check for understanding
|
||
|
||
**You're done when:**
|
||
|
||
- Your `tasks-app` exists on a remote, and `git remote -v` plus the host's web UI both confirm it.
|
||
- You have pushed at least one commit and pulled at least one commit back, across two copies of the
|
||
repo through one remote.
|
||
- `verify-backup.sh` reports a clean, fully-pushed state and a clone whose commit count matches your
|
||
local repo's — you've *seen* that the offsite copy is complete.
|
||
- 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 does *not* cover.
|
||
- You can state why the choice of host is a logistics decision, not a Git one, and name at least one
|
||
hosted alternative to GitHub and one self-hostable forge.
|
||
|
||
When pushing feels like the natural end of "commit" and you trust that your history is no longer
|
||
trapped on one disk, you have the *backup* half of the backup-and-recovery thread. Module 9 starts
|
||
using the remote for more than storage — issues, the task layer where humans and agents pick up
|
||
work — and Module 12 returns to finish the *recovery* half.
|
||
|
||
---
|
||
|
||
## Verify-before-publish
|
||
|
||
This module makes dated pricing and feature claims that drift. Re-check each before relying on the
|
||
tables, and update the "as of" date when you do.
|
||
|
||
- [ ] **GitHub** tiers and prices — Free / Team / Enterprise per-user/month, and the Free-tier CI
|
||
minutes allowance for private repos.
|
||
- [ ] **GitLab** tiers — Free (user/namespace caps, CI allowance), Premium, Ultimate per-user/month,
|
||
and the SaaS-vs-self-managed price split.
|
||
- [ ] **Bitbucket** tiers — Free user cap, Standard, Premium per-user/month, and free build-minute
|
||
allowance. (Sources disagreed between ~$2–3 and ~$5–6 at build time — re-confirm the exact
|
||
figures.)
|
||
- [ ] **Azure DevOps** — free-user count, Basic per-user/month, and the per-parallel-job pipeline
|
||
price plus free job/minutes.
|
||
- [ ] **Codeberg** — that it remains FOSS-only and free, and its current soft repo/storage caps.
|
||
- [ ] **SourceHut** — paid-to-host tiers ($5/$10/$15 and reduced rate were *proposed for 2026*;
|
||
confirm they're in effect and current).
|
||
- [ ] **Self-hosted forges** — that Forgejo/Gitea still ship GitHub-Actions-compatible CI, GitLab CE's
|
||
current minimum resource footprint, and whether OneDev/Gogs CI status has changed.
|
||
- [ ] **"GitHub integrates first" / AI-ecosystem maturity** — re-assess which forges are first-tier
|
||
agent and MCP targets; this gap narrows fast.
|
||
- [ ] **Self-host/hosted spans** — confirm GitLab still offers CE self-host, and Bitbucket/Azure DevOps
|
||
still offer their self-hostable editions, before describing either as spanning both camps.
|
||
- [ ] Update the comparison's **"as of" date** to the build date.
|