Template
Advanced Security & Review Pipeline
Conditional security scanning with rules:exists, include:rules, retry on exit codes, and environment stop jobs.
Best for projects with compliance scanning, code review apps, and granular conditional includes.
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_BRANCH =~ /^release\//'
stages:
- security-scan
- build
- review
- cleanup
variables:
SCAN_FAILURE_ACTION: warn
# Include security templates only when relevant files change (GitLab 16.4+)
include:
- local: security/sast.yml
rules:
- exists:
- "**/*.js"
- "**/*.ts"
when: always
- when: never
- local: security/dependency-scan.yml
rules:
- exists:
- "package.json"
- "package-lock.json"
when: always
- when: never
# SAST job with retry on specific exit codes (GitLab 17.5+)
sast_scan:
stage: security-scan
image: registry.example.com/security/sast:latest
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
exit_codes:
- 137
- 139
script:
- sast-scanner --output gl-sast-report.json --severity high
artifacts:
reports:
sast: gl-sast-report.json
when: always
# Build with custom cache strategy (GitLab 18.2+)
build_app:
stage: build
image: node:22-alpine
needs:
- sast_scan
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- .npm/
- node_modules/
strategy: mirror
script:
- npm ci --cache .npm --prefer-offline
- npm run build
artifacts:
paths:
- dist/
# Review app deployed from merge request pipeline
review_app:
stage: review
image: alpine:3.21
needs:
- build_app
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: manual
allow_failure: false
environment:
name: review/$CI_MERGE_REQUEST_IID
url: https://review-$CI_MERGE_REQUEST_IID.app.example.com
auto_stop_in: 2 days
on_stop: stop_review
script:
- echo "Deploying review app for MR !$CI_MERGE_REQUEST_IID"
- echo "URL: https://review-$CI_MERGE_REQUEST_IID.app.example.com"
# Environment stop trigger (GitLab 18.3+)
stop_review:
stage: cleanup
image: alpine:3.21
needs:
- review_app
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: manual
environment:
name: review/$CI_MERGE_REQUEST_IID
action: stop
script:
- echo "Tearing down review app for MR !$CI_MERGE_REQUEST_IID"
# Production deploy with variables in publish (GitLab 17.9+)
deploy_prod:
stage: cleanup
image: alpine:3.21
needs:
- build_app
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
environment:
name: production
url: https://app.example.com
script:
- echo "Deploying to production"
- echo "Version: $CI_COMMIT_TAG"
release:
tag_name: $CI_COMMIT_TAG
name: "Release $CI_COMMIT_TAG"
description: "./CHANGELOG.md"
assets:
links:
- name: "Build Artifacts"
url: "$CI_JOB_URL/artifacts/download"