Creating a Repository on GitHub

A repository (or “repo”) on GitHub is a project’s home in the cloud: it stores every commit, branch, and file in a project’s history, plus GitHub-specific extras like issues, pull requests, and Actions workflows. Creating one is the first real step in getting a project under version control that others can see, clone, and contribute to. You can create a repository straight from the GitHub website, from the command line with the gh CLI, or by pushing an existing local project up to a brand-new empty repo. This lesson walks through all three paths, plus the Git internals that explain what actually happens when you “push to GitHub.”

Overview / How it works

Every Git project has two possible locations: a local repository (the .git folder on your machine, containing the object database and refs) and a remote repository (a copy hosted somewhere else, most commonly on GitHub). A GitHub repository is, at its core, a bare Git repository sitting on GitHub’s servers — it has the same commit, tree, and blob objects as your local .git folder, just without a working tree of checked-out files. GitHub wraps that bare repository with a web interface: issues, pull requests, wikis, Actions, and access control.

Recall the object model: a commit object records an author, a message, a pointer to a parent commit (or several, for a merge), and a pointer to a tree — a snapshot of the whole directory structure at that point in time. A tree lists blobs (raw file contents) and other trees (subdirectories). Every object is identified by the SHA-1 hash of its content, which is why two commits with byte-identical trees and metadata are literally the same object. A branch is nothing more than a small file containing a commit SHA — a movable pointer. HEAD normally points at a branch (e.g. refs/heads/main), which in turn points at a commit.

When you “create a repository on GitHub,” you are asking GitHub to allocate a new bare repository under an owner’s namespace (github.com/<owner>/<repo>) with a chosen visibility (public, private, or, on some plans, internal) and a default branch name, which is main by default on every repository created today. You have a choice at creation time: leave it completely empty (no commits, no branches at all yet), or have GitHub create an initial commit for you containing a README.md, a .gitignore template, and/or a license file. That choice matters a lot for how smoothly your first push goes, which is covered in Common Mistakes below.

Syntax

There are two equivalent ways to create a repository: the GitHub website, or the gh command-line tool (GitHub’s official CLI, installed separately from Git itself).

Via the website

  • Sign in to GitHub, click the + icon in the top right, then New repository.
  • Enter a Repository name (letters, numbers, hyphens, underscores — no spaces).
  • Optionally add a Description.
  • Choose Public or Private visibility.
  • Optionally check Add a README file, choose a .gitignore template, and pick a license.
  • Click Create repository.

Via the gh CLI

gh repo create <name> [flags]
Flag Purpose
--public / --private / --internal Sets visibility (one is required)
--description <text> Sets the repo description
--source . Uses the current local directory as the repo’s source
--remote <name> Adds a remote with this name (default origin) pointing at the new repo
--push Pushes the local main branch to the new remote right after creating it
--clone Clones the new (empty) repo into a new local directory after creating it
--gitignore <template> Initializes the repo with a .gitignore template (e.g. Node, Python)
--license <key> Initializes the repo with a license file (e.g. mit)

Once a repository exists, connecting a local Git project to it is done with:

git remote add origin <url>

origin is just a conventional name for “the primary remote” — you could call it anything, but almost every tutorial and tool assumes origin exists.

Examples

Example 1: Connect an existing local project to a new empty GitHub repo

Suppose you already have a project on your machine and created a completely empty repository named weather-app on GitHub (no README, no license — nothing checked).

cd weather-app
git init
git add .
git commit -m "chore: initial commit"
git branch -M main
git remote add origin git@github.com:letsmakelearningsimple/weather-app.git
git push -u origin main

Output:

Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 8 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (8/8), 2.14 KiB | 2.14 MiB/s, done.
Total 8 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:letsmakelearningsimple/weather-app.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

git init creates the .git folder and an empty object database. git add . stages every file into the index (the staging area, a binary file that tracks what the next commit will contain). git commit writes blob, tree, and commit objects and moves the main branch pointer to the new commit. git branch -M main ensures the branch is named main regardless of your Git version’s default. git remote add origin <url> just records the URL under the name origin in .git/config — nothing is transferred yet. git push -u origin main uploads every object the remote doesn’t already have and creates refs/heads/main on GitHub pointing at your latest commit; -u (short for --set-upstream) remembers the link so future plain git push/git pull commands know where to go.

Example 2: Create and clone a repo in one step with gh

gh repo create weather-app --public --clone --description "A simple CLI weather lookup tool"

Output:

✓ Created repository letsmakelearningsimple/weather-app on github.com
  https://github.com/letsmakelearningsimple/weather-app
Cloning into 'weather-app'...
warning: You appear to have cloned an empty repository.

This single command talks to GitHub’s API to create the repository, then runs a normal git clone against it. Because the repo has no commits yet, Git prints a warning — that’s expected, not an error. You now have an empty local folder called weather-app with origin already configured, ready for your first commit.

Example 3: Create a repo with a README, then clone it

If you check Add a README file when creating the repo on the website, GitHub creates an initial commit for you before you ever touch your terminal.

git clone git@github.com:letsmakelearningsimple/portfolio-site.git
cd portfolio-site
ls

Output:

Cloning into 'portfolio-site'...
remote: Enumerating objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.
README.md

git clone copies the entire object database from GitHub, creates a local main branch tracking origin/main, and checks out the latest commit’s tree into your working directory — here, a single README.md file that GitHub generated for you.

How it works step by step

When you run git push -u origin main against a freshly created empty GitHub repository:

  • Git compares the commit SHAs your local main branch has against what the remote reports it has (nothing, in this case).
  • Git walks your commit history from main backward and determines every blob, tree, and commit object the remote is missing — for a brand-new repo, that’s everything.
  • Those objects are packed into a single compressed “pack file” and transferred over the network (SSH or HTTPS).
  • The remote unpacks the objects into its own object database.
  • The remote creates the ref refs/heads/main pointing at the commit SHA you pushed. This is the moment the branch “exists” on GitHub.
  • Your local Git records that main tracks origin/main, so future git status, git push, and git pull commands can compare the two without you specifying the remote and branch every time.

Common Mistakes

Mistake 1: Initializing with a README on GitHub, then trying to push local history

If you check “Add a README” on GitHub and already have local commits, the two histories share no common commit (“unrelated histories”):

git push -u origin main

Output:

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

Git refuses because pushing would silently discard the remote’s README commit. The fix is to pull the remote history down and merge (or rebase) it in before pushing:

git pull --rebase origin main
git push -u origin main

The simplest way to avoid this entirely: create the GitHub repo completely empty (no README, no .gitignore, no license) whenever you already have a local project to push, and only let GitHub initialize the repo when you’re starting fresh and plan to git clone it.

Mistake 2: Authenticating over HTTPS with a password

GitHub removed password authentication for Git operations over HTTPS. Typing your account password when prompted fails:

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/letsmakelearningsimple/weather-app.git/'

Use a Personal Access Token (e.g. ghp_xxxxxxxxxxxx) in place of the password over HTTPS, or switch the remote to SSH and authenticate with an SSH key instead — SSH avoids typing a token on every push.

Mistake 3: Forgetting to add a remote before pushing

Running git push in a freshly git init‘d repo with no remote configured fails with fatal: No configured push destination. Always run git remote add origin <url> (or use gh repo create --source . --push, which does it for you) before your first push.

Best Practices

  • Create the GitHub repo empty when you already have local history to push; let GitHub initialize it (README/.gitignore/license) only when you plan to clone it as your starting point.
  • Prefer SSH remotes for repos you push to often — no token to paste on every push once your key is set up.
  • Add a .gitignore appropriate to your language/framework at creation time so build artifacts and dependency folders never get committed in the first place.
  • Pick a license early if the project is public; an unlicensed public repo legally defaults to “all rights reserved,” which surprises contributors.
  • Use descriptive, kebab-case repository names (weather-app, not Project1 or test).
  • Never commit real credentials in the first commit “just to get something up” — history is permanent even if you delete the file in a later commit.
  • Use Conventional Commits (feat:, fix:, chore:) from the very first commit so your history stays readable as the project grows.

Practice Exercises

  • Exercise 1: Create a new empty repository on GitHub named notes-app (no README). On your machine, create a folder with a single notes.md file, initialize it as a Git repo, commit it, and push it up so it becomes the repo’s first commit.
  • Exercise 2: Use gh repo create to create a private repository called scratch with the --clone flag, then make one commit inside the cloned folder and push it. Confirm on github.com that it shows as private.
  • Exercise 3: Deliberately create a GitHub repo with “Add a README file” checked, then try to push an unrelated local project with its own history into it. Reproduce the rejected-push error, then resolve it with git pull --rebase.

Summary

  • A GitHub repository is a bare Git repository plus GitHub’s web layer (issues, PRs, Actions).
  • You can create one via the website or with gh repo create, choosing visibility and whether to auto-initialize with a README, .gitignore, or license.
  • Connecting a local project uses git remote add origin <url> followed by git push -u origin main.
  • Initializing the GitHub repo with a README while also having local history creates unrelated histories and a rejected push — avoid it, or fix it with git pull --rebase.
  • Use SSH keys or a Personal Access Token for authentication — password auth over HTTPS no longer works.