Creating a GitLab Remote and Pushing with `glab`

A step-by-step walkthrough of taking a local git repo, creating a matching project on a self-hosted GitLab instance, wiring up the remote, and pushing — using the glab CLI. Written from a real run against an internal home-lab GitLab, so it includes the gotchas, not just the happy path.

Scenario: a local repo (sensor-logger) already had an initial commit on master and no remote. Goal: publish it to the internal GitLab and push.

Host, username, and project names here are fictional (git.example.com, user ana). The commands, outputs, and gotchas are real — substitute your own.


Prerequisites

  • git with a local repo that already has at least one commit.
  • glab — the official GitLab CLI.
  • SSH access to the GitLab instance (an SSH key registered in your GitLab profile). glab will configure git operations to use SSH.
  • A personal access token stored in glab’s config (used for API calls like creating the project).

Step 1 — Confirm glab is installed and authenticated

Before anything else, check which host you’re logged into and how git/API protocols are configured:

glab --version
glab auth status

Example output from a working setup:

git.example.com
  ✓ Logged in to git.example.com as ana
  ✓ Git operations for git.example.com configured to use ssh protocol.
  ✓ API calls for git.example.com are made over https protocol.
  ✓ REST API Endpoint: https://git.example.com/api/v4/
  ✓ Token found: ****************

Two things to note here:

  • Git = SSH, API = HTTPS. This split is normal. The project is created over the HTTPS API (using the token); the code is pushed over SSH.
  • The as <username> tells you which namespace the project lands in by default (here, ana).

If glab auth status shows you’re not logged in, run glab auth login first and follow the prompts (choose your host, paste a token, pick SSH).

Optional sanity check that the host is reachable:

curl -sS -o /dev/null -w "%{http_code}" --max-time 6 https://git.example.com
# 302 / 200 = reachable

Step 2 — The #1 gotcha: glab defaults to gitlab.com

Running glab repo create with no host context targets gitlab.com, even if you’re authenticated to a self-hosted instance. You’ll get:

ERROR
Error creating project: POST https://gitlab.com/api/v4/projects: 401 {message: 401 Unauthorized}.

That 401 is misleading — it’s not that your token is wrong, it’s that glab sent the request to the wrong server.

Fix: force the host with the GITLAB_HOST environment variable (or embed the host in the project path). Prefix it on the command:

GITLAB_HOST=git.example.com glab repo create ...

You can also set it for the whole session (export GITLAB_HOST=git.example.com) if you’ll run several commands.


Step 3 — Create the project (run from inside the repo)

Run glab repo create from within the local repo directory. When run inside a git repo, glab creates the remote project and adds the origin remote for you in one step.

cd /path/to/sensor-logger

GITLAB_HOST=git.example.com glab repo create \
  --private \
  --name sensor-logger \
  -d "Sensor reading logger and staleness watchdog"

Output:

✓ Created project on GitLab: Ana Ruiz / sensor-logger - https://git.example.com/ana/sensor-logger
✓ Added remote ssh://git@git.example.com:2222/ana/sensor-logger.git

Visibility flags — pick deliberately

FlagWho can see itNotes
--privateOnly you + project members you addSafest default (least exposure)
--internalAny authenticated user on the instanceGitLab’s default if you pass nothing
--publicAnyone, no authRarely what you want internally

If you omit a visibility flag, GitLab uses internal. On a multi-user / multi-agent instance that means everyone logged in can browse it. Choose on purpose. Visibility is easy to change later in Settings → General → Visibility.

Internal is not private. If a repo will ever hold credentials, personal data, or anything you’d not want a colleague reading, --private it at creation — not afterwards. History is not retroactively hidden by a later visibility change; anything already pushed was readable for as long as it was exposed, and clones taken in that window keep everything.

Other useful flags: --group <name> (create under a group instead of your user namespace), --defaultBranch <name>, --readme (seed a README — skip if your repo already has commits, or it will try to reconcile).


Step 4 — Verify the remote

git remote -v
origin  ssh://git@git.example.com:2222/ana/sensor-logger.git (fetch)
origin  ssh://git@git.example.com:2222/ana/sensor-logger.git (push)

Note the non-standard SSH port 2222 in this example. Self-hosted instances often move SSH off port 22. glab picks up the correct port from your host config automatically, so the remote URL just works — but it’s good to know why the URL looks unusual.


Step 5 — Push

Push your branch and set upstream tracking with -u:

git push -u origin master
branch 'master' set up to track 'origin/master'.
To ssh://git.example.com:2222/ana/sensor-logger.git
 * [new branch]      master -> master

After this, plain git push / git pull work with no extra arguments because the upstream is set.

Branch name: this repo used master (the local git default at init time). Newer setups default to main. Push whatever your local branch actually is — check with git branch --show-current.


Full sequence (copy/paste template)

# 0. Verify auth + host
glab auth status

# 1. From inside the repo, create the project on the RIGHT host
cd /path/to/<repo>
GITLAB_HOST=<your-gitlab-host> glab repo create \
  --private \
  --name <repo-name> \
  -d "<description>"

# 2. Confirm the remote glab added
git remote -v

# 3. Push and set upstream (use your actual branch name)
git push -u origin "$(git branch --show-current)"

Changing visibility later (this is NOT a git operation)

A common point of confusion: changing a project’s visibility has nothing to do with git commit or git push. Visibility lives in GitLab’s project settings on the server, not in your repository’s tracked files. There is no diff, no commit, and nothing to push — the local repo is completely untouched.

Change it one of two ways:

Via the API with glab (note the URL-encoded project path — / becomes %2F):

GITLAB_HOST=git.example.com \
  glab api "projects/ana%2Fsensor-logger" -X PUT -f visibility=internal

Valid values: private, internal, public. The change takes effect server-side immediately. To confirm, the API returns the updated project JSON — check the visibility field.

Via the UI: Settings → General → Visibility, project features, permissions.

Rule of thumb: anything about who can see or access the project (visibility, members, protected branches) is a GitLab server setting managed through the API or web UI. Only your code and files are managed through git.

Troubleshooting

SymptomCauseFix
401 Unauthorized against gitlab.comglab targeted the wrong hostPrefix GITLAB_HOST=<host>
403 Forbidden on createToken lacks api scope, or no rights in target groupRegenerate token with api scope; check group membership
Push hangs / Permission denied (publickey)SSH key not registered or wrong portAdd key to GitLab; confirm the port in git remote -v (e.g. :2222)
Project name already takenName exists in that namespaceUse a different --name or --group
glab not logged inNo stored credentialsglab auth login

Why this split (SSH vs HTTPS API) exists

  • Creating a project is an API operation → HTTPS + personal access token.
  • Pushing code is a git transport operation → SSH + your SSH key.

glab orchestrates both: it calls the REST API to make the project, then writes an SSH remote into your local git config so subsequent git push/pull use key auth with no password prompts.