creating components
Creating GitLab CI/CD Components
This guide covers the complete process of building reusable CI/CD components.
Project Structure
Required Files
my-components/
README.md # Required: Project documentation
LICENSE.md # Recommended: License information
.gitlab-ci.yml # Required: Release automation
.gitignore # Recommended: Ignore artifacts
templates/ # Required: Component definitions
component-one.yml # Single-file component
component-two/ # Directory-based component
template.yml
component-three/
template.yml
scripts/ # Optional: Helper scripts
setup.sh
Directory Structure Rules
- templates/ is mandatory: All components must live here
- Two naming patterns:
- Single file:
templates/component-name.yml - Directory:
templates/component-name/template.yml
- Single file:
- Component name: Derived from filename or directory name
- No nested templates: Subdirectories inside component directories are for supporting files only
Component Definition
Basic Component
# templates/docker-build.yml spec: inputs: image_name: type: string dockerfile: type: string default: 'Dockerfile' context: type: string default: '.' --- build-docker-image: stage: build image: docker:latest services: - docker:dind script: - docker build -t $[[ inputs.image_name ]] -f $[[ inputs.dockerfile ]] $[[ inputs.context ]] - docker push $[[ inputs.image_name ]]
Component with Multiple Jobs
# templates/node-pipeline.yml spec: inputs: node_version: type: string default: '20' enable_lint: type: boolean default: true enable_tests: type: boolean default: true --- install: stage: .pre image: node:$[[ inputs.node_version ]] script: - npm ci cache: key: node-modules-$CI_COMMIT_REF_SLUG paths: - node_modules/ artifacts: paths: - node_modules/ expire_in: 1 hour lint: stage: test image: node:$[[ inputs.node_version ]] script: - npm run lint rules: - if: $[[ inputs.enable_lint ]] needs: - install test: stage: test image: node:$[[ inputs.node_version ]] script: - npm test coverage: '/Coverage: \d+\.\d+%/' rules: - if: $[[ inputs.enable_tests ]] needs: - install artifacts: reports: junit: junit.xml coverage_report: coverage_format: cobertura path: coverage/cobertura-coverage.xml
Input Types and Validation
String Inputs
spec: inputs: environment: type: string default: 'staging' # With options (enum) log_level: type: string options: ['debug', 'info', 'warn', 'error'] default: 'info' # With regex validation version: type: string regex: '^\d+\.\d+\.\d+$'
Boolean Inputs
spec: inputs: debug: type: boolean default: false enable_notifications: type: boolean default: true
Number Inputs
spec: inputs: timeout: type: number default: 300 parallel: type: number options: [1, 2, 4, 8] default: 1
Array Inputs
spec: inputs: environments: type: array default: ['staging', 'production'] scan_paths: type: array
Usage in component:
deploy-job: parallel: matrix: - ENVIRONMENT: $[[ inputs.environments ]] script: - deploy.sh ${ENVIRONMENT}
Conditional Inputs with Rules
Basic Conditional Options
spec: inputs: cache_mode: type: string default: 'pull-push' options: - 'pull-push' - 'pull' - 'push' - 'none' rules: - if: $[[ inputs.environment == "production" ]] options: ['pull', 'none'] # Restrict options in production
Dynamic Defaults Based on Other Inputs
spec: inputs: environment: type: string default: 'staging' replicas: type: number rules: - if: $[[ inputs.environment == "production" ]] default: 5 - if: $[[ inputs.environment == "staging" ]] default: 2 - if: $[[ inputs.environment == "development" ]] default: 1
Complex Conditional Logic
spec: inputs: deployment_strategy: type: string default: 'rolling' enable_canary: type: boolean default: false canary_percentage: type: number default: 10 rules: - if: $[[ inputs.enable_canary == true ]] options: [10, 25, 50] - if: $[[ inputs.enable_canary == false ]] default: 0 # Ignored when canary disabled
Using Inputs in Component Logic
String Interpolation
--- deploy-job: stage: deploy script: - echo "Deploying to $[[ inputs.environment ]]" - deploy --target $[[ inputs.environment ]] environment: name: $[[ inputs.environment ]]
Conditional Job Execution
--- security-scan: stage: test script: - run-security-scan rules: - if: $[[ inputs.enable_security_scan ]]
Dynamic Job Extension
spec: inputs: cache_mode: type: string options: ['pull-push', 'pull', 'none'] --- .cache-pull-push: cache: key: $CI_COMMIT_REF_SLUG paths: - .cache/ policy: pull-push .cache-pull: cache: key: $CI_COMMIT_REF_SLUG paths: - .cache/ policy: pull .cache-none: {} build-job: extends: .cache-$[[ inputs.cache_mode ]] script: - make build
Component Documentation
README.md Requirements
Every component project must have a README.md in the root:
# My CI/CD Components Reusable GitLab CI/CD components for [organization name]. ## Components ### docker-build Builds and pushes Docker images. **Inputs:** - `image_name` (string, required): Full image name with registry - `dockerfile` (string, default: `Dockerfile`): Path to Dockerfile - `context` (string, default: `.`): Build context path **Example:** \`\`\`yaml include: - component: gitlab.com/my-group/my-components/docker-build@1.0.0 inputs: image_name: registry.gitlab.com/my-group/my-app:latest dockerfile: docker/Dockerfile \`\`\` ### node-pipeline Complete Node.js CI pipeline with install, lint, and test. **Inputs:** - `node_version` (string, default: `20`): Node.js version - `enable_lint` (boolean, default: `true`): Run linting - `enable_tests` (boolean, default: `true`): Run tests **Example:** \`\`\`yaml include: - component: gitlab.com/my-group/my-components/node-pipeline@1.0.0 inputs: node_version: '18' enable_lint: true \`\`\` ## Versioning We use semantic versioning. See [CHANGELOG.md](CHANGELOG.md) for release history. ## License MIT - See [LICENSE.md](LICENSE.md)
Inline Documentation
Add comments in component YAML:
# templates/deployment.yml # Component: Kubernetes Deployment # Description: Deploys application to Kubernetes cluster # Maintainer: DevOps Team <devops@example.com> spec: inputs: # The Kubernetes namespace to deploy to namespace: type: string default: 'default' # Number of replicas to run replicas: type: number default: 3 # Docker image to deploy (with tag) image: type: string
Testing Components
Test Pipeline Configuration
# .gitlab-ci.yml stages: - validate - test - release # Validate component YAML syntax validate-components: stage: validate image: alpine:latest before_script: - apk add --no-cache yq script: - | for file in templates/*.yml templates/*/template.yml; do if [ -f "$file" ]; then echo "Validating $file" yq eval '.' "$file" > /dev/null || exit 1 fi done # Test each component in a real pipeline test-docker-build: stage: test needs: [] trigger: include: - local: templates/docker-build.yml strategy: depend test-node-pipeline: stage: test needs: [] trigger: include: - local: templates/node-pipeline.yml strategy: depend # Release to catalog (only on tags) release: stage: release image: registry.gitlab.com/gitlab-org/release-cli:latest rules: - if: $CI_COMMIT_TAG =~ /^\d+\.\d+\.\d+$/ script: - echo "Releasing version $CI_COMMIT_TAG" release: tag_name: $CI_COMMIT_TAG description: 'Release $CI_COMMIT_TAG'
Test Project Pattern
Create test projects that consume your components:
my-components/
templates/
docker-build.yml
tests/
test-docker-build/
.gitlab-ci.yml # Uses docker-build component
Dockerfile
test-node-pipeline/
.gitlab-ci.yml # Uses node-pipeline component
package.json
.gitlab-ci.yml # Triggers test projects
# tests/test-docker-build/.gitlab-ci.yml include: - component: $CI_PROJECT_PATH/docker-build@$CI_COMMIT_SHA inputs: image_name: test-image:latest
Component Composition
Extending Other Components
# templates/advanced-deploy.yml spec: inputs: environment: type: string enable_rollback: type: boolean default: true --- # Include and extend base deployment component include: - component: gitlab.com/my-group/components/base-deploy@1.0.0 inputs: environment: $[[ inputs.environment ]] # Add rollback job rollback-deployment: stage: .post script: - rollback.sh $[[ inputs.environment ]] when: on_failure rules: - if: $[[ inputs.enable_rollback ]]
Using !reference for Reusability
# templates/shared-scripts.yml spec: {} --- .deploy-scripts: script: - echo "Authenticating..." - auth.sh - echo "Deploying..." - deploy.sh --- # templates/deploy-staging.yml include: - component: gitlab.com/my-group/components/shared-scripts@1.0.0 --- deploy-staging: stage: deploy script: - !reference [.deploy-scripts, script] - echo "Deployed to staging"
Advanced Patterns
Matrix Deployments
# templates/multi-environment-deploy.yml spec: inputs: environments: type: array default: ['staging', 'production'] parallel: type: number default: 1 --- deploy: stage: deploy parallel: matrix: - ENVIRONMENT: $[[ inputs.environments ]] script: - deploy.sh ${ENVIRONMENT} environment: name: ${ENVIRONMENT}
Dynamic Service Dependencies
# templates/integration-test.yml spec: inputs: enable_database: type: boolean default: true enable_redis: type: boolean default: false database_version: type: string default: '15' --- integration-test: stage: test image: node:20 services: - name: postgres:$[[ inputs.database_version ]] alias: database rules: - if: $[[ inputs.enable_database ]] - name: redis:alpine alias: cache rules: - if: $[[ inputs.enable_redis ]] script: - npm run test:integration
Common Pitfalls
Empty spec:inputs
Wrong:
spec: inputs: # Empty section causes errors --- my-job: script: echo "hello"
Right:
spec: {} # Empty spec is valid --- my-job: script: echo "hello"
Input Syntax Errors
Wrong:
script: - echo "Value: ${inputs.my_value}" # Wrong syntax
Right:
script: - echo "Value: $[[ inputs.my_value ]]" # Correct syntax
Missing Separators
Wrong:
spec: inputs: foo: string my-job: # Missing separator script: echo "test"
Right:
spec: inputs: foo: type: string --- # Required separator my-job: script: echo "test"
Best Practices
- Single Responsibility: Each component should do one thing well
- Sensible Defaults: Provide defaults for all non-required inputs
- Clear Naming: Use descriptive names for components and inputs
- Comprehensive Testing: Test all input combinations
- Document Everything: README, inline comments, and examples
- Version Carefully: Follow semantic versioning strictly
- Validate Inputs: Use
optionsandregexfor input validation - Think Reusability: Design for multiple use cases, not just one project
Next Steps
- Using Components: Learn how to consume components
- Versioning: Understand semantic versioning strategies
- Multi-Project Strategy: Roll out to 70+ projects
- Testing: Comprehensive component testing strategies