SSD Nodes Learn 🎉 VPS from $4.99/mo
Guides Matt ConnorBy Matt Connor

What is GitHub? Git vs GitHub for VPS owners

Git is the version control program on your machine. GitHub is one hosted service built around it. What the difference means when you own a VPS.

What is GitHub?

GitHub is a hosted service that stores Git repositories and builds a website around them. Git is the version control program that runs on your own computer or your own server. GitHub is one company's product on top of Git, owned by Microsoft since 2018. You can use Git every day and never open GitHub. You cannot use GitHub without Git.

That line matters the moment you own a VPS (virtual private server). Git is what records the history of your config files and deploy scripts. GitHub is where a copy of that history lives when the server does not, plus a place to run builds and reviews. This guide follows one example from an empty folder to a deploy on a server, and defines each new word where you first meet it.

What Git does on its own

Git is a version control system: it records the state of a directory over time, so you can see what changed, when, and why. It was written in 2005 for Linux kernel work. It is distributed, which means every copy of a repository holds the entire history. There is no central server in the design. A colleague's laptop is as complete a copy as any server is.

Install it and set your identity. Git refuses to record a commit without a name and an email address, because both are written into the commit itself.

sudo apt update && sudo apt install -y git
git --version
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

On Ubuntu 24.04, git --version prints git version 2.43.0. Any release from the last few years behaves the same way for everything below.

The example: a repository for your VPS deploy files

A repository, usually shortened to "repo", is a directory that Git is watching. It becomes one when you run git init, which creates a hidden .git folder inside it. That folder is the repository. Delete .git and you are left with an ordinary directory holding no history.

mkdir vps-deploy && cd vps-deploy
git init -b main
printf '.env\n*.key\n' > .gitignore

-b main names the first branch main. Leave it out and Git prints a long hint about the default branch name instead. .gitignore lists paths Git must never track. Write your secrets file into it on day one, because a file that has been committed once stays in the history after you delete it, and removing it properly means rewriting every commit that came after.

Commits: the unit of history

Now add a script and record it.

printf '#!/bin/sh\nsudo systemctl restart caddy\n' > restart.sh
git add restart.sh .gitignore
git commit -m "Add restart script and gitignore"
git log --oneline

git add moves a change into the staging area, which is the list of what will go into the next commit. git commit writes that list into history as one entry. A commit holds a snapshot of every tracked file, a message, an author, a timestamp, and a pointer to the commit before it. git log --oneline prints one line per commit, each beginning with a short hash such as a1b2c3d. That hash is the commit's name, and nearly every Git command accepts it.

Skip the git add step and git commit answers no changes added to commit (use "git add" and/or "git commit -a"). Nothing is broken. Git is telling you the staging area is empty, so there is nothing to snapshot. git status is the command to run whenever you are lost: it names the current branch, the staged changes, and the files Git can see but is not tracking.

Branches: a second line of history

A branch is a moving pointer to a commit. main is a branch, and it is not special to Git in any way. Creating one costs nothing, because Git writes a new pointer rather than copying your files.

git switch -c add-backup
printf '#!/bin/sh\nrestic backup /srv\n' > backup.sh
git add backup.sh
git commit -m "Add nightly backup"
git switch main
ls

After git switch main, backup.sh is missing from the listing. Nothing was deleted. The file exists on the add-backup branch, and main never had it, so Git removed it from your working directory when you moved. This surprises everybody once. git switch add-backup brings it back.

Remotes: where GitHub finally appears

Everything so far ran on one machine with no network at all. A remote is a named URL for another copy of the same repository. GitHub hosts one of those copies for you. The conventional name for the main remote is origin.

Create an empty repository through the GitHub website, then connect to it. Prefer SSH over HTTPS here: an SSH key is a file you control, and it does not expire the way a personal access token does.

ssh-keygen -t ed25519 -C "vps-deploy"
cat ~/.ssh/id_ed25519.pub
ssh -T git@github.com

Paste the printed public key into the SSH keys page of your GitHub account, then run the test again. A working key answers Hi yourname! You've successfully authenticated, but GitHub does not provide shell access. GitHub gives you no shell, so that refusal is the success case. git@github.com: Permission denied (publickey). means your key was never offered or was not accepted, so check that you pasted the .pub file and not the private key next to it.

git remote add origin git@github.com:yourname/vps-deploy.git
git push -u origin main

git push sends your commits to the remote. -u records that local main tracks remote main, so later a bare git push is enough. git clone <url> is the reverse on a new machine: it copies the whole repository with its history and sets origin for you. An HTTPS remote works too, and it travels over the same protocol as any web page, which helps on networks that block outbound port 22. If that sentence needs unpacking, what an HTTP request is actually made of covers the mechanics.

Pull requests, issues and forks: the parts that are GitHub, not Git

Everything above is Git, and it works against any server. The three words below are GitHub features. Other hosts copy them, and Git itself knows nothing about them.

A pull request (PR) is a request to merge one branch into another, wrapped in a page for discussion. You push add-backup, open a PR against main, and the site shows the difference commit by commit. People comment on single lines. Automated checks report pass or fail against the branch. Click merge and GitHub performs the merge on its own copy, then updates main. The name comes from the original workflow, where you asked a maintainer to pull your branch into theirs.

An issue is a numbered thread for a bug or a task. It lives in GitHub's database, not in your repository, which is worth knowing before you pick a host: clone the repo and you have every commit, but not one issue. Getting issues out means calling the API.

A fork is your own server-side copy of somebody else's repository. You have write access to the copy, you push a branch to it, and you open a pull request from your copy back to theirs. That is how you contribute to a project whose maintainers have never heard of you. A fork is a clone that lives on GitHub and remembers where it came from.

Software reads all three through the same API a person uses. A pull request review agent you run on your own server watches for new PRs, reads the diff, and posts line comments. Conventions such as an AGENTS.md file at the root of a repository exist because a repo is now read by tools as well as by people.

What GitHub actually does for a VPS owner

Start with off-server storage. Your deploy scripts and playbooks belong somewhere that is not the server they configure. Rebuild the VPS from a fresh image, clone, run. Keep that repository private and give the server a deploy key: an SSH key registered against one repository instead of your whole account, set to read only. A leaked read-only deploy key exposes one repo. A leaked account key exposes everything you can push to.

sudo git clone git@github.com:yourname/vps-deploy.git /srv/vps-deploy
cd /srv/vps-deploy
git pull --ff-only

--ff-only refuses to create a merge commit. On a server that only consumes changes, a merge is always an accident, so this flag turns a confusing history into the plain error fatal: Not possible to fast-forward, aborting. Something changed on the server that should not have. Find it before you pull again.

Clone as root and then run Git as another user and you get fatal: detected dubious ownership in repository at '/srv/vps-deploy'. Git refuses to read a repository owned by a different user, because a hostile .git/config can make Git run commands. Fix the ownership with chown rather than adding a safe.directory exception, since the exception silences the check without removing the cause.

GitHub Actions: build and deploy pipelines

Actions is GitHub's CI/CD system (continuous integration and continuous delivery). Commit a YAML file under .github/workflows/ and GitHub runs it when the event you named happens.

name: check
on:
  push:
    branches: [main]
jobs:
  shellcheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - run: sudo apt-get update && sudo apt-get install -y shellcheck
      - run: shellcheck *.sh

The file is a workflow. A job runs on one machine. A step is one command or one published action. uses: pulls in an action from another repository, and @v7 pins its major version (v7 is current for actions/checkout as of August 2026). Always pin something, because an unpinned action means code you have not read runs with access to your secrets.

runs-on: ubuntu-latest asks GitHub for a fresh virtual machine, thrown away when the job ends. Standard runners are free on public repositories, and the free plan includes 2,000 minutes per month for private repositories as of August 2026. Check the current pricing page before you build a budget on that figure.

Secrets are stored in repository settings and read as ${{ secrets.DEPLOY_KEY }}. A workflow triggered by a pull request from a fork gets a read-only token and no access to those secrets, because otherwise a stranger could open a PR whose only job is to print them.

Running the Actions runner on your own VPS

runs-on: self-hosted sends the job to a machine you own instead. The repository's runner settings page hands you a download line, the repository web address, and a registration token that is valid for one hour. Put those last two into REPO_URL and RUNNER_TOKEN, then the setup is three commands.

./config.sh --url "$REPO_URL" --token "$RUNNER_TOKEN"
sudo ./svc.sh install
sudo ./svc.sh start
./svc.sh status

svc.sh status should report the service as active and show recent log lines. The runner opens an outbound HTTPS connection to GitHub and asks for work, so you do not open any inbound port for it. svc.sh install writes the systemd unit, and it is the step people skip: without it the runner exits with your SSH session and every later job sits queued with no explanation. the full self-hosted runner setup on a VPS walks through the hardening and the cleanup that a long-lived runner needs.

The payoff is that a deploy no longer needs an inbound SSH key reachable from the internet, because the job is already running on the box. The build cache also stays warm between runs, and no minute meter is counting.

One warning is not optional. GitHub's own documentation recommends self-hosted runners only for private repositories, because forks of a public repository can run dangerous code on your runner by opening a pull request. The runner executes whatever the workflow file on that branch says. On a private repo where you control who can push, the risk is small. On a public repo, treat any self-hosted runner as a machine that strangers can execute code on.

Do you need GitHub at all?

No. Git is the standard, and GitHub is a convenience. Forgejo and Gitea are self-hosted forges, a forge being a Git host with issues and pull requests attached. Both ship as a single Go binary, both run on a small VPS, and Forgejo is a 2022 fork of Gitea that now powers Codeberg. Moving a repository is one command, because the wire protocol is identical.

git remote -v
git remote set-url origin git@git.example.com:you/vps-deploy.git
git push origin main

Every commit moves, because every clone already holds the full history. What does not move is the layer GitHub built on top: the issues and the pull request threads. CI does not transfer either. Forgejo has its own Actions implementation reading similar YAML from .forgejo/workflows/, and its documentation is direct about the limits, saying GitHub Actions and Forgejo Actions are not the same and things might not work right away. It also needs its own runner. Plan that step as a port, not a copy.

The honest reason most projects stay is contributors. Public code has to sit where people already have an account. Your private deploy scripts do not. Those are two separate decisions, and you are allowed to answer them differently.

What breaks first, and what the error says

A push is rejected. You see this:

 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'github.com:yourname/vps-deploy.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.

Something was pushed since your last pull, often an edit you made in the web editor. Run git pull --rebase to replay your commits on top of theirs, then push again. Avoid git push --force on a shared branch, because it removes the other commits from that branch on the server.

fatal: refusing to merge unrelated histories. You ran git init locally and let GitHub create the repository with a README. The two histories share no commit, so Git will not guess. The clean fix is to clone the GitHub copy into a new folder and move your files into it.

error: src refspec main does not match any. The branch you named does not exist here. Usually the repository has zero commits so far, or your branch is called master. git branch --show-current settles it.

A secret reached a commit. Rotate the credential now. Treat it as public from the moment it was pushed, because forks, mirrors and cached views hold copies you have no way to delete.

FAQ

Is GitHub the same thing as Git?

No. Git is a version control program you install on a machine, and it works with no network and no account. GitHub is a commercial hosted service that stores Git repositories and adds a web interface, issues, pull requests and CI around them. Git was released in 2005 and GitHub launched in 2008 on top of it. You can run Git forever without GitHub. Every GitHub feature depends on Git underneath.

Do I need a GitHub account to use Git on my VPS?

No. git init, git commit and git log work on a server with no remote configured at all, which is already enough to track changes to /etc files or deploy scripts. An account becomes useful when you want a copy of the history that survives the server, or a second machine that can clone it. Self-hosted forges such as Forgejo and Gitea cover the same need on hardware you own, and a plain SSH remote pointing at a bare repository on another box works with no forge software at all.

What is a pull request?

A pull request is a request to merge one branch into another, with a discussion page attached. You push a branch, open the PR against main, and the host shows the change commit by commit so reviewers can comment on individual lines and automated checks can report pass or fail. It is a GitHub feature rather than a Git feature, so Git itself has no command for it. Other hosts implement the same idea, sometimes calling it a merge request.

Should I run a GitHub Actions runner on my own VPS?

For a private repository, often yes. The job runs on hardware you already pay for, no minutes are metered, the build cache stays warm, and a deploy no longer needs an inbound SSH key exposed to the internet, because the runner connects outbound to GitHub and asks for work. For a public repository, GitHub advises against it: anyone can fork your repo and open a pull request whose workflow runs code on your machine.

Can I move my repositories off GitHub later?

The code, yes, easily. Every clone holds the complete history, so git remote set-url origin <new url> followed by a push moves everything a commit contains. What stays behind is the layer GitHub owns: issues, pull request discussions and Actions history live in its database, not in your .git folder. Migration tools can copy issues through the API, and workflow files usually need editing for the new host's CI. Keeping that in mind is the argument for putting real documentation in the repository rather than in issue threads.

#github#git#version-control#ci-cd#developer-tools