
Gitlab Ci Generator
- 397 installs
- 286 repo stars
- Updated July 26, 2026
- akin-ozer/cc-devops-skills
gitlab-ci-generator is a Claude Code DevOps skill that scaffolds validated .gitlab-ci.yml pipelines with build, test, security scan, and deploy stages using four starter templates and three complexity modes aligned to Gi
About
gitlab-ci-generator is an akin-ozer/cc-devops-skills skill for production-ready GitLab CI/CD pipeline generation. Route requests across three modes: Targeted (snippet or review), Lightweight (simple single-file pipelines), and Full (multi-environment deploys, includes, advanced rules, SAST). Mandatory pre-generation loads tiered references—best-practices.md, common-patterns.md, security-guidelines.md—and picks from four templates: basic-pipeline.yml, docker-build.yml, kubernetes-deploy.yml, and multi-project.yml. Generated pipelines pass validation through the companion gitlab-ci-validator skill with Critical/High gates before delivery. Supports Docker build and push, Kubernetes deploy with kubectl, parent-child multi-project triggers, reusable extends/anchors, and GitLab SAST/Dependency-Scanning template includes. Invoke when asked to create .gitlab-ci.yml, add Docker or K8s deploy jobs, or include security scanning templates.
- .gitlab-ci.yml stage scaffolding
- Runner tag and image selection
- Cache and artifact job patterns
- Deploy environment promotion templates
Gitlab Ci Generator by the numbers
- 397 all-time installs (skills.sh)
- Ranked #302 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akin-ozer/cc-devops-skills --skill gitlab-ci-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 397 |
|---|---|
| repo stars | ★ 286 |
| Last updated | July 26, 2026 |
| Repository | akin-ozer/cc-devops-skills ↗ |
How do you generate a GitLab CI pipeline yaml?
Generate .gitlab-ci.yml pipelines with stages for build, test, security scan, and deploy aligned to GitLab runners and project layout.
Who is it for?
DevOps engineers scaffolding GitLab CI/CD for Node, Python, Java, Docker, or Kubernetes projects who need validated pipelines with security scanning.
Skip if: Skip gitlab-ci-generator for GitHub Actions, Jenkins Groovyfiles, or GitLab runtime troubleshooting when an existing pipeline only needs a one-line fix.
When should I use this skill?
User asks to create .gitlab-ci.yml, build a GitLab pipeline, add Docker/Kubernetes deploy jobs, or include SAST templates in GitLab CI.
What you get
Validated .gitlab-ci.yml with staged jobs, security templates, deploy configuration, and documented template foundation notes.
- .gitlab-ci.yml pipeline file
- Validation status report
By the numbers
- Four pipeline templates: basic-pipeline, docker-build, kubernetes-deploy, multi-project
- Three generation modes: Targeted, Lightweight, and Full complexity routing
Files
GitLab CI/CD Pipeline Generator
Overview
Generate production-ready GitLab CI/CD pipeline configurations following current best practices, security standards, and naming conventions. All generated resources are automatically validated using the devops-skills:gitlab-ci-validator skill to ensure syntax correctness and compliance with best practices.
---
Trigger Phrases
Use this skill when the user asks for GitLab CI/CD generation requests such as:
- "Create a
.gitlab-ci.ymlfor..." - "Build a GitLab pipeline for Node/Python/Java..."
- "Add Docker build and deploy jobs in GitLab CI"
- "Set up GitLab parent-child or multi-project pipelines"
- "Include SAST/dependency scanning templates in GitLab CI"
Execution Model
Follow this deterministic flow in order: 1. Classify request complexity (targeted, lightweight, or full). 2. Load only the required reference tier for that complexity. 3. Output the matching response profile for the selected mode. 4. For complete pipeline generation, start from the closest template and customize. 5. Validate complete pipelines with strict Critical/High gates. 6. Present output with validation status and template/version notes.
If tooling is unavailable, use the documented fallback branch and report it explicitly.
Mode Routing (Quick Decision)
| Request shape | Mode | Required references | Output profile |
|---|---|---|---|
| Simple single-file pipeline with common jobs/stages and low risk | Lightweight | Tier 1 (+ Tier 2 only if needed) | Lightweight confirmation + compact final sections |
Multi-environment deploy, advanced rules, includes/templates, security/compliance-sensitive workflow, or unclear/risky requirement | Full | Tier 1 + Tier 2 (Tier 3 only if needed) | Full confirmation + full final sections |
| Review/Q&A/snippet/focused fix (not full file generation) | Targeted | Only directly relevant files | Concise targeted response (no full boilerplate) |
When uncertain on a complete-generation request, route to Full mode.
MANDATORY PRE-GENERATION STEPS
CRITICAL: Before generating any complete GitLab CI/CD pipeline, complete these steps.
Step 1: Classify Complexity (REQUIRED)
| Mode | Use When | Minimum Confirmation |
|---|---|---|
| Targeted | Review/Q&A/snippet/focused fix where full pipeline generation is not requested | Concise targeted response |
| Lightweight | Simple single-file pipeline, common stages/jobs, no advanced GitLab features, no sensitive deploy/security customization | Lightweight confirmation |
| Full | Multi-environment deploys, includes/templates, advanced rules logic, security scanning customization, compliance-sensitive workflows, or any unclear/risky request | Full confirmation |
When uncertain on a complete-generation request, default to Full mode.
Step 2: Load References by Tier (REQUIRED)
Use an open/read action to load references based on the selected mode.
Targeted mode (review/Q&A/snippet/focused fix):
- Load only directly relevant references/templates for the scoped request.
- Do not enforce Full-generation Tier 1/Tier 2 checklist items.
Tier 1 (Required for complete pipeline generation in Lightweight and Full modes): 1. references/best-practices.md - baseline security, performance, naming 2. references/common-patterns.md - starting pattern selection 3. Matching template from assets/templates/:
- Docker pipelines ->
assets/templates/docker-build.yml - Kubernetes deployments ->
assets/templates/kubernetes-deploy.yml - Multi-project pipelines ->
assets/templates/multi-project.yml - Basic pipelines ->
assets/templates/basic-pipeline.yml
Tier 2 (Required for Full mode; optional for Lightweight mode): 1. references/gitlab-ci-reference.md - keyword/syntax edge cases 2. references/security-guidelines.md - security-sensitive controls
Tier 3 (Conditional external docs lookup):
- Use only when local references do not cover requested features or version-specific behavior.
- Follow the lookup flow in "Handling GitLab CI/CD Documentation Lookup."
If a required local reference or template is unavailable:
- Report the exact missing path.
- Continue with available references and mark assumptions explicitly.
- Do not claim production-ready confidence until missing critical inputs are resolved.
Step 3: Confirm Understanding (EXPLICIT OUTPUT REQUIRED)
Lightweight Confirmation Mode
Use for simple requests only.
Required format:
## Reference Analysis Complete (Lightweight)
**Pattern:** [Pattern name] from common-patterns.md
**Template:** [Template file]
**Key standards to enforce:**
- [2-3 concrete standards]Example:
## Reference Analysis Complete (Lightweight)
**Pattern:** Basic Build-Test-Deploy from common-patterns.md
**Template:** assets/templates/basic-pipeline.yml
**Key standards to enforce:**
- Pin runtime image versions (no `:latest`)
- Add explicit job timeouts
- Use `rules` instead of deprecated `only`/`except`Full Confirmation Mode
Use for complex or security-sensitive requests.
Required format:
## Reference Analysis Complete (Full)
**Pipeline Pattern Identified:** [Pattern name] from common-patterns.md
- [Brief description of why this pattern fits]
**Best Practices to Apply:**
- [List 3-5 key best practices relevant to this pipeline]
**Security Guidelines:**
- [List security measures to implement]
**Template Foundation:** [Template file name]
- [What will be customized from this template]Example:
## Reference Analysis Complete (Full)
**Pipeline Pattern Identified:** Docker Build + Kubernetes Deployment from common-patterns.md
- User needs containerized deployment to K8s clusters with staging/production environments
**Best Practices to Apply:**
- Pin all Docker images to specific versions (not `:latest`)
- Use caching for pip dependencies
- Implement DAG optimization with `needs` keyword
- Set explicit timeout on all jobs (15-20 minutes)
- Use `resource_group` for deployment jobs
**Security Guidelines:**
- Use masked CI/CD variables for secrets (KUBE_CONTEXT, registry credentials)
- Include container scanning with Trivy
- Never expose secrets in logs
**Template Foundation:** assets/templates/docker-build.yml + assets/templates/kubernetes-deploy.yml
- Combine Docker build pattern with K8s kubectl deployment
- Add Python-specific test jobsSkipping confirmation is not allowed for complete pipeline generation.
---
Core Capabilities
1. Generate Basic CI/CD Pipelines
Create complete, production-ready .gitlab-ci.yml files with proper structure, security best practices, and efficient CI/CD patterns.
When to use:
- User requests: "Create a GitLab pipeline for...", "Build a CI/CD pipeline...", "Generate GitLab CI config..."
- Scenarios: CI/CD pipelines, automated testing, build automation, deployment pipelines
Process: 1. Understand the user's requirements (what needs to be automated) 2. Identify stages, jobs, dependencies, and artifacts 3. Use assets/templates/basic-pipeline.yml as structural foundation 4. Reference references/best-practices.md for implementation patterns 5. Reference references/common-patterns.md for standard pipeline patterns 6. Generate the pipeline following these principles:
- Use semantic stage and job names
- Pin Docker images to specific versions (not :latest)
- Implement proper secrets management with masked variables
- Use caching for dependencies to improve performance
- Implement proper artifact handling with expiration
- Use
needskeyword for DAG optimization when appropriate - Add proper error handling with retry and allow_failure
- Use
rulesinstead of deprecated only/except - Set explicit `timeout` for all jobs (10-30 minutes typically)
- Add meaningful job descriptions in comments
7. ALWAYS validate the generated pipeline using the devops-skills:gitlab-ci-validator skill 8. If validation fails, fix the issues and re-validate
Example structure:
# Basic CI/CD Pipeline
# Builds, tests, and deploys the application
stages:
- build
- test
- deploy
# Global variables
variables:
NODE_VERSION: "20"
DOCKER_DRIVER: overlay2
# Default settings for all jobs
default:
image: node:20-alpine
timeout: 20 minutes # Default timeout for all jobs
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- echo "Starting job ${CI_JOB_NAME}"
tags:
- docker
interruptible: true
# Build stage - Compiles the application
build-application:
stage: build
timeout: 15 minutes
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
rules:
- changes:
- src/**/*
- package*.json
when: always
- when: on_success
# Test stage
test-unit:
stage: test
needs: [build-application]
script:
- npm run test:unit
coverage: '/Coverage: \d+\.\d+%/'
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
test-lint:
stage: test
needs: [] # Can run immediately
script:
- npm run lint
allow_failure: true
# Deploy stage
deploy-staging:
stage: deploy
needs: [build-application, test-unit]
script:
- npm run deploy:staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: manual
deploy-production:
stage: deploy
needs: [build-application, test-unit]
script:
- npm run deploy:production
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
resource_group: production2. Generate Docker Build Pipelines
Create pipelines for building, testing, and pushing Docker images to container registries.
When to use:
- User requests: "Create a Docker build pipeline...", "Build and push Docker images..."
- Scenarios: Container builds, multi-stage Docker builds, registry pushes
Process: 1. Understand the Docker build requirements (base images, registries, tags) 2. Use assets/templates/docker-build.yml as foundation 3. Implement Docker-in-Docker or Kaniko for builds 4. Configure registry authentication 5. Implement image tagging strategy 6. Add security scanning if needed 7. ALWAYS validate using devops-skills:gitlab-ci-validator skill
Example:
stages:
- build
- scan
- push
variables:
DOCKER_DRIVER: overlay2
IMAGE_NAME: $CI_REGISTRY_IMAGE
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
# Build Docker image
docker-build:
stage: build
image: docker:24-dind
timeout: 20 minutes
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build
--cache-from $IMAGE_NAME:latest
--tag $IMAGE_NAME:$IMAGE_TAG
--tag $IMAGE_NAME:latest
.
- docker push $IMAGE_NAME:$IMAGE_TAG
- docker push $IMAGE_NAME:latest
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
retry:
max: 2
when:
- runner_system_failure
# Scan for vulnerabilities
container-scan:
stage: scan
image: aquasec/trivy:0.49.0
timeout: 15 minutes
script:
- trivy image --exit-code 0 --severity HIGH,CRITICAL $IMAGE_NAME:$IMAGE_TAG
needs: [docker-build]
allow_failure: true
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH3. Generate Kubernetes Deployment Pipelines
Create pipelines that deploy applications to Kubernetes clusters.
When to use:
- User requests: "Deploy to Kubernetes...", "Create K8s deployment pipeline..."
- Scenarios: Kubernetes deployments, Helm deployments, kubectl operations
Process: 1. Identify the Kubernetes deployment method (kubectl, Helm, Kustomize) 2. Use assets/templates/kubernetes-deploy.yml as foundation 3. Configure cluster authentication (service accounts, kubeconfig) 4. Implement proper environment management 5. Add rollback capabilities 6. ALWAYS validate using devops-skills:gitlab-ci-validator skill
Example:
stages:
- build
- deploy
# Kubernetes deployment job
deploy-k8s:
stage: deploy
image: bitnami/kubectl:1.29
timeout: 10 minutes
before_script:
- kubectl config use-context $KUBE_CONTEXT
script:
- kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -n $KUBE_NAMESPACE
- kubectl rollout status deployment/myapp -n $KUBE_NAMESPACE --timeout=5m
environment:
name: production
url: https://example.com
kubernetes:
namespace: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
resource_group: k8s-production
retry:
max: 2
when:
- runner_system_failure4. Generate Multi-Project Pipelines
Create pipelines that trigger other projects or use parent-child pipeline patterns.
When to use:
- User requests: "Create multi-project pipeline...", "Trigger other pipelines..."
- Scenarios: Monorepos, microservices, orchestration pipelines
Process: 1. Identify the pipeline orchestration needs 2. Use assets/templates/multi-project.yml or parent-child templates 3. Configure proper artifact passing 4. Implement parallel execution where appropriate 5. ALWAYS validate using devops-skills:gitlab-ci-validator skill
Example (Parent-Child):
# Parent pipeline
stages:
- trigger
generate-child-pipeline:
stage: trigger
script:
- echo "Generating child pipeline config"
- |
cat > child-pipeline.yml <<EOF
stages:
- build
child-job:
stage: build
script:
- echo "Running child job"
EOF
artifacts:
paths:
- child-pipeline.yml
trigger-child:
stage: trigger
trigger:
include:
- artifact: child-pipeline.yml
job: generate-child-pipeline
strategy: depend
needs: [generate-child-pipeline]5. Generate Template-Based Configurations
Create reusable templates using extends, YAML anchors, and includes.
When to use:
- User requests: "Create reusable templates...", "Build modular pipeline config..."
- Scenarios: Template libraries, DRY configurations, shared CI/CD logic
Process: 1. Identify common patterns to extract 2. Create hidden jobs (prefixed with .) 3. Use extends keyword for inheritance 4. Organize into separate files with include 5. ALWAYS validate using devops-skills:gitlab-ci-validator skill
Example:
# Hidden template jobs (include timeout in templates)
.node-template:
image: node:20-alpine
timeout: 15 minutes # Default timeout for jobs using this template
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- npm ci
interruptible: true
.deploy-template:
timeout: 10 minutes # Deploy jobs should have explicit timeout
before_script:
- echo "Deploying to ${ENVIRONMENT}"
after_script:
- echo "Deployment complete"
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
interruptible: false # Deploys should not be interrupted
# Actual jobs using templates
build:
extends: .node-template
stage: build
script:
- npm run build
deploy-staging:
extends: .deploy-template
stage: deploy
variables:
ENVIRONMENT: staging
script:
- ./deploy.sh staging
resource_group: staging6. Handling GitLab CI/CD Documentation Lookup
Use this flow only when local references do not cover requested features or version-sensitive behavior.
Detection:
- User mentions specific GitLab features (e.g., "Auto DevOps", "SAST", "dependency scanning")
- User requests integration with GitLab templates
- Pipeline requires specific GitLab runner features
Process:
1. Identify the feature:
- Extract the GitLab feature or template name
- Determine if version-specific information is needed
2. Check local references first (Tier 1/Tier 2):
references/common-patterns.mdreferences/gitlab-ci-reference.mdreferences/security-guidelines.md
3. Use Context7 first when external lookup is needed:
- Resolve library:
mcp__context7__resolve-library-id - Query docs:
mcp__context7__query-docs - Prefer GitLab official/library docs over secondary sources
4. Fallback to web search when Context7 is unavailable or insufficient:
- Use
web.search_query - Query pattern:
"GitLab CI/CD [feature] documentation" - Prefer results from
docs.gitlab.com
5. Open and extract from specific docs pages when needed:
- Use
web.openfor selected documentation pages - Capture required syntax, variables, and version constraints
6. Analyze discovered documentation for:
- Current recommended approach
- Required variables and configuration
- Template include syntax
- Best practices and security recommendations
- Example usage
7. If network tools are unavailable (offline/constrained environment):
- Continue using local references only
- State that external version verification could not be performed
- Add a version-assumption note in the final output
8. Generate pipeline using discovered information:
- Use correct template include syntax
- Configure required variables
- Add security best practices
- Include comments about versions and choices
Example with GitLab templates:
# Include GitLab's security templates (use Jobs/ prefix for current templates)
include:
- template: Jobs/SAST.gitlab-ci.yml
- template: Jobs/Dependency-Scanning.gitlab-ci.yml
# Customize SAST behavior via global variables
# Note: Set variables globally rather than overriding template jobs
# to avoid validation issues with partial job definitions
variables:
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp, node_modules"
DS_EXCLUDED_PATHS: "node_modules, vendor"
SECURE_LOG_LEVEL: "info"Important: When using include with GitLab templates, the included jobs arefully defined in the template. If you need to customize them, prefer setting
variables globally rather than creating partial job overrides (which will fail
local validation because the validator cannot resolve the included template).
GitLab merges the configuration at runtime, but local validators only see
your .gitlab-ci.yml file.Validation Workflow
CRITICAL: Every generated GitLab CI/CD configuration MUST be validated before presenting to the user.
Validation Process
1. Primary validation path: after generating a complete pipeline, invoke the devops-skills:gitlab-ci-validator skill:
Skill: devops-skills:gitlab-ci-validator2. Script fallback path (if validator skill cannot be invoked):
PIPELINE_FILE="<generated-output-path>"- Set
PIPELINE_FILEto the exact generated file path (for example,pipelines/review.ymlor.gitlab-ci.yml). - Fail fast if that file does not exist:
if [[ ! -f "$PIPELINE_FILE" ]]; then
echo "ERROR: CI file not found: $PIPELINE_FILE" >&2
exit 1
fi # From repository root
bash devops-skills-plugin/skills/gitlab-ci-validator/scripts/validate_gitlab_ci.sh "$PIPELINE_FILE" # From skills/gitlab-ci-generator directory
bash ../gitlab-ci-validator/scripts/validate_gitlab_ci.sh "$PIPELINE_FILE"- If the script is not executable:
chmod +x devops-skills-plugin/skills/gitlab-ci-validator/scripts/validate_gitlab_ci.sh- Optional API lint fallback when GitLab project context is available:
jq --null-input --arg yaml "$(<"$PIPELINE_FILE")" '.content=$yaml' \
| curl --header "Content-Type: application/json" \
--url "https://gitlab.com/api/v4/projects/:id/ci/lint?include_merged_yaml=true" \
--data @-3. Manual fallback path (only if both primary and script paths are unavailable):
- Run manual checks for YAML validity, stage/job references, and obvious secret exposure.
- Mark output as
Validation status: Manual fallback (not fully verified). - Do not claim production-ready status if Critical/High risk cannot be confidently ruled out.
4. The validator skill/script checks:
- Validate YAML syntax
- Check GitLab CI/CD schema compliance
- Verify job references and dependencies
- Check for best practices violations
- Perform security scanning
- Report any errors, warnings, or issues
5. Analyze validation results and take action based on severity:
| Severity | Action Required |
|---|---|
| CRITICAL | MUST fix before presenting. Pipeline is broken or severely insecure. |
| HIGH | MUST fix before presenting. Significant security or functionality issues. |
| MEDIUM | SHOULD fix before presenting. Apply fixes or explain why not applicable. |
| LOW | MAY fix or acknowledge. Inform user of recommendations. |
| SUGGESTIONS | Review and apply if beneficial. No fix required. |
6. Fix-and-Revalidate Loop (MANDATORY for Critical/High issues):
While validation has CRITICAL or HIGH issues:
1. Edit the generated file to fix the issue
2. Re-run validation
3. Repeat until no CRITICAL or HIGH issues remain7. Before presenting to user, ensure:
- Zero CRITICAL issues
- Zero HIGH issues
- MEDIUM issues either fixed OR explained why they're acceptable
- LOW issues and suggestions acknowledged
8. When presenting the validated configuration:
- State validation status clearly
- State validation path used (skill, script fallback, or manual fallback)
- List any remaining MEDIUM/LOW issues with explanations
- Include template/version freshness notes
- Provide usage instructions
- Mention any trade-offs made
Critical/High gate is strict and never optional for production-ready claims.
Validation Pass Criteria
Pipeline is READY to present when:
- ✅ Validation path executed (validator skill or script fallback)
- ✅ Syntax validation: PASSED
- ✅ Security scan: No CRITICAL or HIGH issues
- ✅ Best practices: Reviewed (warnings acceptable with explanation)
Pipeline is NOT READY when:
- ❌ Any syntax errors exist
- ❌ Any CRITICAL security issues exist
- ❌ Any HIGH security issues exist
- ❌ Job references are broken
- ❌ Only manual fallback was used and Critical/High risks cannot be ruled out
When to Skip Validation
Only skip validation when:
- Generating partial code snippets (not complete files)
- Creating examples for documentation purposes
- User explicitly requests to skip validation
Handling MEDIUM Severity Issues (REQUIRED OUTPUT)
When the validator reports MEDIUM severity issues, you MUST either fix them OR explain why they're acceptable. This explanation is REQUIRED in your output.
Required format for MEDIUM issue handling:
## Validation Issues Addressed
### MEDIUM Severity Issues
| Issue | Status | Explanation |
|-------|--------|-------------|
| [Issue code] | Fixed/Acceptable | [Why it was fixed OR why it's acceptable] |Example MEDIUM issue explanations:
## Validation Issues Addressed
### MEDIUM Severity Issues
| Issue | Status | Explanation |
|-------|--------|-------------|
| `image-variable-no-digest` | Acceptable | Using `python:${PYTHON_VERSION}-alpine` allows flexible version management via CI/CD variables. The PYTHON_VERSION variable is controlled internally and pinned to "3.12". SHA digest pinning would require updating the digest with every image update, adding maintenance burden without significant security benefit for this use case. |
| `pip-without-hashes` | Acceptable | This pipeline installs well-known packages (pytest, flake8) from PyPI. Using `--require-hashes` would require maintaining hash files for all transitive dependencies. For internal CI/CD, the security trade-off is acceptable. For higher security environments, consider using a private PyPI mirror with verified packages. |
| `git-strategy-none` | Acceptable | The `stop-staging` and `rollback-production` jobs use `GIT_STRATEGY: none` because they only run kubectl commands that don't require source code. The scripts are inline in the YAML (not from the repo), so there's no risk of executing untrusted code. |When to FIX vs ACCEPT:
| Scenario | Action |
|---|---|
| Production/high-security environment | FIX the issue |
| Issue has simple fix with no downside | FIX the issue |
| Fix adds significant complexity | ACCEPT with explanation |
| Fix requires external changes (e.g., CI/CD variables) | ACCEPT with explanation |
| Issue is false positive for this context | ACCEPT with explanation |
Reviewing Suggestions (REQUIRED OUTPUT)
When the validator provides suggestions, you MUST briefly acknowledge them and explain whether they should be applied.
Required format:
## Validator Suggestions Review
| Suggestion | Recommendation | Reason |
|------------|----------------|--------|
| [suggestion] | Apply/Skip | [Why] |Example suggestions review:
## Validator Suggestions Review
| Suggestion | Recommendation | Reason |
|------------|----------------|--------|
| `missing-retry` on test jobs | Skip | Test jobs are deterministic and don't interact with external services. Retry would mask flaky tests rather than fail fast. |
| `parallel-opportunity` for test-unit | Apply if beneficial | Could be added if pytest supports sharding. Add `parallel: 3` with `pytest --shard=${CI_NODE_INDEX}/${CI_NODE_TOTAL}` if test suite is large enough to benefit. |
| `dag-optimization` for stop-staging | Skip | This job is manual and only runs on environment cleanup. DAG optimization wouldn't provide meaningful speedup. |
| `no-dependency-proxy` | Apply for production | Consider using `$CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` to avoid Docker Hub rate limits. Requires GitLab Premium. |
| `environment-no-url` for rollback | Skip | Rollback jobs don't deploy new versions, so a URL would be misleading. |
| `missing-coverage` for lint job | Skip | Linting doesn't produce coverage data. This is a false positive. |Template and Version Notes (REQUIRED OUTPUT)
After validation results, include a concise freshness note for templates and documentation assumptions.
Required format:
## Template and Version Notes
- **Template base:** [assets/templates/<file>.yml]
- **Template customization scope:** [what changed from template]
- **Version/doc basis:** [Context7, docs.gitlab.com, or local references only]
- **Freshness note:** [exact date checked, or "external lookup unavailable"]
- **Version-sensitive assumptions:** [if any]Example:
## Template and Version Notes
- **Template base:** assets/templates/docker-build.yml
- **Template customization scope:** Added unit-test stage and environment-specific deploy rules
- **Version/doc basis:** docs.gitlab.com include-template docs + local references
- **Freshness note:** Verified template syntax on 2026-02-28
- **Version-sensitive assumptions:** Uses `Jobs/SAST.gitlab-ci.yml` template pathUsage Instructions Template (REQUIRED OUTPUT)
After presenting the validated pipeline, you MUST provide usage instructions. This is NOT optional.
Required format:
## Usage Instructions
### Required CI/CD Variables
Configure these variables in **Settings → CI/CD → Variables**:
| Variable | Description | Masked | Protected |
|----------|-------------|--------|-----------|
| [VARIABLE_NAME] | [Description] | Yes/No | Yes/No |
### Setup Steps
1. [First setup step]
2. [Second setup step]
...
### Pipeline Behavior
- **On push to `develop`:** [What happens]
- **On push to `main`:** [What happens]
- **On tag `vX.Y.Z`:** [What happens]
### Customization
[Any customization notes]Example usage instructions:
## Usage Instructions
### Required CI/CD Variables
Configure these variables in **Settings → CI/CD → Variables**:
| Variable | Description | Masked | Protected |
|----------|-------------|--------|-----------|
| `KUBE_CONTEXT` | Kubernetes cluster context name | No | Yes |
| `KUBE_NAMESPACE_STAGING` | Staging namespace (default: staging) | No | No |
| `KUBE_NAMESPACE_PRODUCTION` | Production namespace (default: production) | No | Yes |
**Note:** `CI_REGISTRY_USER`, `CI_REGISTRY_PASSWORD`, and `CI_REGISTRY` are automatically provided by GitLab.
### Kubernetes Integration Setup
1. **Enable Kubernetes integration** in **Settings → Infrastructure → Kubernetes clusters**
2. **Add your cluster** using the agent-based or certificate-based method
3. **Create namespaces** for staging and production if they don't exist:kubectl create namespace staging kubectl create namespace production
4. **Ensure deployment exists** in the target namespaces before running the pipeline
### Pipeline Behavior
- **On push to `develop`:** Runs tests → builds Docker image → deploys to staging automatically
- **On push to `main`:** Runs tests → builds Docker image → manual deployment to production
- **On tag `vX.Y.Z`:** Runs tests → builds Docker image → manual deployment to production
### Customization
- Update `APP_NAME` variable to match your Kubernetes deployment name
- Modify environment URLs in `deploy-staging` and `deploy-production` jobs
- Add Helm deployment by uncommenting the Helm jobs in the templateBest Practices to Enforce
Reference references/best-practices.md for comprehensive guidelines. Key principles:
Mandatory Standards
1. Security First:
- Pin Docker images to specific versions (not :latest)
- Use masked variables for secrets ($CI_REGISTRY_PASSWORD should be masked)
- Never expose secrets in logs
- Validate inputs and sanitize variables
- Use protected variables for sensitive environments
2. Performance:
- Implement caching for dependencies (ALWAYS for npm, pip, maven, etc.)
- Use
needskeyword for DAG optimization (ALWAYS when jobs have dependencies) - Set artifact expiration to avoid storage bloat (ALWAYS set
expire_in) - Use
parallelexecution when applicable (only if test framework supports sharding) - Minimize unnecessary artifact passing (use
artifacts: falseinneedswhen not needed)
3. Reliability:
- Set explicit `timeout` for ALL jobs (prevents hanging jobs, typically 10-30 minutes)
- Even when using
defaultorextendsfor timeout inheritance, add explicittimeoutto each job - This improves readability and avoids validator warnings about missing timeout
- Example: A job using
.deploy-templateshould still havetimeout: 15 minutesexplicitly set - Add retry logic for flaky operations (network calls, external API interactions)
- Use
allow_failureappropriately for non-critical jobs (linting, optional scans) - Use
resource_groupfor deployment jobs (prevents concurrent deployments) - Add
interruptible: truefor test jobs (allows cancellation when new commits push)
4. Naming:
- Job names: Descriptive, kebab-case (e.g., "build-application", "test-unit")
- Stage names: Short, clear (e.g., "build", "test", "deploy")
- Variable names: UPPER_SNAKE_CASE for environment variables
- Environment names: lowercase (e.g., "production", "staging")
5. Configuration Organization:
- Use
extendsfor reusable configuration (PREFERRED over YAML anchors for GitLab CI) - Use
includefor modular pipeline files (organize large pipelines into multiple files) - Use
rulesinstead of deprecated only/except (ALWAYS) - Define
defaultsettings for common configurations (image, timeout, cache, tags) - Use YAML anchors only when necessary for complex repeated structures within a single file
- Note:
extendsis preferred because it provides better visualization in GitLab UI
6. Error Handling:
- Set appropriate timeout values (ALWAYS - prevents hanging jobs)
- Configure retry behavior for flaky operations (network calls, external APIs)
- Use
allow_failure: truefor non-blocking jobs (linting, optional scans) - Add cleanup steps with
after_scriptwhen needed (e.g., stopping test containers, cleanup) - Implement notification mechanisms when required (e.g., Slack integration for deployment failures)
Resources
References (Tiered Loading)
references/best-practices.md(Tier 1: required for all) - Comprehensive GitLab CI/CD best practices- Security patterns, performance optimization
- Pipeline design, configuration organization
- Common patterns and anti-patterns
- Use this: When implementing any GitLab CI/CD resource
references/common-patterns.md(Tier 1: required for all) - Frequently used pipeline patterns- Basic CI pipeline patterns
- Docker build and push patterns
- Deployment patterns (K8s, cloud platforms)
- Multi-project and parent-child patterns
- Use this: When selecting which pattern to use
references/gitlab-ci-reference.md(Tier 2: required for Full mode) - GitLab CI/CD YAML syntax reference- Complete keyword reference
- Job configuration options
- Rules and conditional execution
- Variables and environments
- Use this: For syntax and keyword details
references/security-guidelines.md(Tier 2: required for Full mode) - Security best practices- Secrets management
- Image security
- Script security
- Artifact security
- Use this: For security-sensitive configurations
Assets (Templates to Customize)
assets/templates/basic-pipeline.yml- Complete basic pipeline templateassets/templates/docker-build.yml- Docker build pipeline templateassets/templates/kubernetes-deploy.yml- Kubernetes deployment templateassets/templates/multi-project.yml- Multi-project orchestration template
How to use templates: 1. Copy the relevant template structure 2. Replace all [PLACEHOLDERS] with actual values 3. Customize logic based on user requirements 4. Remove unnecessary sections 5. Validate the result
Typical Workflow Example
User request: "Create a CI/CD pipeline for a Node.js app with testing and Docker deployment"
Process: 1. ✅ Understand requirements:
- Node.js application
- Run tests (unit, lint)
- Build Docker image
- Deploy to container registry
- Trigger on push and merge requests
2. ✅ Reference resources:
- Check
references/best-practices.mdfor pipeline structure - Check
references/common-patterns.mdfor Node.js + Docker pattern - Use
assets/templates/docker-build.ymlas base
3. ✅ Generate pipeline:
- Define stages (build, test, dockerize, deploy)
- Create build job with caching
- Create test jobs (unit, lint) with needs optimization
- Create Docker build job
- Add proper artifact management
- Pin Docker images to versions
- Include proper secrets handling
4. ✅ Validate:
- Invoke
devops-skills:gitlab-ci-validatorskill - Fix any reported issues
- Re-validate if needed
5. ✅ Present to user:
- Show validated pipeline
- Explain key sections
- Provide usage instructions
- Mention successful validation
Common Pipeline Patterns
Basic Three-Stage Pipeline
stages:
- build
- test
- deploy
build-job:
stage: build
script: make build
test-job:
stage: test
script: make test
deploy-job:
stage: deploy
script: make deploy
when: manualDAG Pipeline with Needs
stages:
- build
- test
- deploy
build-frontend:
stage: build
script: npm run build:frontend
build-backend:
stage: build
script: npm run build:backend
test-frontend:
stage: test
needs: [build-frontend]
script: npm test:frontend
test-backend:
stage: test
needs: [build-backend]
script: npm test:backend
deploy:
stage: deploy
needs: [test-frontend, test-backend]
script: make deployConditional Execution with Rules
deploy-staging:
script: deploy staging
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: always
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
deploy-production:
script: deploy production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
- when: neverMatrix Parallel Jobs
test:
parallel:
matrix:
- NODE_VERSION: ['18', '20', '22']
OS: ['ubuntu', 'alpine']
image: node:${NODE_VERSION}-${OS}
script:
- npm testError Messages and Troubleshooting
If devops-skills:gitlab-ci-validator reports errors:
1. Syntax errors: Fix YAML formatting, indentation, or structure 2. Job reference errors: Ensure referenced jobs exist in needs/dependencies 3. Stage errors: Verify all job stages are defined in stages list 4. Rule errors: Check rules syntax and variable references 5. Security warnings: Address hardcoded secrets and image pinning
If GitLab documentation is not found:
1. Try Context7 first: mcp__context7__resolve-library-id -> mcp__context7__query-docs 2. If needed, run web.search_query scoped to docs.gitlab.com 3. Open specific pages with web.open and extract only required syntax/variables 4. If offline, continue with local references and add version-assumption notes
---
PRE-DELIVERY CHECKLIST
MANDATORY: Before presenting ANY generated pipeline to the user, verify ALL items:
Mode and References
- [ ] Complexity mode selected (
Targeted,Lightweight, orFull) - [ ] For Targeted mode: only directly relevant files/references loaded
- [ ] For Lightweight/Full modes: read
references/best-practices.mdbefore generating - [ ] For Lightweight/Full modes: read
references/common-patterns.mdbefore generating - [ ] For Lightweight/Full modes: read appropriate template from
assets/templates/for the pipeline type - [ ] For Full mode: read
references/gitlab-ci-reference.md - [ ] For Full mode: read
references/security-guidelines.md - [ ] Output explicit confirmation statement for Lightweight/Full modes
Generation Standards Applied
- [ ] All Docker images pinned to specific versions (no
:latest) - [ ] All jobs have explicit
timeout(10-30 minutes typically) - [ ]
defaultblock includestimeoutif defined - [ ] Hidden templates (
.template-name) includetimeout - [ ] Caching configured for dependency installation
- [ ]
needskeyword used for DAG optimization where appropriate - [ ]
rulesused (not deprecatedonly/except) - [ ]
resource_groupconfigured for deployment jobs - [ ] Artifacts have
expire_inset - [ ] Secrets use masked CI/CD variables (not hardcoded)
Validation Completed
- [ ] Validation executed via
devops-skills:gitlab-ci-validatoror script fallback - [ ] Zero CRITICAL issues
- [ ] Zero HIGH issues
- [ ] MEDIUM issues addressed (fixed OR explained in output using required format)
- [ ] LOW issues acknowledged (listed in output)
- [ ] Suggestions reviewed (using required format)
- [ ] Re-validated after any fixes
- [ ] If only manual fallback was available: output marked as not fully verified
Presentation Ready
- [ ] Validation status stated clearly
- [ ] Validation path stated clearly (skill, script fallback, or manual fallback)
- [ ] MEDIUM/LOW issues explained (with table format)
- [ ] Suggestions review provided (with table format)
- [ ] Template and version notes provided (with required format)
- [ ] Usage instructions provided (with required sections)
- [ ] Key sections explained
If any checkbox is unchecked, DO NOT present the pipeline. Complete the missing steps first.
Required Output Sections
Use the smallest valid output profile for the selected mode.
Full mode (complete/complex pipeline): 1. Reference Analysis Complete (from Step 3) 2. Generated Pipeline (the .gitlab-ci.yml content) 3. Validation Results Summary (pass/fail status) 4. Validation Issues Addressed (MEDIUM issues table) 5. Validator Suggestions Review (suggestions table) 6. Template and Version Notes (template base + freshness/version assumptions) 7. Usage Instructions (variables, setup, behavior)
Lightweight mode (complete/simple pipeline): 1. Reference Analysis Complete (Lightweight) 2. Generated Pipeline 3. Validation Results Summary 4. Template and Version Notes 5. Usage Instructions
- Add Validation Issues Addressed only when MEDIUM issues exist.
- Add Validator Suggestions Review only when suggestions are present.
Targeted mode (review/Q&A/snippet/focused fix):
- Provide only the directly requested artifact/answer and a concise rationale.
- Include validation/fallback disclosure if validation was not run.
- Do not force full pipeline-generation sections.
Done Criteria
This skill execution is done when:
- Simple requests use Lightweight mode without unnecessary Tier 2 loading.
- Complex requests use Full mode with Tier 2 references and complete confirmation.
- Validation enforces strict Critical/High gates before production-ready claims.
- Output includes template/version freshness notes plus usage instructions.
- Any fallback path is explicit and does not hide verification gaps.
---
Summary
Always follow this sequence when generating GitLab CI/CD pipelines:
1. Classify Complexity - choose Targeted, Lightweight, or Full mode. 2. Load References - use tiered loading:
- For
Targetedmode, load only directly relevant files. - For
Lightweight/Fullmodes, load: references/best-practices.mdreferences/common-patterns.md- Plus the appropriate template from
assets/templates/ - For
Fullmode, also load: references/gitlab-ci-reference.mdreferences/security-guidelines.md
3. Confirm - Output targeted response or Lightweight/Full reference analysis as required by mode. 4. Generate - Use templates and follow standards (security, caching, naming, explicit timeout on ALL jobs). 5. Lookup Docs When Needed - Context7 first, then web.search_query/web.open, with offline fallback notes when constrained. 6. Validate - Use devops-skills:gitlab-ci-validator, script fallback if needed. 7. Fix - Resolve all Critical/High issues, address Medium issues. 8. Verify Checklist - Confirm all pre-delivery checklist items. 9. Present - Deliver output with validation summary, template/version notes, and usage instructions.
Generate GitLab CI/CD pipelines that are:
- ✅ Secure with pinned images and proper secrets handling
- ✅ Following current best practices and conventions
- ✅ Using proper configuration organization (extends, includes)
- ✅ Optimized for performance (caching, needs, DAG)
- ✅ Properly documented with usage instructions
- ✅ Validated with zero Critical/High issues
- ✅ Production-ready and maintainable
test-*.yml
temp-*.yml
# Basic GitLab CI/CD Pipeline Template
# A simple three-stage pipeline for building, testing, and deploying applications
#
# Usage: Copy this template and customize for your needs
# - Replace [PLACEHOLDERS] with actual values
# - Remove sections you don't need
# - Adjust image versions, commands, and paths
# - Validate with gitlab-ci-validator skill
stages:
- build
- test
- deploy
# Global variables (customize for your project)
variables:
# Language/framework version
NODE_VERSION: "20" # [PLACEHOLDER: Set your Node.js version]
# Build configuration
BUILD_DIR: "dist" # [PLACEHOLDER: Set your build output directory]
# Cache configuration
CACHE_VERSION: "v1" # Bump this to invalidate cache
# Default settings for all jobs
default:
# Default Docker image (customize based on your tech stack)
image: node:${NODE_VERSION}-alpine # [PLACEHOLDER: Set your base image]
# Cache configuration
cache:
key: ${CACHE_VERSION}-${CI_COMMIT_REF_SLUG}
paths:
- node_modules/ # [PLACEHOLDER: Set your dependency directory]
policy: pull # Jobs only pull cache by default
# Common setup commands
before_script:
- echo "Starting job ${CI_JOB_NAME} in stage ${CI_JOB_STAGE}"
- echo "Commit: ${CI_COMMIT_SHORT_SHA} on branch ${CI_COMMIT_BRANCH}"
# Runner tags (customize for your runners)
tags:
- docker # [PLACEHOLDER: Set your runner tags]
# Test jobs can be interrupted by newer pipelines
interruptible: true
# ============================================================================
# BUILD STAGE
# ============================================================================
build-application:
stage: build
# Override cache policy to push updated cache
cache:
key: ${CACHE_VERSION}-${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull-push # Pull existing, push updates
script:
# Install dependencies
- npm ci # [PLACEHOLDER: Replace with your install command]
# Run build
- npm run build # [PLACEHOLDER: Replace with your build command]
# Verify build output
- ls -la ${BUILD_DIR}
# Save build artifacts
artifacts:
paths:
- ${BUILD_DIR}/ # [PLACEHOLDER: Set your build output path]
exclude:
- "**/*.map" # [PLACEHOLDER: Exclude unnecessary files]
expire_in: 1 hour # Short expiration for intermediate artifacts
when: on_success
# Run on changes to source files or dependencies
rules:
- changes:
- src/**/* # [PLACEHOLDER: Set your source directory]
- package*.json # [PLACEHOLDER: Set your dependency files]
when: always
- when: on_success
# ============================================================================
# TEST STAGE
# ============================================================================
test-unit:
stage: test
# Start immediately after build completes (DAG optimization)
needs: [build-application]
script:
# Install dependencies (from cache)
- npm ci
# Run unit tests
- npm run test:unit # [PLACEHOLDER: Replace with your test command]
# Code coverage reporting
coverage: '/Coverage: \d+\.\d+%/' # [PLACEHOLDER: Adjust regex for your coverage format]
# Test reports
artifacts:
when: always # Save reports even on failure
reports:
junit: junit.xml # [PLACEHOLDER: Set your test report path]
coverage_report:
coverage_format: cobertura # [PLACEHOLDER: Set your coverage format]
path: coverage/cobertura-coverage.xml
paths:
- coverage/
expire_in: 1 week
test-lint:
stage: test
# Can run immediately without waiting for build
needs: []
script:
- npm ci
- npm run lint # [PLACEHOLDER: Replace with your lint command]
# Allow linting to fail without blocking pipeline
allow_failure: true
test-integration:
stage: test
# Depends on build artifacts
needs: [build-application]
# Service dependencies (customize for your needs)
services:
- postgres:15-alpine # [PLACEHOLDER: Add/remove services as needed]
- redis:7-alpine
# Environment variables for services
variables:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
DATABASE_URL: postgres://testuser:testpass@postgres:5432/testdb
REDIS_URL: redis://redis:6379
script:
- npm ci
- npm run test:integration # [PLACEHOLDER: Replace with your integration test command]
# Retry on flaky network/service issues
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
- api_failure
# ============================================================================
# DEPLOY STAGE
# ============================================================================
deploy-staging:
stage: deploy
# Override interruptible for deployments
interruptible: false
# Depends on successful tests
needs:
- job: build-application
artifacts: true
- job: test-unit
artifacts: false
# Environment configuration
environment:
name: staging
url: https://staging.example.com # [PLACEHOLDER: Set your staging URL]
on_stop: stop-staging
auto_stop_in: 1 week
# Prevent concurrent deployments to same environment
resource_group: staging
script:
# [PLACEHOLDER: Add your deployment commands]
- echo "Deploying to staging environment"
- echo "Using build from ${BUILD_DIR}"
# Example: rsync, scp, kubectl, helm, etc.
# - rsync -avz ${BUILD_DIR}/ $STAGING_SERVER:/var/www/html/
# - kubectl apply -f k8s/staging/
# - helm upgrade --install myapp ./chart --namespace staging
# Only run on main/develop branch
rules:
- if: $CI_COMMIT_BRANCH == "develop" || $CI_COMMIT_BRANCH == "main"
when: on_success
- when: never
stop-staging:
stage: deploy
environment:
name: staging
action: stop
script:
# [PLACEHOLDER: Add cleanup commands]
- echo "Stopping staging environment"
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "develop" || $CI_COMMIT_BRANCH == "main"
when: manual
- when: never
deploy-production:
stage: deploy
# Cannot be interrupted
interruptible: false
# Depends on successful tests
needs:
- job: build-application
artifacts: true
- job: test-unit
artifacts: false
- job: test-integration
artifacts: false
# Production environment
environment:
name: production
url: https://example.com # [PLACEHOLDER: Set your production URL]
# Prevent concurrent production deployments
resource_group: production
script:
# [PLACEHOLDER: Add your production deployment commands]
- echo "Deploying to production environment"
- echo "Using build from ${BUILD_DIR}"
# Example deployment commands
# - rsync -avz ${BUILD_DIR}/ $PROD_SERVER:/var/www/html/
# - kubectl apply -f k8s/production/
# - helm upgrade --install myapp ./chart --namespace production
# Requires manual approval for production
when: manual
# Only run on main branch or version tags
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
- when: never
# ============================================================================
# CLEANUP (Optional)
# ============================================================================
cleanup:
stage: .post
script:
- echo "Running cleanup tasks"
# [PLACEHOLDER: Add cleanup commands if needed]
when: always
allow_failure: true# Docker Build and Push Pipeline Template
# Builds Docker images and pushes to GitLab Container Registry
#
# Usage: Copy this template and customize for your needs
# - Replace [PLACEHOLDERS] with actual values
# - Choose between Docker-in-Docker or Kaniko
# - Adjust image tags and build arguments
# - Validate with gitlab-ci-validator skill
stages:
- build
- scan
- push
# Global variables
variables:
# Docker configuration
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
# Image naming
IMAGE_NAME: $CI_REGISTRY_IMAGE
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
# Build configuration
DOCKERFILE_PATH: "Dockerfile" # [PLACEHOLDER: Set your Dockerfile path]
BUILD_CONTEXT: "." # [PLACEHOLDER: Set your build context]
# ============================================================================
# BUILD WITH DOCKER-IN-DOCKER
# ============================================================================
build-docker-dind:
stage: build
image: docker:24-dind
services:
- docker:24-dind
before_script:
# Login to GitLab Container Registry
- echo "Logging in to $CI_REGISTRY"
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
# Build Docker image with cache
- 'echo "Building Docker image: ${IMAGE_NAME}:${IMAGE_TAG}"'
- docker build
--cache-from ${IMAGE_NAME}:latest
--tag ${IMAGE_NAME}:${IMAGE_TAG}
--tag ${IMAGE_NAME}:latest
--build-arg VERSION=${CI_COMMIT_SHORT_SHA}
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
--file ${DOCKERFILE_PATH}
${BUILD_CONTEXT}
# Push images
- echo "Pushing images to registry"
- docker push ${IMAGE_NAME}:${IMAGE_TAG}
- docker push ${IMAGE_NAME}:latest
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: always
- if: $CI_MERGE_REQUEST_ID
when: manual
- when: never
# Retry on network issues
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
# ============================================================================
# BUILD WITH KANIKO (Rootless, more secure)
# ============================================================================
build-docker-kaniko:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.21.0-debug
entrypoint: [""]
script:
# Create Docker config for authentication
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
# Build and push with Kaniko
- /kaniko/executor
--context "${CI_PROJECT_DIR}/${BUILD_CONTEXT}"
--dockerfile "${CI_PROJECT_DIR}/${DOCKERFILE_PATH}"
--destination "${IMAGE_NAME}:${IMAGE_TAG}"
--destination "${IMAGE_NAME}:latest"
--cache=true
--cache-ttl=24h
--build-arg VERSION=${CI_COMMIT_SHORT_SHA}
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: always
- if: $CI_MERGE_REQUEST_ID
when: manual
- when: never
# ============================================================================
# MULTI-PLATFORM BUILD
# ============================================================================
build-multiarch:
stage: build
image: docker:24-dind
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
# Set up buildx for multi-platform builds
- docker buildx create --use --name multiarch-builder
- docker buildx inspect --bootstrap
script:
# Build for multiple platforms
- docker buildx build
--platform linux/amd64,linux/arm64
--tag ${IMAGE_NAME}:${IMAGE_TAG}
--tag ${IMAGE_NAME}:latest
--build-arg VERSION=${CI_COMMIT_SHORT_SHA}
--file ${DOCKERFILE_PATH}
--push
${BUILD_CONTEXT}
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
- when: never
# ============================================================================
# SECURITY SCANNING
# ============================================================================
scan-trivy:
stage: scan
image: aquasec/trivy:0.49.0
# Start after build completes
needs: [build-docker-dind]
script:
# Scan for vulnerabilities
- trivy image
--severity HIGH,CRITICAL
--exit-code 0
--format json
--output trivy-report.json
${IMAGE_NAME}:${IMAGE_TAG}
# Also output to console
- trivy image
--severity HIGH,CRITICAL
--exit-code 1
${IMAGE_NAME}:${IMAGE_TAG}
artifacts:
paths:
- trivy-report.json
expire_in: 1 week
when: always
# Allow to fail but warn
allow_failure: true
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: always
- when: never
# ============================================================================
# ALTERNATIVE: GITLAB CONTAINER SCANNING TEMPLATE
# ============================================================================
# To use GitLab's built-in container scanning, add the following at the ROOT
# level of your .gitlab-ci.yml file (NOT inside a job):
#
# include:
# - template: Security/Container-Scanning.gitlab-ci.yml
#
# Then override the container_scanning job:
#
# container_scanning:
# variables:
# CS_IMAGE: ${IMAGE_NAME}:${IMAGE_TAG}
# needs: [build-docker-dind]
# rules:
# - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# when: always
# - when: never
# ============================================================================
# TAG AND PUSH RELEASE
# ============================================================================
push-release:
stage: push
image: docker:24-dind
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
# Pull the image built from commit
- docker pull ${IMAGE_NAME}:${IMAGE_TAG}
# Tag with release version
- docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:${CI_COMMIT_TAG}
- docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:stable
# Push release tags
- docker push ${IMAGE_NAME}:${CI_COMMIT_TAG}
- docker push ${IMAGE_NAME}:stable
# Only run on version tags
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: on_success
- when: never
# Depends on successful security scan
needs: [build-docker-dind, scan-trivy]
# ============================================================================
# PUSH TO EXTERNAL REGISTRY (Optional)
# ============================================================================
push-dockerhub:
stage: push
image: docker:24-dind
services:
- docker:24-dind
before_script:
# Login to GitLab registry
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
# Login to Docker Hub (requires $DOCKERHUB_USER and $DOCKERHUB_TOKEN variables)
- docker login -u $DOCKERHUB_USER -p $DOCKERHUB_TOKEN
script:
# Pull from GitLab registry
- docker pull ${IMAGE_NAME}:${IMAGE_TAG}
# Tag for Docker Hub
- docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${DOCKERHUB_USER}/[IMAGE-NAME]:${CI_COMMIT_TAG}
- docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${DOCKERHUB_USER}/[IMAGE-NAME]:latest
# Push to Docker Hub
- docker push ${DOCKERHUB_USER}/[IMAGE-NAME]:${CI_COMMIT_TAG}
- docker push ${DOCKERHUB_USER}/[IMAGE-NAME]:latest
when: manual
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
- when: never
# ============================================================================
# EXAMPLE DOCKERFILE REFERENCE
# ============================================================================
#
# Multi-stage Dockerfile example:
#
# # Build stage
# FROM node:20-alpine AS builder
# WORKDIR /app
# COPY package*.json ./
# RUN npm ci --only=production
# COPY . .
# RUN npm run build
#
# # Production stage
# FROM node:20-alpine
#
# # Security: Create non-root user
# RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
#
# WORKDIR /app
#
# # Copy dependencies and build artifacts
# COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
# COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
# COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
#
# # Switch to non-root user
# USER nodejs
#
# # Metadata
# ARG VERSION
# ARG BUILD_DATE
# LABEL version="${VERSION}" \
# build-date="${BUILD_DATE}"
#
# EXPOSE 3000
#
# CMD ["node", "dist/server.js"]# Kubernetes Deployment Pipeline Template
# Deploys applications to Kubernetes clusters using kubectl, Helm, or Kustomize
#
# Usage: Copy this template and customize for your needs
# - Replace [PLACEHOLDERS] with actual values
# - Choose deployment method: kubectl, Helm, or Kustomize
# - Configure cluster authentication
# - Validate with gitlab-ci-validator skill
stages:
- build
- deploy
- verify
# Global variables
variables:
# Kubernetes configuration
KUBE_NAMESPACE: production # [PLACEHOLDER: Set your namespace]
KUBE_CONTEXT: "" # [PLACEHOLDER: Set if using specific context]
# Image configuration
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
IMAGE_NAME: $CI_REGISTRY_IMAGE
# Deployment configuration
APP_NAME: myapp # [PLACEHOLDER: Set your application name]
REPLICAS: "3" # [PLACEHOLDER: Set desired replicas]
# ============================================================================
# BUILD DOCKER IMAGE
# ============================================================================
build-image:
stage: build
image: docker:24-dind
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
- docker push ${IMAGE_NAME}:${IMAGE_TAG}
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
# ============================================================================
# DEPLOY WITH KUBECTL
# ============================================================================
deploy-kubectl-staging:
stage: deploy
image: bitnami/kubectl:1.29
before_script:
# Configure kubectl (using GitLab Kubernetes integration or kubeconfig)
- if [ -n "$KUBE_CONTEXT" ]; then kubectl config use-context $KUBE_CONTEXT; fi
- kubectl config set-context --current --namespace=$KUBE_NAMESPACE
# Verify connection
- kubectl cluster-info
- kubectl get nodes
script:
# Update deployment image
- kubectl set image deployment/${APP_NAME}
${APP_NAME}=${IMAGE_NAME}:${IMAGE_TAG}
-n $KUBE_NAMESPACE
--record
# Wait for rollout to complete
- kubectl rollout status deployment/${APP_NAME}
-n $KUBE_NAMESPACE
--timeout=5m
# Verify deployment
- kubectl get deployment ${APP_NAME} -n $KUBE_NAMESPACE
- kubectl get pods -n $KUBE_NAMESPACE -l app=${APP_NAME}
environment:
name: staging
url: https://staging.example.com # [PLACEHOLDER: Set your staging URL]
kubernetes:
namespace: $KUBE_NAMESPACE
resource_group: k8s-staging
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: always
- when: never
needs: [build-image]
deploy-kubectl-production:
stage: deploy
image: bitnami/kubectl:1.29
before_script:
- if [ -n "$KUBE_CONTEXT" ]; then kubectl config use-context $KUBE_CONTEXT; fi
- kubectl config set-context --current --namespace=$KUBE_NAMESPACE
script:
# Update deployment
- kubectl set image deployment/${APP_NAME}
${APP_NAME}=${IMAGE_NAME}:${IMAGE_TAG}
-n $KUBE_NAMESPACE
--record
# Wait for rollout
- kubectl rollout status deployment/${APP_NAME}
-n $KUBE_NAMESPACE
--timeout=10m
environment:
name: production
url: https://example.com # [PLACEHOLDER: Set your production URL]
kubernetes:
namespace: $KUBE_NAMESPACE
resource_group: k8s-production
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
- when: never
needs: [build-image]
# ============================================================================
# DEPLOY WITH HELM
# ============================================================================
deploy-helm-staging:
stage: deploy
image: alpine/helm:3.14.0
before_script:
- if [ -n "$KUBE_CONTEXT" ]; then kubectl config use-context $KUBE_CONTEXT; fi
- helm version
script:
# Add Helm repositories if needed
# - helm repo add myrepo https://charts.example.com
# - helm repo update
# Deploy or upgrade with Helm
- helm upgrade --install ${APP_NAME} ./helm/${APP_NAME}
--namespace $KUBE_NAMESPACE
--create-namespace
--set image.repository=${IMAGE_NAME}
--set image.tag=${IMAGE_TAG}
--set replicaCount=${REPLICAS}
--set environment=staging
--wait
--timeout 5m
--atomic # Rollback on failure
# Verify deployment
- helm list -n $KUBE_NAMESPACE
- helm status ${APP_NAME} -n $KUBE_NAMESPACE
environment:
name: staging
url: https://staging.example.com
kubernetes:
namespace: $KUBE_NAMESPACE
resource_group: k8s-staging
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: always
- when: never
needs: [build-image]
deploy-helm-production:
stage: deploy
image: alpine/helm:3.14.0
before_script:
- if [ -n "$KUBE_CONTEXT" ]; then kubectl config use-context $KUBE_CONTEXT; fi
- helm version
script:
- helm upgrade --install ${APP_NAME} ./helm/${APP_NAME}
--namespace $KUBE_NAMESPACE
--create-namespace
--set image.repository=${IMAGE_NAME}
--set image.tag=${IMAGE_TAG}
--set replicaCount=${REPLICAS}
--set environment=production
--wait
--timeout 10m
--atomic
environment:
name: production
url: https://example.com
kubernetes:
namespace: $KUBE_NAMESPACE
resource_group: k8s-production
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
- when: never
needs: [build-image]
# ============================================================================
# DEPLOY WITH KUSTOMIZE
# ============================================================================
deploy-kustomize-staging:
stage: deploy
image:
name: registry.k8s.io/kubectl:v1.29.1
entrypoint: [""]
variables:
KUSTOMIZE_DIR: k8s/overlays/staging # [PLACEHOLDER: Set your kustomize directory]
before_script:
- if [ -n "$KUBE_CONTEXT" ]; then kubectl config use-context $KUBE_CONTEXT; fi
script:
# Update image in kustomization
- cd ${KUSTOMIZE_DIR}
- kustomize edit set image ${APP_NAME}=${IMAGE_NAME}:${IMAGE_TAG}
# Apply kustomization
- kustomize build . | kubectl apply -f -
# Wait for rollout
- kubectl rollout status deployment/${APP_NAME} -n $KUBE_NAMESPACE --timeout=5m
environment:
name: staging
url: https://staging.example.com
kubernetes:
namespace: $KUBE_NAMESPACE
resource_group: k8s-staging
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: always
- when: never
needs: [build-image]
# ============================================================================
# DEPLOY WITH MANIFEST FILES
# ============================================================================
deploy-manifests:
stage: deploy
image: bitnami/kubectl:1.29
before_script:
- if [ -n "$KUBE_CONTEXT" ]; then kubectl config use-context $KUBE_CONTEXT; fi
script:
# Apply all manifests
- kubectl apply -f k8s/namespace.yaml
- kubectl apply -f k8s/configmap.yaml
- kubectl apply -f k8s/secret.yaml
- kubectl apply -f k8s/deployment.yaml
- kubectl apply -f k8s/service.yaml
- kubectl apply -f k8s/ingress.yaml
# Wait for deployment
- kubectl rollout status deployment/${APP_NAME} -n $KUBE_NAMESPACE
environment:
name: staging
kubernetes:
namespace: $KUBE_NAMESPACE
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: always
- when: never
needs: [build-image]
# ============================================================================
# ROLLBACK
# ============================================================================
rollback-kubectl:
stage: deploy
image: bitnami/kubectl:1.29
before_script:
- if [ -n "$KUBE_CONTEXT" ]; then kubectl config use-context $KUBE_CONTEXT; fi
script:
# Rollback to previous revision
- kubectl rollout undo deployment/${APP_NAME} -n $KUBE_NAMESPACE
- kubectl rollout status deployment/${APP_NAME} -n $KUBE_NAMESPACE
environment:
name: production
kubernetes:
namespace: $KUBE_NAMESPACE
when: manual
resource_group: k8s-production
rollback-helm:
stage: deploy
image: alpine/helm:3.14.0
before_script:
- if [ -n "$KUBE_CONTEXT" ]; then kubectl config use-context $KUBE_CONTEXT; fi
script:
# Rollback Helm release
- helm rollback ${APP_NAME} -n $KUBE_NAMESPACE
- helm status ${APP_NAME} -n $KUBE_NAMESPACE
environment:
name: production
kubernetes:
namespace: $KUBE_NAMESPACE
when: manual
resource_group: k8s-production
# ============================================================================
# VERIFY DEPLOYMENT
# ============================================================================
verify-deployment:
stage: verify
image: curlimages/curl:8.6.0
script:
# Wait for service to be ready
- sleep 30
# Health check
- curl -f https://staging.example.com/health || exit 1
# Smoke tests
- curl -f https://staging.example.com/api/status || exit 1
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: always
- when: never
needs: [deploy-kubectl-staging]
allow_failure: true
# ============================================================================
# EXAMPLE KUBERNETES MANIFESTS
# ============================================================================
#
# k8s/deployment.yaml:
#
# apiVersion: apps/v1
# kind: Deployment
# metadata:
# name: myapp
# namespace: production
# spec:
# replicas: 3
# selector:
# matchLabels:
# app: myapp
# template:
# metadata:
# labels:
# app: myapp
# spec:
# containers:
# - name: myapp
# image: registry.gitlab.com/group/project:latest
# ports:
# - containerPort: 3000
# env:
# - name: NODE_ENV
# value: "production"
# resources:
# requests:
# memory: "128Mi"
# cpu: "100m"
# limits:
# memory: "512Mi"
# cpu: "500m"
# livenessProbe:
# httpGet:
# path: /health
# port: 3000
# initialDelaySeconds: 30
# periodSeconds: 10
# readinessProbe:
# httpGet:
# path: /ready
# port: 3000
# initialDelaySeconds: 5
# periodSeconds: 5
#
# ---
# k8s/service.yaml:
#
# apiVersion: v1
# kind: Service
# metadata:
# name: myapp
# namespace: production
# spec:
# selector:
# app: myapp
# ports:
# - protocol: TCP
# port: 80
# targetPort: 3000
# type: ClusterIP# Multi-Project and Parent-Child Pipeline Template
# Orchestrates multiple projects or creates dynamic child pipelines
#
# Usage: Copy this template and customize for your needs
# - Replace [PLACEHOLDERS] with actual values
# - Choose pattern: multi-project trigger or parent-child
# - Configure dependencies and artifact passing
# - Validate with gitlab-ci-validator skill
# ============================================================================
# PATTERN 1: MULTI-PROJECT PIPELINES
# Trigger pipelines in other GitLab projects
# ============================================================================
stages:
- build
- trigger-downstream
- verify
variables:
# Version to pass to downstream projects
VERSION: $CI_COMMIT_SHORT_SHA
# Build the library/artifact
build-library:
stage: build
image: node:20-alpine
script:
- npm ci
- npm run build
- npm pack # Create npm package
artifacts:
paths:
- "*.tgz" # Package file
- dist/
expire_in: 1 hour
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Trigger multiple downstream projects in parallel
trigger-app-1:
stage: trigger-downstream
trigger:
project: group/application-1 # [PLACEHOLDER: Set downstream project]
branch: main
strategy: depend # Wait for completion
variables:
LIBRARY_VERSION: $VERSION
TRIGGER_SOURCE: library-update
needs: [build-library]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
trigger-app-2:
stage: trigger-downstream
trigger:
project: group/application-2 # [PLACEHOLDER: Set downstream project]
branch: main
strategy: depend
variables:
LIBRARY_VERSION: $VERSION
TRIGGER_SOURCE: library-update
needs: [build-library]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
trigger-app-3:
stage: trigger-downstream
trigger:
project: group/application-3 # [PLACEHOLDER: Set downstream project]
branch: main
strategy: depend
variables:
LIBRARY_VERSION: $VERSION
TRIGGER_SOURCE: library-update
needs: [build-library]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Verify all downstream projects succeeded
verify-downstream:
stage: verify
image: alpine:3.19
script:
- echo "All downstream projects completed successfully"
- echo "Library version $VERSION deployed"
needs:
- trigger-app-1
- trigger-app-2
- trigger-app-3
# ============================================================================
# PATTERN 2: PARENT-CHILD PIPELINES (STATIC)
# Include child pipeline configurations from repository
# ============================================================================
# Uncomment to use this pattern:
#
# trigger-frontend:
# stage: trigger-downstream
#
# trigger:
# include: frontend/.gitlab-ci.yml
# strategy: depend
#
# rules:
# - changes:
# - frontend/**/*
#
# trigger-backend:
# stage: trigger-downstream
#
# trigger:
# include: backend/.gitlab-ci.yml
# strategy: depend
#
# rules:
# - changes:
# - backend/**/*
#
# trigger-infrastructure:
# stage: trigger-downstream
#
# trigger:
# include: infrastructure/.gitlab-ci.yml
# strategy: depend
#
# rules:
# - changes:
# - infrastructure/**/*
# ============================================================================
# PATTERN 3: DYNAMIC CHILD PIPELINES
# Generate child pipeline configuration dynamically
# ============================================================================
# Uncomment to use this pattern:
#
# generate-child-pipeline:
# stage: build
#
# image: python:3.12-alpine
#
# script:
# # Generate pipeline configuration dynamically
# - python scripts/generate-pipeline.py > generated-pipeline.yml
# - cat generated-pipeline.yml # Show generated config
#
# artifacts:
# paths:
# - generated-pipeline.yml
# expire_in: 1 hour
#
# rules:
# - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
#
# trigger-generated-pipeline:
# stage: trigger-downstream
#
# trigger:
# include:
# - artifact: generated-pipeline.yml
# job: generate-child-pipeline
# strategy: depend
#
# needs: [generate-child-pipeline]
#
# rules:
# - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# ============================================================================
# PATTERN 4: MATRIX-BASED MULTI-PROJECT TRIGGERS
# Trigger multiple projects using matrix parallelization
# ============================================================================
# Uncomment to use this pattern:
#
# trigger-microservices:
# stage: trigger-downstream
#
# parallel:
# matrix:
# - PROJECT:
# - group/service-auth
# - group/service-users
# - group/service-orders
# - group/service-payments
# - group/service-notifications
#
# trigger:
# project: $PROJECT
# branch: main
# strategy: depend
#
# variables:
# DEPLOY_VERSION: $VERSION
# ENVIRONMENT: staging
#
# rules:
# - if: $CI_COMMIT_BRANCH == "develop"
# when: always
# - when: never
# ============================================================================
# PATTERN 5: CONDITIONAL DOWNSTREAM TRIGGERS
# Trigger different projects based on conditions
# ============================================================================
# Uncomment to use this pattern:
#
# trigger-staging-deploy:
# stage: trigger-downstream
#
# trigger:
# project: group/deployment-staging
# branch: main
#
# variables:
# SERVICE_VERSION: $VERSION
# ENVIRONMENT: staging
#
# rules:
# - if: $CI_COMMIT_BRANCH == "develop"
# when: always
# - when: never
#
# trigger-production-deploy:
# stage: trigger-downstream
#
# trigger:
# project: group/deployment-production
# branch: main
#
# variables:
# SERVICE_VERSION: $CI_COMMIT_TAG
# ENVIRONMENT: production
#
# rules:
# - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
# when: manual
# - when: never
# ============================================================================
# PATTERN 6: MONOREPO WITH CONDITIONAL CHILD PIPELINES
# For monorepos: only trigger child pipelines for changed components
# ============================================================================
# Uncomment to use this pattern:
#
# trigger-service-a:
# stage: trigger-downstream
#
# trigger:
# include: services/service-a/.gitlab-ci.yml
# strategy: depend
#
# rules:
# - changes:
# - services/service-a/**/*
# when: always
# - when: never
#
# trigger-service-b:
# stage: trigger-downstream
#
# trigger:
# include: services/service-b/.gitlab-ci.yml
# strategy: depend
#
# rules:
# - changes:
# - services/service-b/**/*
# when: always
# - when: never
#
# trigger-service-c:
# stage: trigger-downstream
#
# trigger:
# include: services/service-c/.gitlab-ci.yml
# strategy: depend
#
# rules:
# - changes:
# - services/service-c/**/*
# when: always
# - when: never
# ============================================================================
# EXAMPLE: PYTHON SCRIPT FOR DYNAMIC PIPELINE GENERATION
# ============================================================================
#
# scripts/generate-pipeline.py:
#
# #!/usr/bin/env python3
# import yaml
# import sys
#
# # Define services to deploy
# services = ['auth', 'users', 'orders', 'payments']
#
# # Generate pipeline configuration
# pipeline = {
# 'stages': ['deploy'],
# 'variables': {
# 'VERSION': '$CI_COMMIT_SHORT_SHA'
# }
# }
#
# # Add deploy job for each service
# for service in services:
# job_name = f'deploy-{service}'
# pipeline[job_name] = {
# 'stage': 'deploy',
# 'image': 'bitnami/kubectl:1.29',
# 'script': [
# f'kubectl set image deployment/{service} {service}=$IMAGE:$VERSION',
# f'kubectl rollout status deployment/{service}'
# ],
# 'environment': {
# 'name': f'{service}-production',
# 'kubernetes': {
# 'namespace': 'production'
# }
# }
# }
#
# # Output YAML
# print(yaml.dump(pipeline, default_flow_style=False))GitLab CI/CD Best Practices
This document outlines comprehensive best practices for creating production-ready, secure, and efficient GitLab CI/CD pipelines.
Table of Contents
1. Security Best Practices 2. Performance Optimization 3. Configuration Organization 4. Reliability and Error Handling 5. Naming Conventions 6. Pipeline Architecture 7. Common Anti-Patterns
---
Security Best Practices
1. Docker Image Pinning
Always pin Docker images to specific versions to ensure reproducibility and security.
# ❌ BAD: Using :latest tag
test-job:
image: node:latest
script: npm test
# ✅ GOOD: Pinned to specific version
test-job:
image: node:20.11-alpine3.19
script: npm testBest practices:
- Pin to major.minor.patch versions
- Use official images from trusted registries
- Regularly update pinned versions
- Document why specific versions are chosen
2. Secrets and Variables Management
Never hardcode secrets in your .gitlab-ci.yml file. Use GitLab CI/CD variables instead.
# ❌ BAD: Hardcoded credentials
deploy:
script:
- deploy --token abc123xyz
# ✅ GOOD: Using masked variables
deploy:
script:
- deploy --token $DEPLOY_TOKENBest practices:
- Mark sensitive variables as Masked and Protected
- Use project/group CI/CD variables for secrets
- Rotate secrets regularly
- Use
$CI_JOB_TOKENfor GitLab API operations - Limit variable scope to specific environments
3. Artifact Security
Be careful with artifact paths to avoid exposing sensitive files.
# ❌ BAD: Overly broad artifact paths
build:
artifacts:
paths:
- ./** # Exposes everything including .env files
# ✅ GOOD: Specific artifact paths
build:
artifacts:
paths:
- dist/
- build/
exclude:
- "**/*.env"
- "**/*.pem"
- "**/credentials.*"
expire_in: 1 hourBest practices:
- Be explicit about artifact paths
- Use
excludeto prevent sensitive files - Set appropriate expiration times
- Use
artifacts:reportsfor test/coverage reports - Don't include node_modules or vendor directories
4. Script Security
Avoid dangerous script patterns that can introduce security vulnerabilities.
# ❌ BAD: Dangerous patterns
install:
script:
- curl https://install.sh | bash # Pipe to bash
- eval "$COMMAND" # Code injection risk
- chmod 777 /app # Overly permissive
# ✅ GOOD: Secure patterns
install:
script:
- curl -o install.sh https://install.sh
- sha256sum -c install.sh.sha256 # Verify integrity
- bash install.shBest practices:
- Never pipe curl directly to bash
- Validate downloaded scripts
- Use minimal file permissions
- Sanitize user inputs
- Avoid
evaland similar dynamic execution
5. Protected Branches and Environments
Configure protected branches and environments for critical deployments.
deploy-production:
stage: deploy
script:
- deploy production
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TAG == null
when: manual
resource_group: productionBest practices:
- Require manual approval for production deployments
- Use protected environments
- Restrict who can deploy to production
- Use resource_group to prevent concurrent deployments
- Implement approval rules in GitLab
---
Performance Optimization
1. Caching Strategies
Use cache to speed up repeated operations like dependency installation.
# ✅ GOOD: Comprehensive caching
variables:
CACHE_VERSION: "v1" # Bump to invalidate cache
default:
cache:
key: ${CACHE_VERSION}-${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
policy: pull
build:
stage: build
script:
- npm ci
- npm run build
cache:
key: ${CACHE_VERSION}-${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
policy: pull-push # Push after installing
test:
stage: test
script:
- npm test
cache:
key: ${CACHE_VERSION}-${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull # Only pull, don't pushCache best practices:
- Use appropriate cache keys (branch, commit, files)
- Set
policy: pullfor jobs that only read cache - Set
policy: pull-pushfor jobs that update cache - Cache language-specific directories (node_modules/, vendor/, .gradle/)
- Use
CACHE_VERSIONvariable for cache invalidation - Don't cache build artifacts (use artifacts instead)
2. DAG Optimization with needs
Use the `needs` keyword to create Directed Acyclic Graphs for faster pipelines.
stages:
- build
- test
- deploy
# Without needs: runs sequentially (slow)
build-frontend:
stage: build
script: build frontend
build-backend:
stage: build
script: build backend
test-frontend:
stage: test
script: test frontend
test-backend:
stage: test
script: test backend
# ✅ With needs: runs in parallel (fast)
build-frontend:
stage: build
script: build frontend
build-backend:
stage: build
script: build backend
test-frontend:
stage: test
needs: [build-frontend] # Can start as soon as build-frontend finishes
script: test frontend
test-backend:
stage: test
needs: [build-backend] # Can start as soon as build-backend finishes
script: test backend
deploy:
stage: deploy
needs: [test-frontend, test-backend] # Only depends on tests
script: deployBenefits:
- Pipelines run faster by parallelizing independent jobs
- Reduces waiting time between stages
- Clear dependency visualization
3. Parallel Execution
Use parallel jobs for matrix testing or splitting workloads.
# Parallel with matrix
test:
parallel:
matrix:
- NODE_VERSION: ['18', '20', '22']
OS: ['ubuntu', 'alpine']
image: node:${NODE_VERSION}-${OS}
script:
- npm test
# Parallel with index
test-split:
parallel: 4
script:
- npm test -- --shard=${CI_NODE_INDEX}/${CI_NODE_TOTAL}4. Artifact Optimization
Minimize artifact size and set appropriate expiration.
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
exclude:
- dist/**/*.map # Exclude source maps if not needed
expire_in: 1 hour # Short expiration for intermediate artifacts
deploy:
stage: deploy
needs: [build]
script:
- deploy dist/Best practices:
- Set short expiration for intermediate artifacts (1 hour - 1 day)
- Set longer expiration for release artifacts (1 week - 1 month)
- Use
artifacts:reportsfor test/coverage reports - Exclude unnecessary files
- Compress large artifacts
---
Configuration Organization
1. Using extends for Reusability
Use `extends` to reduce duplication and create maintainable configurations.
# Hidden template jobs (prefixed with .)
.node-base:
image: node:20-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- npm ci
.deploy-base:
before_script:
- echo "Deploying to ${ENVIRONMENT}"
retry:
max: 2
when:
- runner_system_failure
resource_group: ${ENVIRONMENT}
# Actual jobs extending templates
build:
extends: .node-base
stage: build
script:
- npm run build
test:
extends: .node-base
stage: test
script:
- npm test
deploy-staging:
extends: .deploy-base
stage: deploy
variables:
ENVIRONMENT: staging
script:
- ./deploy.sh staging
deploy-production:
extends: .deploy-base
stage: deploy
variables:
ENVIRONMENT: production
script:
- ./deploy.sh production
when: manual2. Using include for Modular Configuration
Split large configurations into multiple files using include.
# .gitlab-ci.yml (main file)
include:
- local: '.gitlab/ci/templates.yml'
- local: '.gitlab/ci/build-jobs.yml'
- local: '.gitlab/ci/test-jobs.yml'
- local: '.gitlab/ci/deploy-jobs.yml'
stages:
- build
- test
- deploy
variables:
NODE_VERSION: "20"# .gitlab/ci/templates.yml
.node-base:
image: node:${NODE_VERSION}-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- npm ci3. Using YAML Anchors
Use YAML anchors for complex repeated structures within a file.
# Define anchor
.retry-config: &retry-config
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
# Use anchor
deploy-staging:
stage: deploy
<<: *retry-config
script:
- deploy staging
deploy-production:
stage: deploy
<<: *retry-config
script:
- deploy production4. Using default for Common Settings
Set default values for all jobs using the default keyword.
default:
image: node:20-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- echo "Starting job ${CI_JOB_NAME}"
retry:
max: 1
when:
- runner_system_failure
tags:
- docker
interruptible: true
# Jobs inherit default settings
build:
stage: build
script: npm run build
test:
stage: test
script: npm test---
Reliability and Error Handling
1. Retry Configuration
Configure retry for flaky operations to improve reliability.
# Retry on specific failures
test-integration:
script:
- npm run test:integration
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
- api_failure
# Conditional retry
deploy:
script:
- deploy.sh
retry:
max: 2
when: alwaysRetry scenarios:
- Network-dependent operations
- External API calls
- Integration tests
- Deployment operations
- Runner system failures
2. Timeout Settings
Set appropriate timeouts to prevent jobs from hanging.
# Global default timeout (project settings)
# Job-specific timeout
test-quick:
script: npm run test:unit
timeout: 10 minutes
test-e2e:
script: npm run test:e2e
timeout: 30 minutes
deploy:
script: deploy.sh
timeout: 15 minutes3. Allow Failure
Use `allow_failure` strategically for non-critical jobs.
# Job can fail without blocking pipeline
lint:
script: npm run lint
allow_failure: true
# Conditional allow_failure
test-experimental:
script: npm run test:experimental
allow_failure:
exit_codes: [1, 137]4. Interruptible Jobs
Mark test jobs as interruptible to save resources.
test:
script: npm test
interruptible: true # Can be canceled if new pipeline starts
deploy:
script: deploy.sh
interruptible: false # Should not be canceled5. After Script for Cleanup
Use `after_script` for cleanup operations that always run.
test:
script:
- npm test
after_script:
- echo "Cleaning up..."
- docker stop test-container || true
- rm -rf temp/---
Naming Conventions
Job Names
Use descriptive, action-oriented names in kebab-case.
# ✅ GOOD: Clear, descriptive names
build-frontend:
script: npm run build:frontend
test-unit:
script: npm run test:unit
test-integration:
script: npm run test:integration
deploy-staging:
script: deploy staging
# ❌ BAD: Vague names
job1:
script: npm build
job2:
script: npm testStage Names
Use short, standard stage names.
stages:
- build # ✅ Standard, clear
- test # ✅ Standard, clear
- deploy # ✅ Standard, clear
- .pre # ✅ GitLab special stage
- .post # ✅ GitLab special stageVariable Names
Use UPPER_SNAKE_CASE for variables.
variables:
NODE_VERSION: "20"
DOCKER_DRIVER: overlay2
CACHE_VERSION: "v1"
DEPLOY_ENVIRONMENT: stagingEnvironment Names
Use lowercase for environment names.
deploy-staging:
environment:
name: staging # ✅ lowercase
url: https://staging.example.com
deploy-production:
environment:
name: production # ✅ lowercase
url: https://example.com---
Pipeline Architecture
1. Basic Three-Stage Pipeline
Simple, linear pipeline for straightforward projects.
stages:
- build
- test
- deploy
build:
stage: build
script: make build
test:
stage: test
script: make test
deploy:
stage: deploy
script: make deploy
when: manualUse when:
- Simple projects with linear workflows
- Few dependencies between jobs
- Quick prototyping
2. DAG Pipeline with Needs
Optimized pipeline for complex projects with independent components.
stages:
- build
- test
- security
- deploy
build-frontend:
stage: build
script: build frontend
build-backend:
stage: build
script: build backend
test-frontend:
stage: test
needs: [build-frontend]
script: test frontend
test-backend:
stage: test
needs: [build-backend]
script: test backend
security-scan:
stage: security
needs: [] # Runs immediately
script: security scan
deploy:
stage: deploy
needs: [test-frontend, test-backend, security-scan]
script: deployUse when:
- Large projects with multiple components
- Need faster pipeline execution
- Clear dependencies between jobs
3. Parent-Child Pipelines
Hierarchical pipelines for monorepos or complex orchestration.
# Parent pipeline
stages:
- trigger
trigger-frontend:
stage: trigger
trigger:
include: frontend/.gitlab-ci.yml
strategy: depend
trigger-backend:
stage: trigger
trigger:
include: backend/.gitlab-ci.yml
strategy: dependUse when:
- Monorepo with multiple projects
- Need isolated pipeline configurations
- Complex orchestration scenarios
4. Multi-Project Pipelines
Cross-project orchestration triggering other projects.
trigger-downstream:
stage: deploy
trigger:
project: group/downstream-project
branch: main
strategy: dependUse when:
- Microservices deployment
- Library updates triggering dependent projects
- Complex multi-project workflows
---
Common Anti-Patterns
1. Using :latest Tag
# ❌ ANTI-PATTERN
test:
image: node:latest
script: npm test
# ✅ CORRECT
test:
image: node:20.11-alpine3.19
script: npm test2. Hardcoding Secrets
# ❌ ANTI-PATTERN
deploy:
script:
- deploy --api-key abc123xyz
# ✅ CORRECT
deploy:
script:
- deploy --api-key $API_KEY3. Using Deprecated only/except
# ❌ ANTI-PATTERN
deploy:
only:
- main
except:
- tags
# ✅ CORRECT
deploy:
rules:
- if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TAG == null4. Not Using Cache
# ❌ ANTI-PATTERN (installs dependencies every time)
test:
script:
- npm install
- npm test
# ✅ CORRECT (caches node_modules)
test:
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
script:
- npm ci
- npm test5. No Artifact Expiration
# ❌ ANTI-PATTERN (artifacts stored forever)
build:
artifacts:
paths:
- dist/
# ✅ CORRECT (artifacts expire)
build:
artifacts:
paths:
- dist/
expire_in: 1 hour6. Missing Resource Groups for Deployments
# ❌ ANTI-PATTERN (concurrent deployments possible)
deploy-production:
script: deploy production
# ✅ CORRECT (prevents concurrent deployments)
deploy-production:
script: deploy production
resource_group: production7. Overly Broad Artifact Paths
# ❌ ANTI-PATTERN
build:
artifacts:
paths:
- ./** # Includes everything
# ✅ CORRECT
build:
artifacts:
paths:
- dist/
- build/
exclude:
- "**/*.env"8. Not Using Needs for DAG Optimization
# ❌ ANTI-PATTERN (waits for all stage jobs)
stages:
- build
- test
build-frontend:
stage: build
script: build frontend
build-backend:
stage: build
script: build backend
test-frontend:
stage: test
script: test frontend # Waits for build-backend too
# ✅ CORRECT (starts as soon as build-frontend completes)
test-frontend:
stage: test
needs: [build-frontend]
script: test frontend---
Summary Checklist
When creating GitLab CI/CD pipelines, ensure:
- [ ] Docker images pinned to specific versions
- [ ] Secrets stored in masked CI/CD variables
- [ ] Cache configured for dependencies
- [ ] Artifacts have appropriate expiration times
- [ ]
needskeyword used for DAG optimization - [ ]
rulesused instead of deprecatedonly/except - [ ]
resource_groupused for deployment jobs - [ ]
interruptible: truefor test jobs - [ ] Retry configured for flaky operations
- [ ] Timeout set for long-running jobs
- [ ]
extendsorincludeused to reduce duplication - [ ] Descriptive job and stage names
- [ ] Cleanup operations in
after_script - [ ] Manual approval for production deployments
- [ ] Security scanning included in pipeline
---
Reference this document when generating or reviewing GitLab CI/CD pipelines to ensure best practices are followed.
GitLab CI/CD Common Patterns
This document provides ready-to-use patterns for common GitLab CI/CD scenarios. Use these patterns as starting points and customize them for your specific needs.
Table of Contents
1. Basic CI Pipeline Patterns 2. Docker Build and Push Patterns 3. Kubernetes Deployment Patterns 4. Testing Patterns 5. Deployment Patterns 6. Multi-Project Pipeline Patterns 7. Parent-Child Pipeline Patterns 8. Monorepo Patterns 9. Template and Reusability Patterns
---
Basic CI Pipeline Patterns
Pattern 1: Simple Three-Stage Pipeline
Use case: Basic projects with build, test, and deploy stages.
stages:
- build
- test
- deploy
variables:
NODE_VERSION: "20"
build-job:
stage: build
image: node:${NODE_VERSION}-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
test-job:
stage: test
image: node:${NODE_VERSION}-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull
script:
- npm ci
- npm test
deploy-job:
stage: deploy
image: alpine:3.19
script:
- apk add --no-cache rsync openssh
- rsync -avz dist/ $DEPLOY_SERVER:/var/www/html/
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manualPattern 2: Multi-Language Build Pipeline
Use case: Projects with multiple language components.
stages:
- build
- test
- deploy
build-frontend:
stage: build
image: node:20-alpine
script:
- cd frontend
- npm ci
- npm run build
artifacts:
paths:
- frontend/dist/
expire_in: 1 hour
build-backend:
stage: build
image: golang:1.22-alpine
script:
- cd backend
- go build -o app
artifacts:
paths:
- backend/app
expire_in: 1 hour
test-frontend:
stage: test
image: node:20-alpine
needs: [build-frontend]
script:
- cd frontend
- npm ci
- npm test
test-backend:
stage: test
image: golang:1.22-alpine
needs: [build-backend]
script:
- cd backend
- go test ./...---
Docker Build and Push Patterns
Pattern 1: Docker Build with Multi-Stage
Use case: Building and pushing Docker images to GitLab Container Registry.
stages:
- build
- push
variables:
DOCKER_DRIVER: overlay2
IMAGE_NAME: $CI_REGISTRY_IMAGE
DOCKER_TLS_CERTDIR: "/certs"
build-docker-image:
stage: build
image: docker:24-dind
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build
--cache-from $IMAGE_NAME:latest
--tag $IMAGE_NAME:$CI_COMMIT_SHORT_SHA
--tag $IMAGE_NAME:latest
--build-arg VERSION=$CI_COMMIT_SHORT_SHA
.
- docker push $IMAGE_NAME:$CI_COMMIT_SHORT_SHA
- docker push $IMAGE_NAME:latest
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Alternative: Build and push with tags
push-release-image:
stage: push
image: docker:24-dind
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build --tag $IMAGE_NAME:$CI_COMMIT_TAG .
- docker push $IMAGE_NAME:$CI_COMMIT_TAG
rules:
- if: $CI_COMMIT_TAGPattern 2: Docker Build with Kaniko (Rootless)
Use case: Building Docker images without Docker-in-Docker (more secure).
stages:
- build
variables:
IMAGE_NAME: $CI_REGISTRY_IMAGE
docker-build-kaniko:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.21.0-debug
entrypoint: [""]
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "${IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
--destination "${IMAGE_NAME}:latest"
--cache=true
--cache-ttl=24h
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHPattern 3: Multi-Platform Docker Build
Use case: Building Docker images for multiple architectures.
stages:
- build
variables:
DOCKER_DRIVER: overlay2
build-multiarch:
stage: build
image: docker:24-dind
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker buildx create --use
script:
- docker buildx build
--platform linux/amd64,linux/arm64
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
--tag $CI_REGISTRY_IMAGE:latest
--push
.
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH---
Kubernetes Deployment Patterns
Pattern 1: kubectl Deployment
Use case: Deploying to Kubernetes using kubectl.
stages:
- build
- deploy
variables:
KUBE_NAMESPACE: production
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
deploy-k8s:
stage: deploy
image: bitnami/kubectl:1.29
before_script:
- kubectl config use-context $KUBE_CONTEXT
- kubectl config set-context --current --namespace=$KUBE_NAMESPACE
script:
- kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$IMAGE_TAG
- kubectl rollout status deployment/myapp --timeout=5m
environment:
name: production
url: https://example.com
kubernetes:
namespace: $KUBE_NAMESPACE
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
resource_group: k8s-productionPattern 2: Helm Deployment
Use case: Deploying to Kubernetes using Helm charts.
stages:
- build
- deploy
variables:
HELM_CHART_PATH: ./helm/mychart
RELEASE_NAME: myapp
deploy-helm-staging:
stage: deploy
image: alpine/helm:3.14.0
before_script:
- kubectl config use-context $KUBE_CONTEXT
script:
- helm upgrade --install $RELEASE_NAME $HELM_CHART_PATH
--namespace staging
--create-namespace
--set image.tag=$CI_COMMIT_SHORT_SHA
--set environment=staging
--wait
--timeout 5m
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "develop"
deploy-helm-production:
stage: deploy
image: alpine/helm:3.14.0
before_script:
- kubectl config use-context $KUBE_CONTEXT
script:
- helm upgrade --install $RELEASE_NAME $HELM_CHART_PATH
--namespace production
--create-namespace
--set image.tag=$CI_COMMIT_TAG
--set environment=production
--wait
--timeout 10m
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_TAG
when: manual
resource_group: k8s-productionPattern 3: Kustomize Deployment
Use case: Deploying to Kubernetes using Kustomize.
stages:
- deploy
deploy-kustomize:
stage: deploy
image:
name: registry.k8s.io/kubectl:v1.29.1
entrypoint: [""]
before_script:
- kubectl config use-context $KUBE_CONTEXT
script:
- cd k8s/overlays/$ENVIRONMENT
- kustomize edit set image myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
- kustomize build . | kubectl apply -f -
- kubectl rollout status deployment/myapp -n $ENVIRONMENT
environment:
name: $ENVIRONMENT
kubernetes:
namespace: $ENVIRONMENT
rules:
- if: $CI_COMMIT_BRANCH == "main"
variables:
ENVIRONMENT: production
when: manual---
Testing Patterns
Pattern 1: Comprehensive Testing Pipeline
Use case: Multiple types of tests (unit, integration, e2e).
stages:
- test
- integration
variables:
NODE_VERSION: "20"
default:
image: node:${NODE_VERSION}-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull
test-unit:
stage: test
needs: []
script:
- npm ci
- npm run test:unit
coverage: '/Coverage: \d+\.\d+%/'
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
test-lint:
stage: test
needs: []
script:
- npm ci
- npm run lint
allow_failure: true
test-types:
stage: test
needs: []
image: node:${NODE_VERSION}-alpine
script:
- npm ci
- npm run type-check
test-integration:
stage: integration
needs: [test-unit]
services:
- postgres:15-alpine
- redis:7-alpine
variables:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
DATABASE_URL: postgres://testuser:testpass@postgres:5432/testdb
REDIS_URL: redis://redis:6379
script:
- npm ci
- npm run test:integration
retry:
max: 2
when:
- runner_system_failure
- api_failure
test-e2e:
stage: integration
needs: [test-unit]
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-123.0-edge-121.0.2277.83-1
script:
- npm ci
- npm run start:test &
- npx wait-on http://localhost:3000
- npm run test:e2e
artifacts:
when: always
paths:
- cypress/videos/
- cypress/screenshots/
expire_in: 1 weekPattern 2: Matrix Testing (Multiple Versions)
Use case: Testing across multiple language/platform versions.
stages:
- test
test-matrix:
stage: test
parallel:
matrix:
- NODE_VERSION: ['18', '20', '22']
OS: ['alpine', 'bookworm-slim']
image: node:${NODE_VERSION}-${OS}
script:
- node --version
- npm --version
- npm ci
- npm testPattern 3: Security Scanning
Use case: SAST, dependency scanning, container scanning.
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
stages:
- test
- security
# Customize SAST
semgrep-sast:
variables:
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
# Customize dependency scanning
gemnasium-dependency_scanning:
variables:
DS_EXCLUDED_PATHS: "node_modules, vendor"
# Container scanning after build
container_scanning:
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
needs: [build-docker-image]---
Deployment Patterns
Pattern 1: Multi-Environment Deployment
Use case: Deploy to multiple environments (dev, staging, production).
stages:
- build
- deploy
variables:
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
.deploy-template:
image: alpine:3.19
before_script:
- apk add --no-cache curl
script:
- curl -X POST $DEPLOY_WEBHOOK_URL
-H "Authorization: Bearer $DEPLOY_TOKEN"
-d "{\"environment\":\"${ENVIRONMENT}\",\"version\":\"${IMAGE_TAG}\"}"
resource_group: ${ENVIRONMENT}
deploy-dev:
extends: .deploy-template
stage: deploy
variables:
ENVIRONMENT: development
environment:
name: development
url: https://dev.example.com
rules:
- if: $CI_COMMIT_BRANCH == "develop"
deploy-staging:
extends: .deploy-template
stage: deploy
variables:
ENVIRONMENT: staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
deploy-production:
extends: .deploy-template
stage: deploy
variables:
ENVIRONMENT: production
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
needs: [deploy-staging]Pattern 2: Blue-Green Deployment
Use case: Zero-downtime deployments with blue-green strategy.
stages:
- deploy
- verify
- switch
deploy-green:
stage: deploy
script:
- kubectl apply -f k8s/deployment-green.yaml
- kubectl rollout status deployment/myapp-green -n production
environment:
name: production-green
url: https://green.example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
verify-green:
stage: verify
needs: [deploy-green]
script:
- curl -f https://green.example.com/health || exit 1
- npm run test:smoke -- --baseUrl=https://green.example.com
retry:
max: 3
when: always
switch-traffic:
stage: switch
needs: [verify-green]
script:
- kubectl patch service myapp -n production -p '{"spec":{"selector":{"version":"green"}}}'
- echo "Traffic switched to green deployment"
environment:
name: production
url: https://example.com
when: manualPattern 3: Canary Deployment
Use case: Gradual rollout with traffic splitting.
stages:
- deploy
- canary
deploy-canary:
stage: deploy
script:
- kubectl apply -f k8s/deployment-canary.yaml
- kubectl set image deployment/myapp-canary myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
environment:
name: production-canary
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
# Increase canary traffic gradually
canary-10-percent:
stage: canary
script:
- kubectl patch virtualservice myapp -n production --type merge -p '{"spec":{"http":[{"route":[{"destination":{"host":"myapp-stable"},"weight":90},{"destination":{"host":"myapp-canary"},"weight":10}]}]}}'
needs: [deploy-canary]
when: manual
canary-50-percent:
stage: canary
script:
- kubectl patch virtualservice myapp -n production --type merge -p '{"spec":{"http":[{"route":[{"destination":{"host":"myapp-stable"},"weight":50},{"destination":{"host":"myapp-canary"},"weight":50}]}]}}'
needs: [canary-10-percent]
when: manual
canary-promote:
stage: canary
script:
- kubectl patch virtualservice myapp -n production --type merge -p '{"spec":{"http":[{"route":[{"destination":{"host":"myapp-canary"},"weight":100}]}]}}'
- kubectl set image deployment/myapp-stable myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
needs: [canary-50-percent]
when: manual---
Multi-Project Pipeline Patterns
Pattern 1: Trigger Downstream Projects
Use case: Orchestrate multiple project pipelines.
stages:
- build
- trigger
build-library:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
trigger-dependent-projects:
stage: trigger
parallel:
matrix:
- PROJECT: ['group/app1', 'group/app2', 'group/app3']
trigger:
project: $PROJECT
branch: main
strategy: depend
needs: [build-library]
rules:
- if: $CI_COMMIT_BRANCH == "main"Pattern 2: Multi-Project Pipeline with Variables
Use case: Pass variables to downstream pipelines.
trigger-downstream:
stage: deploy
trigger:
project: group/deployment-project
branch: main
strategy: depend
variables:
SERVICE_NAME: my-service
SERVICE_VERSION: $CI_COMMIT_SHORT_SHA
ENVIRONMENT: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual---
Parent-Child Pipeline Patterns
Pattern 1: Dynamic Child Pipeline
Use case: Generate child pipeline configuration dynamically.
stages:
- generate
- deploy
generate-child-pipeline:
stage: generate
script:
- python scripts/generate-pipeline.py > generated-pipeline.yml
artifacts:
paths:
- generated-pipeline.yml
trigger-child-pipeline:
stage: deploy
trigger:
include:
- artifact: generated-pipeline.yml
job: generate-child-pipeline
strategy: depend
needs: [generate-child-pipeline]Pattern 2: Monorepo with Multiple Child Pipelines
Use case: Each component has its own pipeline configuration.
# Parent .gitlab-ci.yml
stages:
- trigger
trigger-frontend:
stage: trigger
trigger:
include: frontend/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- frontend/**/*
trigger-backend:
stage: trigger
trigger:
include: backend/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- backend/**/*
trigger-infrastructure:
stage: trigger
trigger:
include: infrastructure/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- infrastructure/**/*---
Monorepo Patterns
Pattern 1: Conditional Jobs Based on Changes
Use case: Only run jobs for changed components.
stages:
- build
- test
- deploy
build-frontend:
stage: build
script:
- cd frontend
- npm ci
- npm run build
rules:
- changes:
- frontend/**/*
build-backend:
stage: build
script:
- cd backend
- go build
rules:
- changes:
- backend/**/*
test-frontend:
stage: test
needs: [build-frontend]
script:
- cd frontend
- npm test
rules:
- changes:
- frontend/**/*
test-backend:
stage: test
needs: [build-backend]
script:
- cd backend
- go test ./...
rules:
- changes:
- backend/**/*Pattern 2: Monorepo with Parallel Child Pipelines
Use case: Run multiple child pipelines in parallel for different components.
stages:
- trigger
.trigger-template:
stage: trigger
trigger:
strategy: depend
trigger-service-a:
extends: .trigger-template
trigger:
include: services/service-a/.gitlab-ci.yml
rules:
- changes:
- services/service-a/**/*
trigger-service-b:
extends: .trigger-template
trigger:
include: services/service-b/.gitlab-ci.yml
rules:
- changes:
- services/service-b/**/*
trigger-service-c:
extends: .trigger-template
trigger:
include: services/service-c/.gitlab-ci.yml
rules:
- changes:
- services/service-c/**/*---
Template and Reusability Patterns
Pattern 1: Global Template Library
Use case: Reusable templates across multiple projects.
# templates/build-templates.yml
.node-build-template:
image: node:20-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- npm ci
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
.python-build-template:
image: python:3.12-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .venv/
before_script:
- python -m venv .venv
- source .venv/bin/activate
- pip install -r requirements.txt
script:
- python setup.py build# Project .gitlab-ci.yml
include:
- project: 'group/ci-templates'
file: 'templates/build-templates.yml'
stages:
- build
build-app:
extends: .node-build-template
stage: buildPattern 2: Local Template with Extends
Use case: DRY configuration within a single project.
# Hidden template jobs
.deployment-base:
image: alpine:3.19
before_script:
- apk add --no-cache curl
script:
- ./scripts/deploy.sh $ENVIRONMENT
resource_group: ${ENVIRONMENT}
retry:
max: 2
when:
- runner_system_failure
# Actual deployment jobs
deploy-staging:
extends: .deployment-base
stage: deploy
variables:
ENVIRONMENT: staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy-production:
extends: .deployment-base
stage: deploy
variables:
ENVIRONMENT: production
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_TAG
when: manual---
Summary
These patterns provide a solid foundation for common GitLab CI/CD scenarios. When using these patterns:
1. Customize them for your specific needs 2. Validate using gitlab-ci-validator skill 3. Follow best practices from references/best-practices.md 4. Test locally when possible 5. Document any modifications
Remember: These are starting points. Always adapt them to your project's specific requirements, security policies, and infrastructure.
#!/usr/bin/env python3
"""Regression tests for gitlab-ci-generator SKILL.md contract guarantees."""
from pathlib import Path
import unittest
SKILL_MD = Path(__file__).resolve().parent.parent / "SKILL.md"
def _between(text: str, start_marker: str, end_marker: str) -> str:
start = text.find(start_marker)
end = text.find(end_marker, start + len(start_marker))
if start == -1 or end == -1:
raise AssertionError(
f"Could not find section boundaries: {start_marker!r} -> {end_marker!r}"
)
return text[start:end]
class TestP1ValidationFallbackContract(unittest.TestCase):
"""Ensure fallback validation always targets the generated pipeline file."""
@classmethod
def setUpClass(cls) -> None:
cls.skill_text = SKILL_MD.read_text(encoding="utf-8")
cls.script_fallback = _between(
cls.skill_text,
"2. **Script fallback path (if validator skill cannot be invoked):**",
"3. **Manual fallback path (only if both primary and script paths are unavailable):**",
)
def test_declares_pipeline_file_variable(self) -> None:
self.assertIn('PIPELINE_FILE="<generated-output-path>"', self.script_fallback)
def test_requires_file_existence_precheck(self) -> None:
self.assertIn('if [[ ! -f "$PIPELINE_FILE" ]]; then', self.script_fallback)
self.assertIn('echo "ERROR: CI file not found: $PIPELINE_FILE" >&2', self.script_fallback)
def test_validator_commands_use_selected_pipeline_file(self) -> None:
self.assertIn(
'validate_gitlab_ci.sh "$PIPELINE_FILE"',
self.script_fallback,
"Fallback validator commands must use PIPELINE_FILE.",
)
self.assertNotIn(
"validate_gitlab_ci.sh .gitlab-ci.yml",
self.script_fallback,
"Hardcoded .gitlab-ci.yml fallback commands can validate the wrong file.",
)
def test_api_lint_fallback_uses_generated_file_content(self) -> None:
self.assertIn('$(<"$PIPELINE_FILE")', self.script_fallback)
self.assertIn(
"https://gitlab.com/api/v4/projects/:id/ci/lint?include_merged_yaml=true",
self.script_fallback,
)
class TestP2ModeContractConsistency(unittest.TestCase):
"""Ensure Targeted mode is first-class across routing, checklist, and summary."""
@classmethod
def setUpClass(cls) -> None:
cls.skill_text = SKILL_MD.read_text(encoding="utf-8")
cls.step1 = _between(
cls.skill_text,
"### Step 1: Classify Complexity (REQUIRED)",
"### Step 2: Load References by Tier (REQUIRED)",
)
cls.step2 = _between(
cls.skill_text,
"### Step 2: Load References by Tier (REQUIRED)",
"### Step 3: Confirm Understanding (EXPLICIT OUTPUT REQUIRED)",
)
cls.checklist_mode = _between(
cls.skill_text,
"### Mode and References",
"### Generation Standards Applied",
)
cls.summary = _between(
cls.skill_text,
"## Summary",
"Generate GitLab CI/CD pipelines that are:",
)
def test_step1_includes_targeted_mode(self) -> None:
self.assertIn("| **Targeted** |", self.step1)
def test_step2_has_targeted_specific_loading_rules(self) -> None:
self.assertIn("**Targeted mode (review/Q&A/snippet/focused fix):**", self.step2)
self.assertIn(
"Do not enforce Full-generation Tier 1/Tier 2 checklist items.",
self.step2,
)
self.assertIn(
"Tier 1 (Required for complete pipeline generation in Lightweight and Full modes)",
self.step2,
)
def test_checklist_mode_selection_supports_targeted(self) -> None:
self.assertIn("Complexity mode selected (`Targeted`, `Lightweight`, or `Full`)", self.checklist_mode)
self.assertIn(
"For **Targeted** mode: only directly relevant files/references loaded",
self.checklist_mode,
)
self.assertNotIn(
"Complexity mode selected (`Lightweight` or `Full`)",
self.checklist_mode,
)
def test_summary_includes_all_modes(self) -> None:
self.assertIn(
"Classify Complexity** - choose `Targeted`, `Lightweight`, or `Full` mode.",
self.summary,
)
self.assertIn(
"For `Targeted` mode, load only directly relevant files.",
self.summary,
)
if __name__ == "__main__":
unittest.main()
Related skills
How it compares
Pick gitlab-ci-generator for scaffolded GitLab CI with validation; use gitlab-ci-validator alone when reviewing an existing pipeline without generating new files.
FAQ
How does gitlab-ci-generator validate generated pipelines?
gitlab-ci-generator validates complete pipelines using the companion gitlab-ci-validator skill with strict Critical and High gates before presenting output. Validation status and template version notes accompany every generated .gitlab-ci.yml.
Which templates does gitlab-ci-generator provide?
gitlab-ci-generator ships four templates under assets/templates/: basic-pipeline.yml for simple projects, docker-build.yml for container pipelines, kubernetes-deploy.yml for K8s deployments, and multi-project.yml for parent-child triggers.