The GitLab
/ Gauntlet
Primer.
You have used GitHub for years. Your fingers know git push
like a heartbeat. Welcome to labs.gauntletai.com — same Git underneath,
slightly different chrome on top. This manual gets you operational in about twenty-five minutes.
Ten numbered drills. Six interactive trainers. One final challenge with a personal debrief. Complete them in order, or jump in where you are weakest.
——
Get inside the
perimeter.
The instance lives at labs.gauntletai.com. Sign in with the username and password issued to you when your account was provisioned. Once you are in, the Dashboard is your home base: think of it as the GitHub homepage, rearranged.
- Open labs.gauntletai.com/users/sign_in in your browser of choice.
- Sign in with your GitLab username and password. If you don’t have credentials yet, ping your team lead — accounts are provisioned by hand.
- You will land on the Dashboard. Bookmark it. Pin the tab. This is where every project, MR, and todo flows back to.
- Your first move after login: mint a Personal Access Token and turn on git’s credential helper (see Drill 06) so you never type a password into Git again.
——
The translation
layer.
GitLab and GitHub are 99% identical on the command line. The differences are almost all vocabulary. Tap each card to reveal the GitLab equivalent and a one-line explanation. Flip them all to clear the drill.
——
Spin up a
new project.
A “project” in GitLab is what GitHub calls a “repo.” Same artifact,
friendlier name. Here is the click trail — four steps from the dashboard to a working
remote that you can git clone.
- From the left sidebar, open Projects and select New project.
- Choose Create blank project. (Templates and Imports live alongside it — useful later.)
- Name it. Set visibility to Internal for most Gauntlet AI work — team members can read, the wider internet cannot.
- Click Create project. You now have a Git remote at
labs.gauntletai.com/<group>/<name>.git.
Tutorial → docs.gitlab.com / Create a project
——
Everyday git,
identical.
The whole point of Git is that the remote is interchangeable. Every command you have ever typed against GitHub works against labs.gauntletai.com with no edits. Run the simulator below to walk a feature branch from clone to push, command by command.
The push response is the one place GitLab does something distinctive — it hands you back a URL to open a Merge Request immediately. Hold onto that URL. We’ll use it next.
——
Pull Request,
renamed: Merge Request.
The single biggest naming difference in your daily life. Same review flow, same comment threads, same approvals, same merge button. Side by side, the two workflows look like this:
// GitHub (what you know)
- Push branch with
git push -u origin <branch> - GitHub returns: “Compare & pull request”
- Open Pull Request — title, description, reviewers
- Reviewers comment, approve, request changes
- CI runs via GitHub Actions on each push
- Click Merge pull request when green
// GitLab (what you’re learning)
- Push branch with
git push -u origin <branch> - GitLab returns: “Create merge request for <branch>” + URL
- Open Merge Request — title, description, reviewers
- Reviewers comment, approve, request changes
- CI runs via GitLab CI/CD (
.gitlab-ci.yml) on each push - Click Merge when green
Tutorial → docs.gitlab.com / Create a merge request
——
Log in once,
stay logged in.
Two minutes of setup, zero password prompts for the rest of your tour. Mint a Personal Access Token, let git’s credential helper cache it on disk, then clone over HTTPS like nothing ever changed. One-time pain. Forever-time payoff.
-
Mint a Personal Access Token. Click your avatar (top-right of GitLab) →
Edit profile → Access Tokens. Give it a name
(e.g.
laptop-2026), pick an expiry, and tick theread_repositoryandwrite_repositoryscopes. Click Create — then copy the token immediately. GitLab will never show it to you again. -
Tell git who you are and turn on the credential helper (right column, top
block).
credential.helper storeis the magic line — it’s what turns this into a one-time login. - Clone any project over HTTPS (right column, bottom block). Grab the URL from the project’s blue Code button → Clone with HTTPS.
- At the prompt, type your GitLab username, then paste the PAT as the password. Not your Gauntlet AI account password — the token you just minted.
- That’s the whole drill. Every clone, push, and pull from this machine is now passwordless — until the token expires. Treat the PAT like a password; if a laptop walks off, revoke it from the same Access Tokens page.
# identity — shows up on every commit you make git config --global user.name "Your Name" git config --global user.email "you@gauntletai.com" # the magic line — cache credentials on disk after first login git config --global credential.helper store
# clone any project over HTTPS git clone https://labs.gauntletai.com/<group>/<project>.git # git prompts you exactly once: # Username for 'https://labs.gauntletai.com': your-gitlab-username # Password for 'https://...': <paste your PAT here> # from now on: no prompts, ever. credentials live in ~/.git-credentials git pull git push
Full guides → docs.gitlab.com / Personal Access Tokens · git-scm.com / gitcredentials
——
The big GitLab
win: built-in CI.
GitLab’s pipeline runner is built into the same surface as your code — no separate
tab, no separate billing, no separate vocabulary. Drop a .gitlab-ci.yml
at the root of your project and it runs on every push. Toggle stages on the left, watch
the YAML rebuild on the right, then run a fake pipeline.
// PIPELINE STAGES
Full docs → docs.gitlab.com / Get started with GitLab CI/CD
——
Pipelines that fit
in four gigs.
Our runners are small Docker containers: 1 to 4 GB of RAM and a couple of CPUs. That’s plenty, if your tools know it. Most red pipelines on small runners aren’t bugs. They’re tools sized for a 32 GB laptop getting OOM-killed. Six rules, one simulator, and one way to split heavy work. The full copy-paste template is in Drill 10.
Know the kill signal.
exit code 137, a bare Killed, or heap out of memory means the
job ran out of RAM. Fix memory first, then debug your code.
Cap the workers.
Containers often report the host’s CPU count, so tools spawn 8+ workers on a
2-CPU box. Pin it: jest --maxWorkers=2, pytest -n 2,
go test -p 2, make -j2.
Cap the heap.
Tell the runtime where the ceiling is. Aim for ~75% of runner RAM:
NODE_OPTIONS=--max-old-space-size=1536,
-XX:MaxRAMPercentage=75, GOMEMLIMIT=1500MiB.
One heavy thing per job.
Don’t build, run a database, and run e2e tests in one job. Split them up and pass build
output forward with artifacts + needs.
Shard anything over ~10 min.
parallel: 4 runs four copies of a job. Each one takes a slice using
$CI_NODE_INDEX / $CI_NODE_TOTAL. You get the same coverage, a quarter of the
wall time, and a smaller footprint per job.
Don’t run what didn’t change.
rules: changes: skips untouched areas. interruptible: true kills stale
pipelines when you push again. timeout: 15m stops runaway jobs early.
A Jest test job, out of the box, on a small runner. It starts out OOM-killed. Change the dials until it fits and finishes fast. The YAML underneath updates as you go.
// RUNNER & JOB DIALS
// Numbers are illustrative, not measured, but the shape is real: RAM scales with workers, speed scales with CPUs.
When a job is long or memory-hungry, the fix is almost never “get a bigger runner.” Break it into small jobs that each fit comfortably, and let GitLab run them side by side.
→ artifact
parallel: 4 + --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
parallel: matrix: - PKG: [api, web, cli]
artifacts: paths: + needs: [build]
if: $CI_PIPELINE_SOURCE == "schedule"
when: manual
cache: key: files: [package-lock.json]
Full docs → docs.gitlab.com / Pipeline efficiency · docs.gitlab.com / parallel
——
Pay the setup tax
once.
Before a job runs a single test, it pays three taxes: pull an image, install tools, install dependencies. On a small instance with a couple of runners, paying all three in every job is where your minutes go. Bake the tools into a prebuilt image, install dependencies once per pipeline, and keep the instance tidy.
uv, pnpm.rebuild when
Dockerfile.ci changesnode_modules, .venv, the Go module cache.reinstall when the lockfile changes
the only thing you should pay for each time
Four jobs from one pipeline. Grey is image + tool setup, amber is installing dependencies, and green is the work you actually wanted done. Flip the two switches and watch the grey and amber shrink.
- Put a
Dockerfile.ciat the project root. Start from a slim base and add only the tools your jobs need. - Add a
ci-imagejob in the built-in.prestage. It runs only whenDockerfile.cichanges on the default branch. - It pushes to your project’s own registry as
$CI_REGISTRY_IMAGE/ci:latest. Every other job uses that as itsimage:. - First time only: start the pipeline by hand (Build → Pipelines → Run pipeline) so the image exists before anything needs it.
- Keep dependencies out of the image. They change too often, and that’s the install layer’s job.
# tools only. no app code, no node_modules FROM node:20-slim RUN apt-get update \ && apt-get install -y --no-install-recommends git ca-certificates curl jq \ && rm -rf /var/lib/apt/lists/* # keep the layer small
ci-image: stage: .pre # built-in, always first image: docker:27 services: [docker:27-dind] # needs a privileged runner variables: DOCKER_TLS_CERTDIR: "/certs" script: - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - docker build -f Dockerfile.ci -t "$CI_REGISTRY_IMAGE/ci:latest" . - docker push "$CI_REGISTRY_IMAGE/ci:latest" rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH changes: [Dockerfile.ci] default: image: $CI_REGISTRY_IMAGE/ci:latest
- Install from the lockfile, strictly. Use the command that refuses to drift, so every job gets byte-for-byte the same dependencies.
- One
installjob does it. Its download cache is keyed on the lockfile, so it’s fast unless dependencies actually changed. - It hands the installed folder forward as a short-lived artifact. Later jobs list
needs: [install]and get it automatically. No reinstall.
npm cipnpm install --frozen-lockfileyarn install --immutableuv sync --frozenpoetry syncgo mod downloadinstall: stage: install script: - npm ci --cache .npm --prefer-offline cache: # speeds up THIS job key: files: [package-lock.json] paths: [.npm/] artifacts: # hands deps to later jobs paths: [node_modules/] expire_in: 1 hour # don't fill the server disk test: stage: test needs: [install] # downloads node_modules/ script: - npx jest --maxWorkers=2
expire_in.npm/, .uv-cache/, Go modulesInstalled deps, build output (dist/), reportsOne pipeline per push.
A branch with an open MR can trigger two identical pipelines. Use workflow: rules:
to run the MR pipeline and skip the branch one. That halves your load on its own.
Expire your artifacts.
Artifacts live on the server’s disk. Use expire_in: 1 hour for installed
deps and 1 day for build output. Only releases deserve longer.
Clean up old images.
Settings → Packages and registries → Cleanup policies. Keep the last 5 tags and drop anything older than 14 days. Otherwise registry disk only grows.
Cancel stale runs.
Mark jobs interruptible: true. When you push again, GitLab cancels the old pipeline
and frees the runner for the one that matters.
Shards ≤ runner slots.
Small instances often have only 2–4 concurrent job slots. Running parallel: 8 on 2 slots
just queues, so match shards to slots.
One deploy at a time.
resource_group: production queues deploys instead of racing them. Two deploys
landing at once is a bad afternoon on any size instance.
Full docs → Caching · workflow:rules · Registry cleanup
——
The pipeline
forge.
Everything from Drills 07–09 in one place. Every dial rewrites the YAML on the right, and the lines that changed light up. The scoreboard tells you how lean the result is. Start from Out of the box and work your way to 100, or jump to the lean preset and copy it straight into your project.
// STACK
// RUNNER
// TESTS
// SPEED
// HYGIENE
// SCORECARD
// Times and memory are illustrative estimates for a typical mid-size project. The YAML is real: copy it, then swap in your own paths and commands.
Full docs → CI/CD YAML reference · needs (DAG pipelines)
——
Further recon.
You are operational. From here, the official GitLab docs are excellent — we’ve curated
the highest-leverage handful below. Internal questions still go to #gitlab-help.
GitLab for GitHub users
The single best document if you only read one. Side-by-side mapping of every concept.
// fundamentalsGitLab Basics
Official walk-through of the core surface area. Skim the headings, deep-read what bites you first.
// review flowMerge Requests, in depth
Approvals, draft MRs, reviewers vs assignees, squash on merge, and the rest of the toolbox.
// pipelinesGitLab CI/CD — full docs
Once Drill 07 made sense: artifacts, caches, environments, manual jobs, parallel matrix builds.
// lean runnersPipeline efficiency
GitLab’s own guide to faster, cheaper pipelines. It’s the long version of Drills 08–10.
// planningIssues & Boards
Identical mental model to GitHub Issues, plus stronger linking between issues, MRs, and milestones.
// fundamentalsPro Git (free book)
If anything underneath the chrome ever feels unclear: this is the canonical reference.
——
The final
challenge.
Fourteen questions across six skill areas. One attempt per question, so pick deliberately. A wrong answer doesn’t end the run. It gets logged, and at the end you get a personal debrief: where you’re strong, where you’re shaky, and which drill to revisit. Score 10 or more to clear the gauntlet. Your name and last score stay put for this browser session.
14 questions · 6 skill areas · ~5 minutes · debrief at the end