---
name: set-up-gitlab-ci
description: Sets up GitLab CI/CD in .gitlab-ci.yml - build, test and deploy stages, dependency caching, artifacts, merge request runs, masked and protected variables, and a deploy job behind a manual gate. Use when the user's code is on GitLab and they want CI, tests on merge requests, a GitLab deploy job, or faster GitLab jobs and runners.
license: MIT
metadata:
  category: deployment
  tags: "gitlab,ci,hosting"
  author: johnny-bravo
  version: "1.0"
  updated: "2026-11-02"
  status: published
  aliases: "gitlab-ci.yml, gitlab ci cd, gitlab runner, merge request pipeline, gitlab deploy"
  source_url: "https://github.com/wshobson/agents/tree/7e2fb559cf4b824b27f1fec49fe88f7511066d26/plugins/cicd-automation/skills/gitlab-ci-patterns"
  source_license: MIT
  copyright: "Copyright (c) 2024 Seth Hobson"
  original_author: "Seth Hobson (wshobson/agents, gitlab-ci-patterns); outside fixes by thc1006"
  modified: "Adapted by TheSkillVault on 2026-11-02: rewritten for a typical web app with an original pipeline using rules instead of only/except; the Kubernetes, Terraform and security-scan blocks were cut to notes; added variables and secrets handling, gotchas and done-when. Not the original file."
  imported_on: "2026-11-02"
---

# Set up GitLab CI

GitLab runs whatever `.gitlab-ci.yml` at the repo root describes. Start with build and test on every merge request, then add a deploy job behind a button.

## When to use
- Your repo lives on GitLab.com or a self-managed GitLab.
- You want tests and a build to run on every merge request.
- You want deploys to staging and production from GitLab.
- Your pipelines are slow and reinstall everything each run.

## When not to use
- Your code is on GitHub. Use configure-github-actions-ci.
- You are still choosing stages, gates and rollout strategy. Use design-deployment-pipeline first.

## Steps
1. Add `.gitlab-ci.yml` at the repo root with `stages: [build, test, deploy]`.
2. Pin an image that matches your runtime, such as `node:22`.
3. Add a `workflow: rules:` block so pipelines run for merge requests and the default branch, not twice per push.
4. Cache dependencies keyed on the lockfile, and pass build output on with `artifacts`.
5. Put lint, type-check and tests in the test stage. Jobs in one stage run in parallel.
6. Store secrets under Settings > CI/CD > Variables. Mark them masked, and protected if only protected branches need them.
7. Add a staging deploy on the default branch, and a production deploy with `when: manual`.
8. Protect the default branch and the production environment so only maintainers can run the deploy.
9. Push a branch, open a merge request and watch the pipeline under Build > Pipelines.

```yaml
stages: [build, test, deploy]

workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

default:
  image: node:22
  cache:
    key: { files: [package-lock.json] }
    paths: [.npm/]
  before_script: [npm ci --cache .npm --prefer-offline]

build:
  stage: build
  script: [npm run build]
  artifacts: { paths: [dist/], expire_in: 1 day }

test:
  stage: test
  script: [npm run lint, npm test]

deploy_production:
  stage: deploy
  script: [npx wrangler deploy] # or your host's CLI
  environment: { name: production, url: https://example.com }
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual
```

## Gotchas
- `only` and `except` are legacy. Mixing them with `rules` in one job is an error.
- Masked variables still leak if a script prints them in a transformed form. Never `echo` secrets or run with shell tracing on.
- Protected variables are empty on unprotected branches, so a deploy job there fails with a blank token.
- Caches are best-effort and may be missing. Jobs must still work, just slower.
- Shared runners on GitLab.com use compute minutes. Cancel redundant pipelines with `interruptible: true`.
- Docker-in-Docker builds need a `docker:dind` service and a runner that allows it.

## Portability
Works with any language: swap the image and the install and test commands for Python, Go, Ruby or Java. Works with any host that has a CLI or API, such as Cloudflare, Netlify, Fly.io, a VPS over SSH or Kubernetes.

## Example prompt
"Create a .gitlab-ci.yml for my Node app that runs lint and tests on merge requests, caches npm, and deploys to production with a manual button."

## Done when
- A merge request shows a green pipeline with build and test jobs.
- Secrets are stored as masked CI/CD variables, never in the YAML or repo.
- Production deploys only from the default branch after a manual click by a maintainer.

---
Adapted from "gitlab-ci-patterns" in wshobson/agents (MIT, Copyright (c) 2024 Seth Hobson). See LICENSE.txt.
