Projects, Repository Settings, Visibility, Forks, Protected Resources, and Project Templates: Guided Hands-On Workflow and Core Operations
Create and inspect a disposable GitLab project, connect it to local Git, verify hosted and repository state independently, and compare blank, template-derived, and fork workflows without relying on paid features.
Learning objectives
- Create a disposable private project through a free-compatible GitLab UI or API path without accidentally creating a competing Git history.
- Clone, commit, push, and independently prove the relationship between local Git refs and GitLab project/default-branch metadata.
- Change safe project metadata only after capturing a before-state and then verify the exact hosted state that changed.
- Compare blank-project, built-in-template, and fork workflows without requiring Premium/Ultimate or a second namespace.
- Inspect protected branch/tag state before changing any rule and explain the role/tier boundary for each action.
glab reuses the authenticated context established in
Chapter 02 without printing its credential.
1. Preflight: prove host, identity, and local state before creation
Project creation is a mutation, so first prove which GitLab host and
account glab will target. Then generate a unique
disposable name so rerunning the lab does not collide with an older
project.
glab auth status
GITLAB_USER="$(glab api user --jq .username)"
LAB_NAME="project-governance-lab-$(date +%Y%m%d%H%M%S)"
printf 'user=%s\nproject=%s\n' "$GITLAB_USER" "$LAB_NAME"
# Local preflight: do not run from a valuable repository.
pwd
git rev-parse --show-toplevel 2>/dev/null || printf 'not inside a Git worktree\n'
If glab auth status names a Self-Managed host instead
of GitLab.com, stop and decide whether that instance is an approved
training target. Host ambiguity is a governance failure, not a
convenience.
2. Create one private project with server-side README initialization
The mandatory path uses the Projects REST API through
glab api. That keeps the target and fields explicit and
returns machine-readable state. We intentionally initialize a README
on the server so this lesson can demonstrate a hosted-first
workflow: clone the server history instead of creating a competing
local history.
FULL_PATH="$(glab api projects -X POST -f "name=$LAB_NAME" -f "path=$LAB_NAME" -f "visibility=private" -f "initialize_with_readme=true" -f "description=Disposable Chapter 03 project governance lab" --jq .path_with_namespace)"
printf 'created=%s\n' "$FULL_PATH"
PROJECT_ID="$(glab repo view "$FULL_PATH" -F json --jq .id)"
glab api "projects/$PROJECT_ID" --jq '{id,path_with_namespace,visibility,default_branch,empty_repo,archived}'
Expected state: the project is Private, is no longer empty, and has a default branch because README initialization created the first commit. If the project already exists, do not blindly retry the POST; choose another disposable name or inspect the existing project first.
3. Correlate API state with the web UI
Open the project with
glab repo view "$FULL_PATH" --web. In the GitLab UI,
identify the project namespace/path, visibility, default branch,
repository files, and Settings → General surfaces.
Do not change anything yet. Your goal is to map human-facing labels
to the fields you just observed through the API.
Depending on GitLab version, project features may appear under Visibility, project features, permissions. A Self-Managed administrator can restrict visibility levels or disable features; absence of a control is not automatically a bug.
4. Clone the hosted history, make one local change, and push it
Because the server already owns the first commit, cloning gives the local repository that same ancestry. This avoids the unrelated-history trap.
LOCAL_DIR="${LAB_NAME}-local"
glab repo clone "$FULL_PATH" "$LOCAL_DIR"
cd "$LOCAL_DIR"
printf 'local_head_before=%s\n' "$(git rev-parse HEAD)"
printf 'remote_url=%s\n' "$(git remote get-url origin)"
git branch -vv
printf '\nChapter 03 verification change.\n' >> README.md
git add README.md
git diff --cached --check
git commit -m "docs: add Chapter 03 verification line"
git push origin HEAD
LOCAL_SHA="$(git rev-parse HEAD)"
REMOTE_SHA="$(git ls-remote origin "refs/heads/$(git branch --show-current)" | awk '{print $1}')"
printf 'local=%s\nremote=%s\n' "$LOCAL_SHA" "$REMOTE_SHA"
test "$LOCAL_SHA" = "$REMOTE_SHA" && echo 'ref identity verified'
The commit changes repository state and therefore appears in Git history. It does not change the project description, visibility, topics, members, or protected-ref rules.
5. Capture before-state, change only safe hosted metadata, and prove after-state
Return to the parent shell directory if desired. We will update the
description with glab repo update; this is hosted
metadata, so no Git commit should appear.
# Works from anywhere because the project target is explicit.
glab repo view "$FULL_PATH" -F json --jq '{description,visibility,default_branch,topics}'
glab repo update "$FULL_PATH" --description "Disposable Chapter 03 project — metadata verified"
glab repo view "$FULL_PATH" -F json --jq '{description,visibility,default_branch,topics}'
# Local repository HEAD should be unchanged by a hosted description edit.
cd "$LOCAL_DIR" 2>/dev/null || true
git rev-parse HEAD
devops-academy-lab through the
UI if the control is available, but do not put confidential business
names or customer identifiers in topic names because instance topics
can be discoverable.
6. Inspect protected refs before touching policy
Never assume the default branch is unprotected or protected with a specific role combination. Read the current rules first. The default branch is commonly protected by default, but instance/group policy can change the details.
PROJECT_ID="$(glab repo view "$FULL_PATH" -F json --jq .id)"
glab api "projects/$PROJECT_ID/protected_branches" --jq '.[] | {name,allow_force_push,push_access_levels,merge_access_levels}'
glab api "projects/$PROJECT_ID/protected_tags" --jq '.[] | {name,create_access_levels}'
This lesson does not mutate protection. Chapter 08 will go deep on merge governance. Here, the key lesson is that a Git push can be rejected even when authentication succeeded because GitLab policy around the target ref denies the operation.
7. Compare a template-derived project and a fork without requiring paid controls
Free-compatible template path: in Create new → New project/repository → Create from template, inspect the Built-in tab. Built-in templates are part of project creation across tiers. Create another disposable private project only if you want the extra practice, then compare its first commits and files with the blank project.
Fork path: forks are available on all tiers, but a useful fork requires a destination namespace where the fork path is valid. If you own a disposable group or have another permitted namespace, fork a synthetic/public training project. Otherwise use the read-only comparison below rather than creating infrastructure just to satisfy the exercise.
| Observation | Blank / template-derived project | Fork |
|---|---|---|
| Git history | Independent history created by initialization/template. | Copied upstream repository history. |
| Upstream relationship | None unless separately configured. | GitLab records a fork relationship to upstream. |
| Issues/MRs/wiki | Independent project state. | Not simply cloned from upstream as fork content. |
| Synchronization expectation | No automatic upstream relationship. | Designed for contribution/sync patterns with upstream. |
8. Challenge: choose the correct surface
For each request, decide whether you would use Git, project settings, the Projects API/glab, or a protected-ref setting before reading the answer:
- Change the project description without creating a commit.
- Move local branch
featureto another commit. - Find whether the server considers the project archived.
- Prevent direct pushes to
main. - Make a project discoverable to unauthenticated users.
Answers: hosted project metadata; Git; hosted project metadata/API; GitLab protected-branch policy; hosted visibility. Only the second operation is intrinsically a Git ref operation.
9. Cleanup / rollback
Keep the lab project if you want to reuse it in Chapters 04–09. If you are finished, remove the local clone and archive the hosted project rather than deleting it immediately. Archiving is reversible but has important side effects such as removing fork relationships and stopping scheduled pipelines, so inspect before acting.
# From outside the local clone:
cd .. 2>/dev/null || true
rm -rf -- "$LOCAL_DIR" # only the disposable local clone named above
# Optional hosted cleanup — verify target twice before archiving.
glab repo view "$FULL_PATH" -F json --jq '{path_with_namespace,visibility,archived}'
# glab repo update "$FULL_PATH" --archive=true
# glab repo view "$FULL_PATH" -F json --jq '{path_with_namespace,archived}'
$FULL_PATH names only the disposable project.
Project deletion is more destructive and is not required by this
lesson.
Knowledge check
Why did this lab initialize the README remotely and then clone, instead of creating an independent local first commit?
To establish one source-of-truth history. The clone inherits the remote first commit, avoiding an unrelated-history divergence from the start.
The description changed, but git log did not. Is
that a failure?
No. Description is GitLab-hosted project metadata, not repository content, so no Git commit is expected.
A push is denied although SSH authentication works. What should you inspect next?
Authorization and target-ref policy: membership/role plus protected-branch rules. Successful authentication proves identity, not permission to mutate a ref.
Why is a fork not equivalent to copying a repository with
git clone and pushing elsewhere?
A GitLab fork records an upstream project relationship and participates in fork-based merge-request workflows; an independent Git copy has no such hosted relationship.
Summary
The safe workflow is inspect → create one disposable hosted source of truth → clone that history → make a small Git change → compare local and remote SHAs → change hosted metadata separately → inspect protected refs → clean up deliberately. Each surface proves a different class of state, and the learner never needs a paid tier, custom runner, or real production project.
Official references
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.