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 onmasterand no remote. Goal: publish it to the internal GitLab and push.
Host, username, and project names here are fictional (
git.example.com, userana). 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).
glabwill 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
| Flag | Who can see it | Notes |
|---|---|---|
--private | Only you + project members you add | Safest default (least exposure) |
--internal | Any authenticated user on the instance | GitLab’s default if you pass nothing |
--public | Anyone, no auth | Rarely 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,
--privateit 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 tomain. Push whatever your local branch actually is — check withgit 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
| Symptom | Cause | Fix |
|---|---|---|
401 Unauthorized against gitlab.com | glab targeted the wrong host | Prefix GITLAB_HOST=<host> |
403 Forbidden on create | Token lacks api scope, or no rights in target group | Regenerate token with api scope; check group membership |
Push hangs / Permission denied (publickey) | SSH key not registered or wrong port | Add key to GitLab; confirm the port in git remote -v (e.g. :2222) |
| Project name already taken | Name exists in that namespace | Use a different --name or --group |
glab not logged in | No stored credentials | glab 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.
Related pages
- Remotes-and-Tracking-Branches — what a remote is, and why
origin/mainis a local cache. The course page this one is the deep-dive for. - Pull-Commit-Push-on-a-Shared-Repo — working in the repo once others push to it.
- Using-git-stash-Safely — the stash traps, and recovering from a conflicted pop.
- _Software-Development-MOC — the rest of the engineering-craft library.