Kiro Methodology

8. GitLab CI/CD Pipeline Templates

← Back to Table of Contents | Previous: Terraform Infrastructure | Next: Workflow New Product →


8.1 Pipeline Architecture

The CI/CD pipeline is split into reusable templates that are included in the main .gitlab-ci.yml.

.gitlab-ci.yml (main orchestrator)
    │
    ├── ci/templates/lint.yml
    ├── ci/templates/test.yml
    ├── ci/templates/build.yml
    ├── ci/templates/security-scan.yml
    └── ci/templates/deploy.yml

8.2 Main Pipeline File

File: .gitlab-ci.yml (application repo)

stages:
  - lint
  - test
  - build
  - security
  - deploy-dev
  - deploy-staging
  - deploy-prod

include:
  - local: ci/templates/lint.yml
  - local: ci/templates/test.yml
  - local: ci/templates/build.yml
  - local: ci/templates/security-scan.yml
  - local: ci/templates/deploy.yml

variables:
  PROJECT_NAME: myapp
  AWS_REGION: eu-west-1
  NODE_IMAGE: node:20-alpine

8.3 Lint Template

File: ci/templates/lint.yml

.lint-base:
  stage: lint
  image: ${NODE_IMAGE}
  before_script:
    - npm ci --prefer-offline

lint:
  extends: .lint-base
  script:
    - npx eslint src/ --max-warnings 0
    - npx prettier --check src/
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

type-check:
  extends: .lint-base
  script:
    - npx tsc --noEmit
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

8.4 Test Template

File: ci/templates/test.yml

.test-base:
  stage: test
  image: ${NODE_IMAGE}
  before_script:
    - npm ci --prefer-offline
  coverage: '/All files\s*\|\s*(\d+\.?\d*)\s*\|/'
  artifacts:
    reports:
      junit: test-results/junit.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
    when: always

unit-tests:
  extends: .test-base
  script:
    - npm run test -- --run --reporter=junit --outputFile=test-results/junit.xml --coverage
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

integration-tests:
  extends: .test-base
  script:
    - npm run test:integration -- --run
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

8.5 Build Template

File: ci/templates/build.yml

.build-base:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  before_script:
    - aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY}

build-auth:
  extends: .build-base
  script:
    - docker build -t ${ECR_REGISTRY}/${PROJECT_NAME}-auth:${CI_COMMIT_SHORT_SHA} -f docker/auth/Dockerfile .
    - docker push ${ECR_REGISTRY}/${PROJECT_NAME}-auth:${CI_COMMIT_SHORT_SHA}
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      changes:
        - src/auth/**/*
        - docker/auth/**/*

build-billing:
  extends: .build-base
  script:
    - docker build -t ${ECR_REGISTRY}/${PROJECT_NAME}-billing:${CI_COMMIT_SHORT_SHA} -f docker/billing/Dockerfile .
    - docker push ${ECR_REGISTRY}/${PROJECT_NAME}-billing:${CI_COMMIT_SHORT_SHA}
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      changes:
        - src/billing/**/*
        - docker/billing/**/*

build-orders:
  extends: .build-base
  script:
    - docker build -t ${ECR_REGISTRY}/${PROJECT_NAME}-orders:${CI_COMMIT_SHORT_SHA} -f docker/orders/Dockerfile .
    - docker push ${ECR_REGISTRY}/${PROJECT_NAME}-orders:${CI_COMMIT_SHORT_SHA}
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      changes:
        - src/orders/**/*
        - docker/orders/**/*

8.6 Security Scan Template

File: ci/templates/security-scan.yml

security-scan:
  stage: security
  image: ${NODE_IMAGE}
  script:
    - npm audit --audit-level=high
    - npx snyk test || true
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"
  allow_failure: false

8.7 Deploy Template

File: ci/templates/deploy.yml

.deploy-base:
  image:
    name: hashicorp/terraform:1.6
    entrypoint: [""]
  before_script:
    - cd infra/environments/${DEPLOY_ENV}
    - terraform init -input=false

deploy-dev:
  extends: .deploy-base
  stage: deploy-dev
  variables:
    DEPLOY_ENV: dev
  script:
    - terraform plan -input=false -out=tfplan
    - terraform apply -input=false tfplan
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: dev

deploy-staging:
  extends: .deploy-base
  stage: deploy-staging
  variables:
    DEPLOY_ENV: staging
  script:
    - terraform plan -input=false -out=tfplan
    - terraform apply -input=false tfplan
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: staging
  when: manual

deploy-prod:
  extends: .deploy-base
  stage: deploy-prod
  variables:
    DEPLOY_ENV: prod
  script:
    - terraform plan -input=false -out=tfplan
    - terraform apply -input=false tfplan
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production
  when: manual

8.8 Infrastructure Pipeline

File: .gitlab-ci.yml (infra repo)

stages:
  - validate
  - plan
  - apply

variables:
  TF_VERSION: "1.6"

validate:
  stage: validate
  image:
    name: hashicorp/terraform:${TF_VERSION}
    entrypoint: [""]
  script:
    - terraform fmt -check -recursive
    - |
      for env in environments/*/; do
        cd $env
        terraform init -backend=false
        terraform validate
        cd ../..
      done
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

plan:
  stage: plan
  image:
    name: hashicorp/terraform:${TF_VERSION}
    entrypoint: [""]
  script:
    - cd environments/${TF_ENV}
    - terraform init -input=false
    - terraform plan -input=false -out=tfplan
  artifacts:
    paths:
      - environments/${TF_ENV}/tfplan
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

apply:
  stage: apply
  image:
    name: hashicorp/terraform:${TF_VERSION}
    entrypoint: [""]
  script:
    - cd environments/${TF_ENV}
    - terraform init -input=false
    - terraform apply -input=false tfplan
  dependencies:
    - plan
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  when: manual

8.9 Pipeline Flow Summary

PR opened
    ↓
lint + type-check (parallel)
    ↓
unit-tests + integration-tests (parallel)
    ↓
security-scan
    ↓
[merge to main]
    ↓
build docker images (only changed modules)
    ↓
deploy-dev (automatic)
    ↓
deploy-staging (manual approval)
    ↓
deploy-prod (manual approval)

← Back to Table of Contents | Previous: Terraform Infrastructure | Next: Workflow New Product →