🎯 Exemplos recomendados
Balanced sample collections from various categories for you to explore
Exemplos GitLab CI
Exemplos de configuração GitLab CI/CD incluindo pipelines multi-estágio, caching, artifacts e estratégias de implantação
💻 GitLab CI Pipeline Básico yaml
🟢 simple
⭐⭐
Pipeline GitLab CI simples com estágios de build, teste e deploy para aplicação Node.js
⏱️ 20 min
🏷️ gitlab-ci, pipeline, nodejs, testing
Prerequisites:
GitLab basics, YAML syntax, CI/CD concepts
# .gitlab-ci.yml - Basic GitLab CI Pipeline
# Define pipeline stages
stages:
- prepare
- build
- test
- security
- deploy
# Global variables
variables:
NODE_VERSION: "18"
APP_NAME: "my-app"
ARTIFACT_EXPIRATION: "1 week"
# Cache configuration
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
- .npm/
# Default configuration for all jobs
default:
image: node:$NODE_VERSION-alpine
before_script:
- echo "Starting job: $CI_JOB_NAME"
- echo "Branch: $CI_COMMIT_REF_NAME"
- echo "Commit: $CI_COMMIT_SHORT_SHA"
- npm ci --cache .npm --prefer-offline
after_script:
- echo "Completed job: $CI_JOB_NAME"
# Job to prepare the environment
prepare:
stage: prepare
script:
- echo "Preparing build environment..."
- node --version
- npm --version
- echo "Environment preparation completed"
artifacts:
reports:
dotenv: prepare.env
expire_in: 1 hour
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Build job
build:
stage: build
script:
- echo "Building application..."
- npm run build
- echo "Build completed successfully"
artifacts:
name: "$APP_NAME-$CI_COMMIT_SHORT_SHA"
paths:
- dist/
- build/
- package.json
- package-lock.json
expire_in: $ARTIFACT_EXPIRATION
reports:
junit: test-results.xml
coverage: '/Liness*:s*(d+.d+)%/'
dependencies:
- prepare
# Unit tests
test:unit:
stage: test
script:
- echo "Running unit tests..."
- npm run test:unit
- echo "Unit tests completed"
artifacts:
reports:
junit: coverage/junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
when: always
expire_in: 1 week
dependencies:
- build
# Integration tests
test:integration:
stage: test
services:
- name: postgres:15-alpine
alias: postgres
variables:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
variables:
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db"
script:
- echo "Running integration tests..."
- npm run test:integration
- echo "Integration tests completed"
artifacts:
reports:
junit: integration-test-results.xml
when: always
expire_in: 1 week
dependencies:
- build
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# E2E tests
test:e2e:
stage: test
image: cypress/browsers:node-18.16.1-chrome114-ff114-edge
script:
- echo "Running E2E tests..."
- npm run build
- npm run test:e2e
- echo "E2E tests completed"
artifacts:
reports:
junit: cypress/results/junit.xml
paths:
- cypress/videos/
- cypress/screenshots/
when: always
expire_in: 1 week
dependencies:
- build
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Code quality analysis
code_quality:
stage: test
image: docker:24.0.5
services:
- docker:24.0.5-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
allow_failure: true
script:
- echo "Running code quality analysis..."
- docker run
--env SOURCE_CODE="$PWD"
--volume "$PWD":/code
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.29" /code
artifacts:
reports:
codequality: gl-code-quality-report.json
expire_in: 1 week
dependencies: []
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Security scanning
security:dependency_scan:
stage: security
image: node:$NODE_VERSION-alpine
script:
- echo "Running dependency security scan..."
- npm audit --audit-level=high --json > npm-audit-report.json || true
- npm audit --audit-level=high
- echo "Dependency scan completed"
artifacts:
reports:
sast: npm-audit-report.json
paths:
- npm-audit-report.json
expire_in: 1 week
allow_failure: true
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# SAST scanning
security:sast:
stage: security
image: node:$NODE_VERSION-alpine
script:
- echo "Running SAST scan..."
- npm install -g semgrep
- semgrep --config=auto --json --output=semgrep-report.json .
- echo "SAST scan completed"
artifacts:
reports:
sast: semgrep-report.json
expire_in: 1 week
allow_failure: true
dependencies: []
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Container scanning (if using Docker)
security:container_scan:
stage: security
image: docker:24.0.5
services:
- docker:24.0.5-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
script:
- echo "Building Docker image for scanning..."
- docker build -t $IMAGE_TAG .
- echo "Running container security scan..."
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
-v $PWD:/tmp/.cache/ aquasec/trivy:latest image
--format json
--output /tmp/.cache/container-scan-report.json
$IMAGE_TAG
- echo "Container scan completed"
artifacts:
reports:
container_scanning: /tmp/.cache/container-scan-report.json
expire_in: 1 week
allow_failure: true
dependencies: []
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- exists:
- Dockerfile
# Deploy to staging
deploy:staging:
stage: deploy
environment:
name: staging
url: https://staging.example.com
script:
- echo "Deploying to staging environment..."
- echo "Deploying version: $CI_COMMIT_SHORT_SHA"
- apk add --no-cache curl
# Example deployment script
- |
curl -X POST "$STAGING_DEPLOY_WEBHOOK" \
-H "Authorization: Bearer $STAGING_DEPLOY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ref": "'$CI_COMMIT_REF_NAME'",
"sha": "'$CI_COMMIT_SHA'",
"environment": "staging"
}'
# Health check
- sleep 30
- curl -f "https://staging.example.com/health" || exit 1
- echo "Staging deployment completed"
dependencies:
- build
- test:unit
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Deploy to production
deploy:production:
stage: deploy
environment:
name: production
url: https://example.com
script:
- echo "Deploying to production environment..."
- echo "Deploying version: $CI_COMMIT_SHORT_SHA"
- apk add --no-cache curl
# Production deployment with manual approval handled by environment protection
- |
curl -X POST "$PRODUCTION_DEPLOY_WEBHOOK" \
-H "Authorization: Bearer $PRODUCTION_DEPLOY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ref": "'$CI_COMMIT_REF_NAME'",
"sha": "'$CI_COMMIT_SHA'",
"environment": "production"
}'
# Health check
- sleep 60
- curl -f "https://example.com/health" || exit 1
- echo "Production deployment completed"
dependencies:
- deploy:staging
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
needs:
- deploy:staging
# Cleanup old deployments
cleanup:
stage: deploy
image: alpine:3.18
script:
- echo "Cleaning up old deployments..."
- echo "This job removes old containers and temporary files"
- |
# Example cleanup commands
echo "Cleaning Docker images older than 30 days"
echo "Removing temporary build files"
- echo "Cleanup completed"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: on_success
dependencies: []
💻 GitLab CI Pipeline Monorepo yaml
🟡 intermediate
⭐⭐⭐⭐
Pipeline monorepo avançado com filtragem de caminho, jobs paralelos e estratégia matricial
⏱️ 35 min
🏷️ gitlab-ci, monorepo, matrix, helm, blue-green
Prerequisites:
GitLab CI advanced, Docker, Kubernetes, Helm, Monorepo concepts
# .gitlab-ci.yml - Monorepo Pipeline with Matrix Strategy
# Global configuration
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
KUBERNETES_NAMESPACE_OVERWRITE: "gitlab-ci"
FF_USE_FASTZIP: "true"
TRANSFER_METER_FREQUENCY: "5s"
# Include common configuration
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
- template: Code-Quality.gitlab-ci.yml
# Global cache configuration
.cache_base: &cache_base
key: "$CI_COMMIT_REF_SLUG"
paths:
- .npm/
- .cache/
# Global default
default:
image: node:18-alpine
cache: *cache_base
# Pipeline stages
stages:
- validate
- plan
- build
- test
- security
- release
- deploy
# Workflow rules
workflow:
rules:
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
when: never
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH =~ /^release\/.*/
- if: $CI_COMMIT_TAG
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Job to validate configuration
validate:config:
stage: validate
image: alpine:3.18
script:
- echo "Validating pipeline configuration..."
- echo "Validating YAML syntax..."
- apk add --no-cache yq
- yq eval '.' .gitlab-ci.yml > /dev/null
- echo "Configuration validation completed"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
dependencies: []
# Generate build matrix
generate:matrix:
stage: plan
image: node:18-alpine
script:
- |
cat > matrix.json << EOF
{
"packages": [
{"name": "frontend", "path": "packages/frontend", "framework": "react"},
{"name": "backend", "path": "packages/backend", "framework": "nestjs"},
{"name": "api-gateway", "path": "packages/api-gateway", "framework": "express"},
{"name": "admin", "path": "packages/admin", "framework": "vue"},
{"name": "shared", "path": "packages/shared", "framework": "typescript-lib"}
],
"environments": ["dev", "staging"],
"node_versions": ["16", "18", "20"]
}
EOF
- cat matrix.json
artifacts:
reports:
dotenv: matrix.env
paths:
- matrix.json
expire_in: 1 hour
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Build all packages using matrix strategy
build:packages:
stage: build
image: node:18-alpine
parallel:
matrix:
- PACKAGE: ["frontend", "backend", "api-gateway", "admin", "shared"]
NODE_VERSION: ["16", "18", "20"]
script:
- echo "Building package: $PACKAGE with Node.js $NODE_VERSION"
- cd packages/$PACKAGE
- npm ci --cache ../../.npm --prefer-offline
- npm run build
- echo "Build completed for $PACKAGE"
artifacts:
name: "$PACKAGE-build-$CI_COMMIT_SHORT_SHA"
paths:
- packages/$PACKAGE/dist/
- packages/$PACKAGE/build/
- packages/$PACKAGE/lib/
expire_in: 1 week
when: always
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "packages/$PACKAGE/**/*"
- if: $CI_PIPELINE_SOURCE == "push"
changes:
- "packages/$PACKAGE/**/*"
# Docker build matrix
build:docker:
stage: build
image: docker:24.0.5
services:
- docker:24.0.5-dind
parallel:
matrix:
- PACKAGE: ["frontend", "backend", "api-gateway", "admin"]
REGISTRY: ["registry.gitlab.com", "docker.io"]
variables:
IMAGE_TAG: "$CI_REGISTRY_IMAGE/$PACKAGE:$CI_COMMIT_SHA"
DOCKERFILE_PATH: "packages/$PACKAGE/Dockerfile"
script:
- echo "Building Docker image for $PACKAGE"
- echo "Registry: $REGISTRY"
- echo "Image tag: $IMAGE_TAG"
- |
if [ -f "$DOCKERFILE_PATH" ]; then
docker build
--build-arg NODE_VERSION=18
--build-arg PACKAGE_NAME=$PACKAGE
-t $IMAGE_TAG
-f $DOCKERFILE_PATH
packages/$PACKAGE/
echo "Image built successfully: $IMAGE_TAG"
# Tag with commit SHA and branch
docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE/$PACKAGE:$CI_COMMIT_REF_SLUG
# Tag with latest for main branch
if [ "$CI_COMMIT_REF_NAME" = "$CI_DEFAULT_BRANCH" ]; then
docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE/$PACKAGE:latest
fi
# Push to registry
echo "Pushing images..."
docker push $IMAGE_TAG
docker push $CI_REGISTRY_IMAGE/$PACKAGE:$CI_COMMIT_REF_SLUG
if [ "$CI_COMMIT_REF_NAME" = "$CI_DEFAULT_BRANCH" ]; then
docker push $CI_REGISTRY_IMAGE/$PACKAGE:latest
fi
echo "Images pushed successfully"
else
echo "No Dockerfile found for $PACKAGE, skipping"
fi
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH =~ /^release\/.*/
- if: $CI_COMMIT_TAG
dependencies:
- build:packages
# Test matrix
test:unit:
stage: test
parallel:
matrix:
- PACKAGE: ["frontend", "backend", "api-gateway", "admin", "shared"]
COVERAGE: ["true", "false"]
script:
- echo "Running unit tests for $PACKAGE"
- cd packages/$PACKAGE
- npm ci --cache ../../.npm --prefer-offline
- |
if [ "$COVERAGE" = "true" ]; then
npm run test:coverage
else
npm run test
fi
artifacts:
reports:
junit: "packages/$PACKAGE/test-results.xml"
coverage_report:
coverage_format: cobertura
path: "packages/$PACKAGE/coverage/cobertura-coverage.xml"
when: always
expire_in: 1 week
paths:
- "packages/$PACKAGE/coverage/"
expire_in: 1 week
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "packages/$PACKAGE/**/*"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH =~ /^release\/.*/
# Integration tests with matrix
test:integration:
stage: test
services:
- name: postgres:15-alpine
alias: postgres
variables:
POSTGRES_DB: integration_test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
- name: redis:7-alpine
alias: redis
parallel:
matrix:
- PACKAGE: ["backend", "api-gateway"]
ENVIRONMENT: ["dev", "staging"]
variables:
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/integration_test_db"
REDIS_URL: "redis://redis:6379"
script:
- echo "Running integration tests for $PACKAGE in $ENVIRONMENT"
- cd packages/$PACKAGE
- npm ci --cache ../../.npm --prefer-offline
- npm run test:integration -- --environment=$ENVIRONMENT
artifacts:
reports:
junit: "packages/$PACKAGE/integration-test-results.xml"
when: always
expire_in: 1 week
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "packages/$PACKAGE/**/*"
# E2E tests
test:e2e:
stage: test
image: cypress/browsers:node-18-chrome114
services:
- name: postgres:15-alpine
alias: postgres
variables:
POSTGRES_DB: e2e_test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
parallel:
matrix:
- PACKAGE: ["frontend", "admin"]
BROWSER: ["chrome", "firefox"]
script:
- echo "Running E2E tests for $PACKAGE with $BROWSER"
- cd packages/$PACKAGE
- npm ci --cache ../../.npm --prefer-offline
- npm run build
- npm run test:e2e -- --browser=$BROWSER
artifacts:
reports:
junit: "packages/$PACKAGE/cypress/results/junit.xml"
paths:
- "packages/$PACKAGE/cypress/videos/"
- "packages/$PACKAGE/cypress/screenshots/"
when: always
expire_in: 1 week
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "packages/$PACKAGE/**/*"
- "cypress/**/*"
# Performance tests
test:performance:
stage: test
image: node:18-alpine
script:
- echo "Running performance tests..."
- npm install -g artillery
- artillery run tests/performance/load-test.yml
artifacts:
reports:
performance: performance-report.json
paths:
- performance-report.json
expire_in: 1 week
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Security scanning with matrix
security:sast:
stage: security
extends: .sast-analyzer
parallel:
matrix:
- SCAN_TYPE: ["semgrep", "eslint", "typescript"]
PACKAGE: ["frontend", "backend", "api-gateway", "admin", "shared"]
variables:
SEARCH_MAX_DEPTH: "10"
ANALYZER_TARGET_DIR: "packages/$PACKAGE"
script:
- echo "Running $SCAN_TYPE scan for $PACKAGE"
- cd packages/$PACKAGE
- |
case "$SCAN_TYPE" in
"semgrep")
semgrep --config=auto --json --output=../../semgrep-$PACKAGE-report.json . || true
;;
"eslint")
npm run lint -- --format=json --output-file=../../eslint-$PACKAGE-report.json || true
;;
"typescript")
npm run type-check || true
;;
esac
artifacts:
reports:
sast: "semgrep-$PACKAGE-report.json"
paths:
- "semgrep-$PACKAGE-report.json"
- "eslint-$PACKAGE-report.json"
expire_in: 1 week
allow_failure: true
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Create releases
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
script:
- echo "Creating release for $CI_COMMIT_TAG"
- |
release-cli create --name "Release $CI_COMMIT_TAG" --description "Release created using the release-cli" --tag-name "$CI_COMMIT_TAG" --ref "$CI_COMMIT_SHA" --assets-link "{"name":"Frontend Build","url":"$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/artifacts/$CI_COMMIT_TAG/raw/packages/frontend/dist/?job=build:packages"}" --assets-link "{"name":"Backend Build","url":"$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/artifacts/$CI_COMMIT_TAG/raw/packages/backend/dist/?job=build:packages"}"
rules:
- if: $CI_COMMIT_TAG
dependencies:
- build:packages
# Deploy to environments
deploy:staging:
stage: deploy
environment:
name: staging
url: https://staging.example.com
on_stop: stop:staging
script:
- echo "Deploying to staging environment..."
- |
# Deploy using Helm
helm upgrade --install app-staging ./helm/app \
--namespace staging \
--set image.tag=$CI_COMMIT_SHA \
--set environment=staging \
--set ingress.hosts[0].host=staging.example.com \
--wait --timeout=10m
- echo "Staging deployment completed"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
dependencies:
- build:docker
- test:unit
- test:integration
# Stop staging environment
stop:staging:
stage: deploy
environment:
name: staging
action: stop
script:
- echo "Stopping staging environment..."
- helm uninstall app-staging --namespace staging || true
- echo "Staging environment stopped"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
# Deploy to production
deploy:production:
stage: deploy
environment:
name: production
url: https://example.com
script:
- echo "Deploying to production environment..."
- echo "Deploying version: $CI_COMMIT_SHA"
- |
# Blue-green deployment
./scripts/blue-green-deploy.sh \
--image-tag $CI_COMMIT_SHA \
--namespace production \
--release-name app-prod
- echo "Production deployment completed"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
dependencies:
- deploy:staging
# Performance monitoring after deployment
monitor:production:
stage: deploy
environment:
name: production
url: https://example.com
script:
- echo "Monitoring production deployment..."
- sleep 300 # Wait 5 minutes
- |
# Check application health
response=$(curl -s -o /dev/null -w "%{http_code}" https://example.com/health)
if [ "$response" != "200" ]; then
echo "Health check failed with status: $response"
exit 1
fi
- echo "Production monitoring completed successfully"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: on_success
dependencies:
- deploy:production
# Cleanup old artifacts and images
cleanup:
stage: deploy
image: alpine:3.18
script:
- echo "Cleaning up old resources..."
- |
# Clean old Docker images (requires proper authentication)
echo "Cleaning Docker images older than 30 days..."
# Add actual cleanup commands here
# Clean old job artifacts
echo "Pipeline cleanup completed"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: on_success
dependencies: []
💻 GitLab CI Pipeline Multi-Ambiente yaml
🔴 complex
⭐⭐⭐⭐⭐
Pipeline multi-ambiente abrangente com feature flags, deploy canary e auto-scaling
⏱️ 50 min
🏷️ gitlab-ci, multi-environment, canary, blue-green, monitoring
Prerequisites:
Advanced GitLab CI, Kubernetes, Helm, DevOps, Monitoring
# .gitlab-ci.yml - Multi-Environment Pipeline with Advanced Features
# Global variables and configuration
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
KUBERNETES_MEMORY_REQUEST: "1Gi"
KUBERNETES_MEMORY_LIMIT: "4Gi"
KUBERNETES_CPU_REQUEST: "500m"
KUBERNETES_CPU_LIMIT: "2000m"
# Pipeline stages
stages:
- setup
- validate
- build
- test
- security
- performance
- release
- deploy
- post-deploy
- cleanup
# Global workflow rules
workflow:
rules:
- if: $CI_COMMIT_MESSAGE =~ /^\[skip-ci\]/
when: never
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
variables:
DEPLOY_ENVIRONMENT: "production"
- if: $CI_COMMIT_BRANCH =~ /^release\/.*/
variables:
DEPLOY_ENVIRONMENT: "staging"
- if: $CI_COMMIT_BRANCH =~ /^feature\/.*/
variables:
DEPLOY_ENVIRONMENT: "development"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
DEPLOY_ENVIRONMENT: "review"
- if: $CI_COMMIT_TAG
variables:
DEPLOY_ENVIRONMENT: "production"
# Include templates
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
- template: Code-Quality.gitlab-ci.yml
- local: '/ci/templates/common.yml'
- local: '/ci/templates/deployments.yml'
# Environment-specific variables
variables:
# Development
DEV_NAMESPACE: "development"
DEV_INGRESS_HOST: "dev.example.com"
DEV_REPLICAS: "1"
# Staging
STAGING_NAMESPACE: "staging"
STAGING_INGRESS_HOST: "staging.example.com"
STAGING_REPLICAS: "2"
# Production
PROD_NAMESPACE: "production"
PROD_INGRESS_HOST: "example.com"
PROD_REPLICAS: "5"
# Setup stage jobs
setup:environment:
stage: setup
image: alpine:3.18
script:
- echo "Setting up environment for $DEPLOY_ENVIRONMENT"
- |
cat > environment.env << EOF
DEPLOY_ENVIRONMENT=$DEPLOY_ENVIRONMENT
NAMESPACE=$(eval "echo ${${DEPLOY_ENVIRONMENT^^}_NAMESPACE}")
INGRESS_HOST=$(eval "echo ${${DEPLOY_ENVIRONMENT^^}_INGRESS_HOST}")
REPLICAS=$(eval "echo ${${DEPLOY_ENVIRONMENT^^}_REPLICAS}")
CI_COMMIT_REF_SLUG=$CI_COMMIT_REF_SLUG
CI_COMMIT_SHORT_SHA=$CI_COMMIT_SHORT_SHA
CI_COMMIT_TIMESTAMP=$CI_COMMIT_TIMESTAMP
EOF
- cat environment.env
artifacts:
reports:
dotenv: environment.env
expire_in: 1 hour
rules:
- if: $DEPLOY_ENVIRONMENT
setup:infrastructure:
stage: setup
image: hashicorp/terraform:1.5
script:
- echo "Setting up infrastructure for $DEPLOY_ENVIRONMENT"
- cd infrastructure/$DEPLOY_ENVIRONMENT
- terraform init
- terraform fmt -check
- terraform validate
- terraform plan -out=terraform.plan
- terraform apply -auto-approve terraform.plan
environment:
name: $DEPLOY_ENVIRONMENT
url: https://$INGRESS_HOST
artifacts:
paths:
- "infrastructure/$DEPLOY_ENVIRONMENT/terraform.plan"
- "infrastructure/$DEPLOY_ENVIRONMENT/terraform.tfstate"
expire_in: 1 week
rules:
- if: $DEPLOY_ENVIRONMENT
changes:
- "infrastructure/**/*"
cache:
key: "terraform-$DEPLOY_ENVIRONMENT"
paths:
- "infrastructure/$DEPLOY_ENVIRONMENT/.terraform/"
# Validate configuration
validate:config:
stage: validate
image: node:18-alpine
script:
- echo "Validating configuration files..."
- npm ci
- npm run lint
- npm run type-check
- echo "Configuration validation completed"
artifacts:
reports:
codequality: gl-code-quality-report.json
paths:
- coverage/
expire_in: 1 week
rules:
- if: $DEPLOY_ENVIRONMENT
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
# Build applications
build:frontend:
stage: build
image: node:18-alpine
variables:
NODE_ENV: "production"
script:
- echo "Building frontend application..."
- cd frontend
- npm ci --cache ../.npm --prefer-offline
- npm run build
- npm run analyze
artifacts:
name: "frontend-$CI_COMMIT_SHORT_SHA"
paths:
- frontend/dist/
- frontend/build-stats.json
- frontend/bundle-analysis.html
expire_in: 2 weeks
reports:
dotenv: frontend/version.env
dependencies:
- setup:environment
rules:
- if: $DEPLOY_ENVIRONMENT
build:backend:
stage: build
image: node:18-alpine
script:
- echo "Building backend application..."
- cd backend
- npm ci --cache ../.npm --prefer-offline
- npm run build
- npm run package
artifacts:
name: "backend-$CI_COMMIT_SHORT_SHA"
paths:
- backend/dist/
- backend/package/
expire_in: 2 weeks
reports:
dotenv: backend/version.env
dependencies:
- setup:environment
rules:
- if: $DEPLOY_ENVIRONMENT
# Build Docker images
build:docker:
stage: build
image: docker:24.0.5
services:
- docker:24.0.5-dind
parallel:
matrix:
- SERVICE: ["frontend", "backend", "api-gateway"]
REGISTRY: ["$CI_REGISTRY"]
variables:
IMAGE_NAME: "$CI_REGISTRY_IMAGE/$SERVICE"
IMAGE_TAG: "$CI_COMMIT_SHA"
script:
- echo "Building Docker image for $SERVICE"
- echo "Image: $IMAGE_NAME:$IMAGE_TAG"
- |
docker buildx create --use
docker buildx build
--platform linux/amd64,linux/arm64
--build-arg BUILD_DATE="$CI_COMMIT_TIMESTAMP"
--build-arg VCS_REF="$CI_COMMIT_SHA"
--build-arg VERSION="$IMAGE_TAG"
--tag "$IMAGE_NAME:$IMAGE_TAG"
--tag "$IMAGE_NAME:latest"
--push
./$SERVICE/
- echo "Docker build completed for $SERVICE"
rules:
- if: $DEPLOY_ENVIRONMENT
exists:
- "$SERVICE/Dockerfile"
dependencies:
- build:frontend
- build:backend
# Testing stage
test:unit:
stage: test
image: node:18-alpine
parallel:
matrix:
- SERVICE: ["frontend", "backend", "api-gateway"]
COVERAGE: ["true"]
script:
- echo "Running unit tests for $SERVICE"
- cd $SERVICE
- npm ci --cache ../.npm --prefer-offline
- |
if [ "$COVERAGE" = "true" ]; then
npm run test:coverage
else
npm run test
fi
artifacts:
reports:
junit: "$SERVICE/test-results.xml"
coverage_report:
coverage_format: cobertura
path: "$SERVICE/coverage/cobertura-coverage.xml"
when: always
expire_in: 1 week
paths:
- "$SERVICE/coverage/"
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
rules:
- if: $DEPLOY_ENVIRONMENT
test:integration:
stage: test
image: node:18-alpine
services:
- name: postgres:15-alpine
alias: postgres
variables:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
- name: redis:7-alpine
alias: redis
variables:
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db"
REDIS_URL: "redis://redis:6379"
script:
- echo "Running integration tests..."
- cd backend
- npm ci --cache ../.npm --prefer-offline
- npm run test:integration
- echo "Integration tests completed"
artifacts:
reports:
junit: "backend/integration-test-results.xml"
when: always
expire_in: 1 week
rules:
- if: $DEPLOY_ENVIRONMENT
test:e2e:
stage: test
image: cypress/browsers:node-18-chrome114
services:
- name: postgres:15-alpine
alias: postgres
variables:
POSTGRES_DB: e2e_db
POSTGRES_USER: e2e_user
POSTGRES_PASSWORD: e2e_password
parallel:
matrix:
- SPEC: ["login", "dashboard", "profile", "admin"]
BROWSER: ["chrome", "firefox"]
variables:
CYPRESS_baseUrl: "http://backend:3000"
DATABASE_URL: "postgresql://e2e_user:e2e_password@postgres:5432/e2e_db"
script:
- echo "Running E2E tests for $SPEC with $BROWSER"
- cd frontend
- npm ci --cache ../.npm --prefer-offline
- npm run build
- npm run test:e2e -- --spec "cypress/e2e/$SPEC/**/*" --browser $BROWSER
artifacts:
reports:
junit: "frontend/cypress/results/junit.xml"
paths:
- "frontend/cypress/videos/"
- "frontend/cypress/screenshots/"
when: always
expire_in: 1 week
rules:
- if: $DEPLOY_ENVIRONMENT
# Performance testing
test:performance:
stage: performance
image: node:18-alpine
services:
- name: k6:latest
alias: k6
parallel:
matrix:
- TEST_TYPE: ["load", "stress", "spike"]
VUS: [10, 50, 100]
script:
- echo "Running $TEST_TYPE performance test with $VUS virtual users"
- npm install -g k6
- |
cat > test-config.js << EOF
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
stages: [
{ duration: '2m', target: $VUS },
{ duration: '5m', target: $VUS },
{ duration: '2m', target: 0 },
],
thresholds: {
http_req_duration: ['p(99)<1500'],
http_req_failed: ['rate<0.1'],
},
};
export default function () {
let response = http.get('https://$INGRESS_HOST/api/health');
check(response, {
'status was 200': (r) => r.status == 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
EOF
- k6 run test-config.js --out json=performance-$TEST_TYPE.json
artifacts:
reports:
performance: "performance-$TEST_TYPE.json"
paths:
- "performance-$TEST_TYPE.json"
expire_in: 1 week
rules:
- if: $DEPLOY_ENVIRONMENT == "staging" || $DEPLOY_ENVIRONMENT == "production"
# Security scanning
security:container:
stage: security
image: aquasec/trivy:latest
parallel:
matrix:
- IMAGE: ["frontend", "backend", "api-gateway"]
script:
- echo "Scanning container image for $IMAGE"
- trivy image --format json --output trivy-$IMAGE-report.json $CI_REGISTRY_IMAGE/$IMAGE:$CI_COMMIT_SHA
- trivy image --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE/$IMAGE:$CI_COMMIT_SHA
artifacts:
reports:
container_scanning: "trivy-$IMAGE-report.json"
paths:
- "trivy-$IMAGE-report.json"
expire_in: 1 week
allow_failure: true
rules:
- if: $DEPLOY_ENVIRONMENT
# Create release
release:
stage: release
image: node:18-alpine
script:
- echo "Creating release for $DEPLOY_ENVIRONMENT"
- npm ci
- |
# Generate release notes
cat > release-notes.md << EOF
# Release $CI_COMMIT_SHORT_SHA
## Environment: $DEPLOY_ENVIRONMENT
## Branch: $CI_COMMIT_REF_NAME
## Commit: $CI_COMMIT_SHA
## Timestamp: $CI_COMMIT_TIMESTAMP
### Changes
$(git log --oneline $(git describe --tags --abbrev=0)..HEAD)
### Image Tags
- Frontend: $CI_REGISTRY_IMAGE/frontend:$CI_COMMIT_SHA
- Backend: $CI_REGISTRY_IMAGE/backend:$CI_COMMIT_SHA
- API Gateway: $CI_REGISTRY_IMAGE/api-gateway:$CI_COMMIT_SHA
### Deployment
- Namespace: $NAMESPACE
- Ingress Host: $INGRESS_HOST
EOF
- cat release-notes.md
artifacts:
paths:
- "release-notes.md"
expire_in: 1 month
rules:
- if: $DEPLOY_ENVIRONMENT
dependencies:
- build:docker
# Deployment strategies
deploy:development:
stage: deploy
environment:
name: $DEPLOY_ENVIRONMENT
url: https://$INGRESS_HOST
on_stop: stop:development
script:
- echo "Deploying to $DEPLOY_ENVIRONMENT environment..."
- |
# Deploy using Helm
helm upgrade --install app-$NAMESPACE ./helm/app \
--namespace $NAMESPACE \
--create-namespace \
--set image.tag=$CI_COMMIT_SHA \
--set environment=$DEPLOY_ENVIRONMENT \
--set ingress.hosts[0].host=$INGRESS_HOST \
--set replicas=$REPLICAS \
--set resources.requests.memory="256Mi" \
--set resources.limits.memory="512Mi" \
--wait --timeout=10m
- |
# Configure feature flags
./scripts/configure-feature-flags.sh \
--environment $DEPLOY_ENVIRONMENT \
--config-file feature-flags/$DEPLOY_ENVIRONMENT.json
- echo "Deployment to $DEPLOY_ENVIRONMENT completed"
rules:
- if: $DEPLOY_ENVIRONMENT == "development" || $DEPLOY_ENVIRONMENT == "review"
dependencies:
- release
deploy:canary:
stage: deploy
environment:
name: canary
url: https://canary.example.com
script:
- echo "Starting canary deployment..."
- |
# Deploy 10% traffic to canary
helm upgrade --install app-canary ./helm/app \
--namespace canary \
--create-namespace \
--set image.tag=$CI_COMMIT_SHA \
--set environment=canary \
--set ingress.hosts[0].host=canary.example.com \
--set replicas=1 \
--set canary.enabled=true \
--set canary.traffic=10 \
--wait --timeout=10m
- |
# Monitor canary for 10 minutes
echo "Monitoring canary deployment..."
for i in {1..20}; do
response=$(curl -s -o /dev/null -w "%{http_code}" https://canary.example.com/health)
if [ "$response" != "200" ]; then
echo "Canary health check failed: $response"
helm uninstall app-canary --namespace canary
exit 1
fi
echo "Canary health check passed ($i/20)"
sleep 30
done
- echo "Canary deployment successful"
rules:
- if: $DEPLOY_ENVIRONMENT == "production"
when: manual
dependencies:
- release
deploy:staging:
stage: deploy
environment:
name: staging
url: https://staging.example.com
on_stop: stop:staging
script:
- echo "Deploying to staging environment..."
- |
# Blue-green deployment
./scripts/blue-green-deploy.sh \
--namespace $STAGING_NAMESPACE \
--image-tag $CI_COMMIT_SHA \
--ingress-host $STAGING_INGRESS_HOST \
--replicas $STAGING_REPLICAS
- echo "Staging deployment completed"
rules:
- if: $DEPLOY_ENVIRONMENT == "staging"
dependencies:
- release
deploy:production:
stage: deploy
environment:
name: production
url: https://example.com
script:
- echo "Deploying to production environment..."
- |
# Manual approval for production
echo "⚠️ DEPLOYING TO PRODUCTION ⚠️"
echo "Image tag: $CI_COMMIT_SHA"
echo "Environment: $PROD_NAMESPACE"
echo "Ingress: $PROD_INGRESS_HOST"
- |
# Progressive deployment with auto-scaling
./scripts/progressive-deploy.sh \
--namespace $PROD_NAMESPACE \
--image-tag $CI_COMMIT_SHA \
--ingress-host $PROD_INGRESS_HOST \
--initial-replicas $PROD_REPLICAS \
--max-replicas 20 \
--cpu-threshold 70 \
--memory-threshold 80
- echo "Production deployment completed"
rules:
- if: $DEPLOY_ENVIRONMENT == "production"
when: manual
dependencies:
- deploy:canary
# Post-deployment validation
validate:deployment:
stage: post-deploy
image: alpine:3.18
script:
- echo "Validating deployment in $DEPLOY_ENVIRONMENT..."
- apk add --no-cache curl jq
- |
# Health check
response=$(curl -s -o /dev/null -w "%{http_code}" https://$INGRESS_HOST/health)
if [ "$response" != "200" ]; then
echo "❌ Health check failed: $response"
exit 1
fi
echo "✅ Health check passed"
- |
# API functionality check
api_response=$(curl -s https://$INGRESS_HOST/api/version)
version_check=$(echo $api_response | jq -r '.version // empty')
if [ -z "$version_check" ]; then
echo "❌ API version check failed"
exit 1
fi
echo "✅ API functionality check passed: $version_check"
- echo "Deployment validation completed successfully"
rules:
- if: $DEPLOY_ENVIRONMENT
when: on_success
monitor:performance:
stage: post-deploy
image: node:18-alpine
script:
- echo "Monitoring performance after deployment..."
- npm install -g artillery
- |
# Performance monitoring
artillery run tests/performance/monitor.yml --output monitor-results.json
cat monitor-results.json | jq '.aggregate.latency.p99' > p99-latency.txt
P99_LATENCY=$(cat p99-latency.txt)
echo "P99 Latency: ${P99_LATENCY}ms"
# Alert if latency is too high
if (( $(echo "$P99_LATENCY > 1000" | bc -l) )); then
echo "⚠️ High P99 latency detected: ${P99_LATENCY}ms"
# Send alert notification
curl -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-type: application/json' \
--data '{
"text": "⚠️ High latency detected in production: '${P99_LATENCY}'ms"
}'
fi
artifacts:
paths:
- "monitor-results.json"
- "p99-latency.txt"
expire_in: 1 week
rules:
- if: $DEPLOY_ENVIRONMENT == "production"
when: on_success
# Environment stop jobs
stop:development:
stage: cleanup
environment:
name: development
action: stop
script:
- echo "Stopping development environment..."
- helm uninstall app-development --namespace $DEV_NAMESPACE || true
- kubectl delete namespace $DEV_NAMESPACE || true
- echo "Development environment stopped"
rules:
- if: $DEPLOY_ENVIRONMENT == "development"
when: manual
dependencies: []
stop:staging:
stage: cleanup
environment:
name: staging
action: stop
script:
- echo "Stopping staging environment..."
- helm uninstall app-staging --namespace $STAGING_NAMESPACE || true
- kubectl delete namespace $STAGING_NAMESPACE || true
- echo "Staging environment stopped"
rules:
- if: $DEPLOY_ENVIRONMENT == "staging"
when: manual
dependencies: []
# Cleanup jobs
cleanup:old-images:
stage: cleanup
image: docker:24.0.5
script:
- echo "Cleaning up old Docker images..."
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- |
# Keep only last 10 images
docker images $CI_REGISTRY_IMAGE --format "table {{.Repository}}:{{.Tag}}" | grep -v latest | tail -n +11 | awk '{print $1}' | xargs -r docker rmi || true
- echo "Old images cleanup completed"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: on_success
dependencies: []
cleanup:old-artifacts:
stage: cleanup
image: alpine:3.18
script:
- echo "Cleaning up old artifacts..."
- |
# Clean old GitLab artifacts (requires API token)
echo "Cleaning artifacts older than 30 days..."
# Add actual cleanup commands using GitLab API
- echo "Artifacts cleanup completed"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: on_success
dependencies: []