{"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.","category":"deployment","tags":["gitlab","ci","hosting"],"models":["any"],"proven":false,"passing_families":0,"model_testing":"Not yet model-tested; testing Jan 2027","url":"https://theskillvault.ai/skills/set-up-gitlab-ci","api_url":"https://theskillvault.ai/api/skills/set-up-gitlab-ci","raw_url":"https://theskillvault.ai/api/skills/set-up-gitlab-ci/raw","body":"# Set up GitLab CI\n\nGitLab 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.\n\n## When to use\n- Your repo lives on GitLab.com or a self-managed GitLab.\n- You want tests and a build to run on every merge request.\n- You want deploys to staging and production from GitLab.\n- Your pipelines are slow and reinstall everything each run.\n\n## When not to use\n- Your code is on GitHub. Use configure-github-actions-ci.\n- You are still choosing stages, gates and rollout strategy. Use design-deployment-pipeline first.\n\n## Steps\n1. Add `.gitlab-ci.yml` at the repo root with `stages: [build, test, deploy]`.\n2. Pin an image that matches your runtime, such as `node:22`.\n3. Add a `workflow: rules:` block so pipelines run for merge requests and the default branch, not twice per push.\n4. Cache dependencies keyed on the lockfile, and pass build output on with `artifacts`.\n5. Put lint, type-check and tests in the test stage. Jobs in one stage run in parallel.\n6. Store secrets under Settings > CI/CD > Variables. Mark them masked, and protected if only protected branches need them.\n7. Add a staging deploy on the default branch, and a production deploy with `when: manual`.\n8. Protect the default branch and the production environment so only maintainers can run the deploy.\n9. Push a branch, open a merge request and watch the pipeline under Build > Pipelines.\n\n```yaml\nstages: [build, test, deploy]\n\nworkflow:\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\ndefault:\n  image: node:22\n  cache:\n    key: { files: [package-lock.json] }\n    paths: [.npm/]\n  before_script: [npm ci --cache .npm --prefer-offline]\n\nbuild:\n  stage: build\n  script: [npm run build]\n  artifacts: { paths: [dist/], expire_in: 1 day }\n\ntest:\n  stage: test\n  script: [npm run lint, npm test]\n\ndeploy_production:\n  stage: deploy\n  script: [npx wrangler deploy] # or your host's CLI\n  environment: { name: production, url: https://example.com }\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n      when: manual\n```\n\n## Gotchas\n- `only` and `except` are legacy. Mixing them with `rules` in one job is an error.\n- Masked variables still leak if a script prints them in a transformed form. Never `echo` secrets or run with shell tracing on.\n- Protected variables are empty on unprotected branches, so a deploy job there fails with a blank token.\n- Caches are best-effort and may be missing. Jobs must still work, just slower.\n- Shared runners on GitLab.com use compute minutes. Cancel redundant pipelines with `interruptible: true`.\n- Docker-in-Docker builds need a `docker:dind` service and a runner that allows it.\n\n## Portability\nWorks 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.\n\n## Example prompt\n\"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.\"\n\n## Done when\n- A merge request shows a green pipeline with build and test jobs.\n- Secrets are stored as masked CI/CD variables, never in the YAML or repo.\n- Production deploys only from the default branch after a manual click by a maintainer.\n\n---\nAdapted from \"gitlab-ci-patterns\" in wshobson/agents (MIT, Copyright (c) 2024 Seth Hobson). See LICENSE.txt.","files":["LICENSE.txt"],"license":"MIT","license_name":"MIT License","license_url":"https://opensource.org/license/mit","attribution":"Based on work by Seth Hobson (wshobson/agents, gitlab-ci-patterns); outside fixes by thc1006 (Copyright (c) 2024 Seth Hobson, MIT License), https://github.com/wshobson/agents/tree/7e2fb559cf4b824b27f1fec49fe88f7511066d26/plugins/cicd-automation/skills/gitlab-ci-patterns; adapted by TheSkillVault, https://theskillvault.ai","install":{"claude_code":"curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/set-up-gitlab-ci/raw' -o ~/'.claude/skills/set-up-gitlab-ci/SKILL.md' && curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/set-up-gitlab-ci/files/LICENSE.txt' -o ~/'.claude/skills/set-up-gitlab-ci/LICENSE.txt'","cursor":"curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/set-up-gitlab-ci/raw' -o '.cursor/skills/set-up-gitlab-ci/SKILL.md' && curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/set-up-gitlab-ci/files/LICENSE.txt' -o '.cursor/skills/set-up-gitlab-ci/LICENSE.txt'","agents":"curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/set-up-gitlab-ci/raw' -o '.agents/skills/set-up-gitlab-ci/SKILL.md' && curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/set-up-gitlab-ci/files/LICENSE.txt' -o '.agents/skills/set-up-gitlab-ci/LICENSE.txt'","mcp_url":"https://theskillvault.ai/mcp","cursor_mcp_deeplink":"cursor://anysphere.cursor-deeplink/mcp/install?name=theskillvault&config=eyJ1cmwiOiJodHRwczovL3RoZXNraWxsdmF1bHQuYWkvbWNwIn0%3D","mcp_config":{"mcpServers":{"theskillvault":{"url":"https://theskillvault.ai/mcp"}}}},"bundles":[],"tested_on":[{"family":"chatgpt","name":"ChatGPT","outcome":null,"date":null,"source":null,"counts":false,"stale":false},{"family":"claude","name":"Claude","outcome":null,"date":null,"source":null,"counts":false,"stale":false},{"family":"gemini","name":"Gemini","outcome":null,"date":null,"source":null,"counts":false,"stale":false},{"family":"grok","name":"Grok","outcome":null,"date":null,"source":null,"counts":false,"stale":false},{"family":"llama","name":"Llama","outcome":null,"date":null,"source":null,"counts":false,"stale":false},{"family":"deepseek","name":"DeepSeek","outcome":null,"date":null,"source":null,"counts":false,"stale":false},{"family":"mistral","name":"Mistral","outcome":null,"date":null,"source":null,"counts":false,"stale":false},{"family":"qwen","name":"Qwen","outcome":null,"date":null,"source":null,"counts":false,"stale":false},{"family":"kimi","name":"Kimi K","outcome":null,"date":null,"source":null,"counts":false,"stale":false}],"proven_rule":{"threshold":5,"window_days":365,"computed_at":"2026-11-03T21:40:06.669Z"}}