gitlab-cicd
| Type | Skill |
| Plugin | awl-cicd · v0.0.22 |
| Invoke | /awl-cicd:gitlab-cicd |
| Tools | Read, Write, Edit, Bash, Glob, Grep |
| Source | plugins/awl-cicd/skills/gitlab-cicd/SKILL.md |
When Claude uses it
Abschnitt betitelt „When Claude uses it“This skill should be used when the user asks to “set up a pipeline”, “configure CI/CD”, “create .gitlab-ci.yml”, “add Docker build”, “deploy to kubernetes”, “speed up CI”, “optimize pipeline”, “CI is slow”, “pipeline takes too long”, “add claude review”, “set up iOS CI”, “set up Android CI”, or mentions “stages”, “gitlab-ci”, “ArgoCD”, “Trivy”, “gitlab runners”, “runner tags”, “pipeline caching”, “tart”, “buildkit”. Configures GitLab CI/CD pipelines with AWL reusable components (Docker/BuildKit/NPM/Composer/.NET builds, Node/PHP/.NET/Android/iOS testing, vulnerability scanning, SBOM tracking, ArgoCD deployments, Claude Code jobs) and tunes pipeline performance.
Trigger phrases: set up a pipeline · configure CI/CD · create .gitlab-ci.yml · add Docker build · deploy to kubernetes · speed up CI · optimize pipeline · CI is slow · pipeline takes too long · add claude review · set up iOS CI · set up Android CI · stages · gitlab-ci · ArgoCD · Trivy · gitlab runners · runner tags · pipeline caching · tart · buildkit
Definition
Abschnitt betitelt „Definition“Critical: how to use templates
Abschnitt betitelt „Critical: how to use templates“All templates are pre-deployed as CI/CD components in devops/ci-cd-templates. Never copy template YAML into a project pipeline.
Always use include: component: syntax:
include: - component: $CI_SERVER_FQDN/devops/ci-cd-templates/TEMPLATE_NAME@main inputs: key: value- Example files (
references/examples/) are the patterns to follow when generating.gitlab-ci.yml. - Template config files (
references/templates/) are reference only, to understand inputs, variables, and behavior. Never inline them. - When a template provides hidden jobs (
.docker-build-simple,.deploy-autodeploy-app,.claude-code-review), customize withextends:. Don’t redefine the job. - Inputs resolve at include time. Set them on the
include:entry, not on the job that extends the hidden job. Per-job overrides go intovariables:. - Read a reference project before inventing a pattern. Every AWL convention exists in a live repo:
bafu/becasuisse-cms(Payload + PowerSync on Natron),awl-ecosystem/my-awl(Node),ensemble/mono-repo(.NET). Fetch a file withglab api "projects/<group>%2F<project>/repository/files/<path>/raw?ref=main".
Workflow
Abschnitt betitelt „Workflow“Step 1: identify the project type
Abschnitt betitelt „Step 1: identify the project type“Determine the stack: Node.js, .NET, PHP/Laravel, Statamic, Payload CMS, Migros Next.js, Android/KMP, or iOS. For opinionated setups use the application templates (app-payload, app-statamic, migros-web) or the mobile templates (android, ios-fastlane, ios-lint).
Step 2: compose the pipeline
Abschnitt betitelt „Step 2: compose the pipeline“- Read the relevant example from
references/examples/. - Consult
references/templates/for the inputs of each component. - Assemble
include:components.
Step 3: add stages and variables
Abschnitt betitelt „Step 3: add stages and variables“Define stages: matching the templates used. Top-level stages does not merge across includes; the last component’s list wins, so declare the full list in the project. Set GIT_DEPTH: 1. Add project-specific variables:.
Step 4: customize
Abschnitt betitelt „Step 4: customize“- Override hidden jobs with
extends:plusvariables:. - Add project-specific jobs (e2e tests, custom scripts).
- Give every job that reads environment-scoped CI variables an
environment:block. A job without one only sees variables scoped to*. For non-deploy jobs useenvironment: { name: staging, action: verify }, which binds the scope without creating a deployment. - Keep production deploys manual or on a protected
productionbranch.
Step 5: apply performance defaults
Abschnitt betitelt „Step 5: apply performance defaults“Add the workflow: and default: blocks from the Quick start. They handle auto-cancel, no duplicate runs, and retry on infra failure. See references/performance.md for the rationale.
Step 6: validate before pushing
Abschnitt betitelt „Step 6: validate before pushing“- Run
glab ci linton the project pipeline. It fails on component sources ($[[ inputs ]]), so lint the consuming.gitlab-ci.yml, not a template. - Run the project linter locally (Prettier, SwiftFormat, ESLint) before every push. Lint failures in CI after a push were the most common self-inflicted red pipeline.
- Never push to
maindirectly.mainauto-deploys to staging. Use/mr. - After changing
.gitlab-ci.yml, push before judging the next pipeline run. A run started before the push proves nothing.
Quick start
Abschnitt betitelt „Quick start“variables: GIT_DEPTH: 1 GIT_LFS_SKIP_SMUDGE: "1"
workflow: auto_cancel: on_new_commit: interruptible rules: # Skip branch pipelines when an MR is open. Avoids running the same commit twice. - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS when: never - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH - if: $CI_COMMIT_TAG
default: interruptible: true retry: max: 2 when: - runner_system_failure - api_failure - scheduler_failure - data_integrity_failure - unmet_prerequisites
stages: - .pre - test - build - deploy
include: - component: $CI_SERVER_FQDN/devops/ci-cd-templates/version@main - component: $CI_SERVER_FQDN/devops/ci-cd-templates/test-node@main - component: $CI_SERVER_FQDN/devops/ci-cd-templates/deploy-default-branches@mainMark release-path jobs interruptible: false (release, tag, deploy, version-bump) so they can’t be cancelled mid-flight.
Template pinning
Abschnitt betitelt „Template pinning“Most projects pin @main. Template changes then land in every pipeline immediately, and a previously green .gitlab-ci.yml can become invalid overnight. When a pipeline fails with yaml invalid after no project change, check the template repo’s recent commits first. Tags exist (v0.0.25 from June 2026) but lag behind main; ask DevOps before pinning a tag.
Runner tags (quick reference)
Abschnitt betitelt „Runner tags (quick reference)“| Tag | Executor | Pull caching | Use for |
|---|---|---|---|
web-test |
Kubernetes | IfNotPresent |
Default for Node/web/lightweight jobs |
docker |
Docker | forces always (re-pulls) |
Only when web-test is incompatible |
tart |
macOS VM (Tart) | fresh VM per job, no shared cache | iOS builds, ios-fastlane, ios-lint |
android-dev |
Android shell | persistent Gradle cache | Android builds, android template |
mac-dev |
macOS shell | n/a | Legacy macOS builds |
docker-dind |
Docker-in-Docker | n/a | Only if build-buildkit is not an option |
Prefer web-test over docker. The docker runner forces pull_policy: always, which re-pulls every image on every job (30–75 s overhead). Full details in references/performance.md.
Kubernetes runners request 200m CPU / 128Mi and cap at 2000m / 2000Mi. Raise per job with KUBERNETES_CPU_REQUEST, KUBERNETES_CPU_LIMIT, KUBERNETES_MEMORY_REQUEST, KUBERNETES_MEMORY_LIMIT in the job’s variables:.
The tart tag is served by a single macOS runner. Jobs stuck with stuck_pending_no_matching_runners or dying after 30 minutes in Cloning a new VM... mean the runner is offline. That is an infrastructure issue, not a pipeline issue. Don’t change tags to work around it; report it in Slack #tools-claudecode or to DevOps.
AWL internal Docker images
Abschnitt betitelt „AWL internal Docker images“registry.appswithlove.net/devops/docker-images/* are AWL-maintained, pre-warmed on runners, and include the @awl/* npm registry plus pnpm:
| Image | Tags | Notes |
|---|---|---|
node |
22-alpine, 24-alpine |
Only alpine variants, no full Debian node:* |
playwright |
v1.58.x-noble, v1.60.0-noble |
MS Playwright + browsers |
laravel |
php8.5-dev etc. |
Laravel CLI + Composer auth |
dotnet |
various | .NET SDK + NuGet auth |
local-awl-dev |
latest |
Local HTTPS proxy for *.local.awl.dev |
Use them when the project needs @awl/* packages or pnpm. Docker Hub pulls are fine on web-test.
Template reference
Abschnitt betitelt „Template reference“Build templates
Abschnitt betitelt „Build templates“| Template | Purpose |
|---|---|
version |
Semantic versioning with release-it |
build-buildkit |
Docker build via in-cluster BuildKit, no dind. Preferred for new projects |
build-docker-simple |
Docker build on the docker runner with custom tags |
build-docker |
Legacy develop/release branch builds |
build-docker-multiarch |
Buildx linux/amd64,linux/arm64 with registry cache |
build-npm |
NPM artifact builds |
build-composer |
PHP Composer dependency installer |
build-dotnet |
.NET application builder |
build-dotnet-migration |
Entity Framework Core migrations bundler |
publish-npm |
NPM package publishing to GitLab registry |
npm-scripts |
Matrix of npm scripts (lint, test, …) as separate jobs |
Test templates
Abschnitt betitelt „Test templates“| Template | Purpose |
|---|---|
test-node |
Node.js testing with auto-detected package manager |
test-dotnet |
.NET build and test |
test-php |
PHP/Laravel testing with composer |
test-trivy |
Container image vulnerability scanning |
test-renovate |
Renovate configuration validator |
Dependency checking
Abschnitt betitelt „Dependency checking“| Template | Purpose |
|---|---|
depcheck-node |
Node dependency vulnerability check using audit-ci |
depcheck-composer |
PHP Composer vulnerability audit |
depcheck-dotnet |
.NET dependency vulnerability scanning |
Deploy templates
Abschnitt betitelt „Deploy templates“| Template | Purpose |
|---|---|
deploy-default-branches |
Build + auto-deploy main/testing/production to Natron via ArgoCD. Default for web apps |
deploy-service |
Deploy a preset side service (redis, meilisearch) next to the app |
deploy-autodeploy-app |
Base job behind the two above, for custom deploy jobs |
deploy-grafana-dashboard |
Provision Grafana dashboards from JSON in the repo |
deploy-argocd-project |
ArgoCD project creation via GitOps |
deploy-dma-docker-compose-stack |
DMA compose stack deployments |
deploy |
Legacy trigger-based downstream deployments |
Deployment details live in the deploying-to-kubernetes and natron-deployment skills.
Mobile templates
Abschnitt betitelt „Mobile templates“| Template | Purpose |
|---|---|
android |
Gradle test/build/Updraft/Play Store, optional SBOM, coverage, CI signing |
ios-fastlane |
fastlane test/build/TestFlight on Tart runners, optional Tuist cache |
ios-lint |
SwiftFormat + SwiftLint gate without fastlane |
Tracking and security
Abschnitt betitelt „Tracking and security“| Template | Purpose |
|---|---|
track-vulntracker |
Syft scan + upload to AWL VulnTracker |
track-vulntracker-upload |
Upload an SBOM another job already produced |
GitLab’s own Jobs/Secret-Detection.gitlab-ci.yml is included automatically by deploy-default-branches and android.
Utilities
Abschnitt betitelt „Utilities“| Template | Purpose |
|---|---|
git-auto-commit |
Git automation with direct commit or MR modes |
fetch-secrets |
Read CI variables from another project via job token |
claude-code |
Base job: Claude Code with credentials, extend for custom jobs |
claude-code-review |
Post a Claude review comment on every MR push |
claude-code-autocommit |
Run a prompt, commit, open an MR |
claude-code-manual |
Same, triggered on demand via CLAUDE_PROMPT pipeline variable |
Application templates
Abschnitt betitelt „Application templates“| Template | Purpose |
|---|---|
app-payload |
Payload CMS testing (pnpm-based) |
app-statamic |
Statamic CMS testing (PHP + optional Node) |
migros-web |
Migros Next.js frontend testing |
CI/CD steps
Abschnitt betitelt „CI/CD steps“steps/ holds GitLab CI/CD steps with bundled files (for example a shared Dockerfile). Available: build-sveltekit-docker, track-dependencies. Usage in references/templates/utilities.md.
Required variables
Abschnitt betitelt „Required variables“Set in GitLab CI/CD settings. Instance-level variables need no project setup.
| Variable | Scope | Purpose |
|---|---|---|
GITOPS_TRIGGER_TOKEN |
instance | Trigger the GitOps repo (autodeploy) |
VULNTRACKER_API_KEY |
instance | VulnTracker upload |
CLAUDE_CODE_PERSONAL_TOKEN |
instance | Claude Code jobs (PAT of the claude user) |
TRIGGER_TOKEN |
project | Legacy deploy template |
NUGET_SOURCE/USER/PASSWORD |
project | .NET private feeds |
TUIST_TOKEN |
project | Tuist remote cache (masked, unprotected, scope *) |
GitLab variable constraints that bite: masked values need 8+ characters and a single line (base64-encode .p8 keys and keystores; keep short flags like true visible), hidden variables can’t be read by External Secrets, and a value containing $ needs “Expand variable reference” turned off.
Troubleshooting
Abschnitt betitelt „Troubleshooting“| Error | Cause | Fix |
|---|---|---|
yaml invalid on an unchanged pipeline |
Template on @main changed |
Check devops/ci-cd-templates commits; adapt to the new contract |
on_stop job ... have different environment name |
Deploy job overrides environment.name, stop job doesn’t |
Use the same name on both or drop the override |
| Job can’t see a CI variable | Variable scoped to an environment, job has no environment: |
Add environment: { name: <env>, action: verify } |
glab api .../projects/<id> returns 404 |
No Maintainer access, not a missing project | Ask for membership; GitLab hides projects instead of returning 403 |
glab ci lint fails on a template file |
Components with $[[ inputs ]] aren’t lintable |
Lint the consuming .gitlab-ci.yml instead |
stuck_pending_no_matching_runners on tart jobs |
macOS runner offline | Infra issue; report it, don’t change tags |
| Postgres service starts for every matrix entry | services: applies per job, not per matrix cell |
Include the component twice with different job_name |
Permission denied (publickey) on push from Claude |
1Password SSH agent locked, or port 22 instead of 22222 | Unlock 1Password; SSH URL is ssh://git@gitlab.appswithlove.net:22222/ |
dubious ownership in Claude jobs |
Image runs as claude, checkout owned by root |
Fixed in claude-code base job; update the include |
401 API key is invalid in Claude jobs |
OAuth token stored as ANTHROPIC_API_KEY |
Fixed in claude-code base job; token is re-exported as CLAUDE_CODE_OAUTH_TOKEN |
| Docker login fails | Missing registry vars | Check CI_REGISTRY_* variables |
| Version job skipped | Wrong branch | Only runs on main/release |
| Private npm 401/404 | Missing auth | Pass CI_JOB_TOKEN via BUILD_ARG |
| Trivy scan timeout | Large image / slow DB | Increase job timeout, check DB cache |
| Image pull takes 30–75 s per job | tags: [docker] forces pull_policy: always |
Switch to tags: [web-test] |
| Stale pipelines after force-push | Missing workflow: auto_cancel |
Add auto_cancel: on_new_commit: interruptible + default: interruptible: true |
| Same commit builds twice on MR push | Branch + MR pipelines both triggered | Add workflow: rules: to skip branch-when-MR-exists |
| Stacked MRs rerun every pipeline | Squash merge rewrites SHAs, next MR rebases | Disable squash in project settings (Settings → Merge requests) |
node_modules artifact upload dominates pipeline |
Install stage passing 40k files via artifacts | Drop install stage; cache .npm; npm ci --prefer-offline per job |
Best practices
Abschnitt betitelt „Best practices“- Read a reference project before writing pipeline config from memory.
- Use standard stages: test, build, deploy. Declare all stages in the project.
- Store secrets in GitLab CI/CD variables. Never copy a secret from one project into another; use
fetch-secretsor an instance variable. - Mark secrets masked and protected. Unmasked
MATCH_PASSWORDor keystore variables leak into job logs. - Deploy production only from a protected
productionbranch or a manual job. - Set
GIT_DEPTH: 1(andGIT_LFS_SKIP_SMUDGE: '1'if LFS isn’t needed). - Prefer
tags: [web-test]overtags: [docker]for Node/web jobs. - Add
workflow: auto_cancelanddefault: interruptible: true. - Never upload
node_modulesas job artifacts. Cache.npminstead. - Keep
.dockerignorein sync with what the build imports. Tests run on a full checkout and won’t notice a file the Docker build can’t see.
Reference
Abschnitt betitelt „Reference“Performance and optimization
Abschnitt betitelt „Performance and optimization“- Runners, workflow patterns, caching, anti-patterns. Read when a pipeline is slow, duplicates runs, or has the wrong runner tag.
Template configs
Abschnitt betitelt „Template configs“- Build templates: version, build-buildkit, build-docker, build-docker-simple, build-docker-multiarch, build-composer, build-dotnet, build-dotnet-migration, build-npm, publish-npm, npm-scripts
- Test templates: test-node, test-dotnet, test-php, test-trivy, test-renovate
- Dependency checks: depcheck-node, depcheck-composer, depcheck-dotnet
- Deploy templates: deploy-default-branches, deploy-service, deploy-autodeploy-app, deploy-grafana-dashboard, deploy-argocd-project, deploy-dma-docker-compose-stack, deploy
- Mobile templates: android, ios-fastlane, ios-lint
- Tracking and security: track-vulntracker, track-vulntracker-upload
- Utilities: git-auto-commit, fetch-secrets, claude-code, claude-code-review, claude-code-autocommit, claude-code-manual, CI/CD steps
- Application templates: app-payload, app-statamic, migros-web
Pipeline examples
Abschnitt betitelt „Pipeline examples“- Node.js apps: web app, SvelteKit, full-stack, minimal, NPM publish, Migros
- .NET apps: API, EF migrations
- PHP apps: Laravel, Statamic, Payload CMS
- Deployment: mono-repo, DMA compose, ArgoCD, branch-specific
- Security: Trivy scanning, SBOM tracking
- Utilities: Claude Code review, git auto-commit

