GitLab CI Integration

GitLab CI/CD provides a powerful platform for automating GitOps workflows. It can build container images, update manifests, run tests, and trigger deployments while maintaining GitOps principles.

GitOps + GitLab CI Flow

graph TB A[Developer Push] --> B[GitLab CI Pipeline] B --> C[Build Image] C --> D[Push to Registry] D --> E[Update Manifest] E --> F[Git Commit] F --> G[GitOps Controller] G --> H[Deploy to Cluster] style B fill:#e1f5ff style C fill:#e8f5e9 style E fill:#fff4e1 style G fill:#f3e5f5

Basic Pipeline

.gitlab-ci.yml Structure

# .gitlab-ci.yml
stages:
  - build
  - test
  - update
  - deploy

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"

build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  only:
    - main
    - develop

Build and Push Image

build-image:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - |
      docker build \
        -t $CI_REGISTRY_IMAGE/web-app:$CI_COMMIT_SHA \
        -t $CI_REGISTRY_IMAGE/web-app:latest \
        ./apps/web-app
    - docker push $CI_REGISTRY_IMAGE/web-app:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE/web-app:latest
  only:
    - main
    - develop

Manifest Update Automation

Update Kustomize Manifest

update-manifest:
  stage: update
  image: bitnami/kubectl:latest
  before_script:
    - apk add --no-cache git
    - git config user.name "GitLab CI"
    - git config user.email "[email protected]"
  script:
    - |
      cd apps/web-app/overlays/production
      kustomize edit set image \
        web-app=$CI_REGISTRY_IMAGE/web-app:$CI_COMMIT_SHA
    - git add apps/web-app/overlays/production/kustomization.yaml
    - git commit -m "Update web-app image to $CI_COMMIT_SHA"
    - git push https://oauth2:$CI_JOB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git HEAD:$CI_COMMIT_REF_NAME
  only:
    - main
  when: on_success

Update with Kustomize Image

update-kustomize:
  stage: update
  image: bitnami/kustomize:latest
  before_script:
    - apk add --no-cache git
    - git config user.name "GitLab CI"
    - git config user.email "[email protected]"
  script:
    - |
      NEW_IMAGE="$CI_REGISTRY_IMAGE/web-app:$CI_COMMIT_SHA"
      cd apps/web-app/overlays/production
      kustomize edit set image web-app=$NEW_IMAGE
      git add kustomization.yaml
      git commit -m "chore: update web-app image to $CI_COMMIT_SHA"
      git push https://oauth2:$CI_JOB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git HEAD:$CI_COMMIT_REF_NAME
  only:
    - main

Manifest Validation

Validate Kubernetes Manifests

validate-manifests:
  stage: test
  image: bitnami/kubectl:latest
  script:
    - |
      for file in $(find apps -name "*.yaml" -o -name "*.yml"); do
        kubectl apply --dry-run=client -f $file
      done
  only:
    - merge_requests

Kustomize Build Validation

validate-kustomize:
  stage: test
  image: bitnami/kustomize:latest
  script:
    - |
      cd apps/web-app/overlays/production
      kustomize build . | kubectl apply --dry-run=client -f -
  only:
    - merge_requests

Security Scanning

Container Image Scanning

scan-image:
  stage: test
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - |
      trivy image \
        --format template \
        --template "@contrib/gitlab.tpl" \
        --output gl-container-scanning-report.json \
        $CI_REGISTRY_IMAGE/web-app:$CI_COMMIT_SHA
  artifacts:
    reports:
      container_scanning: gl-container-scanning-report.json
  only:
    - main
    - develop

Manifest Security Scanning

scan-manifests:
  stage: test
  image: bridgecrew/checkov:latest
  script:
    - |
      checkov -d apps/ \
        --framework kubernetes \
        --output json \
        --output-file-path checkov-report.json
  artifacts:
    reports:
      sast: checkov-report.json
  only:
    - merge_requests

Environment Promotion

Manual Promotion

promote-to-production:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - |
      # Update production manifest
      cd apps/web-app/overlays/production
      STAGING_IMAGE=$(grep newImage ../staging/kustomization.yaml | cut -d: -f2-3)
      kustomize edit set image web-app=$STAGING_IMAGE
      git add kustomization.yaml
      git commit -m "Promote web-app to production"
      git push https://oauth2:$CI_JOB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git HEAD:main
  environment:
    name: production
  when: manual
  only:
    - main

Automated Promotion with Tests

test-staging:
  stage: test
  image: curlimages/curl:latest
  script:
    - |
      # Run integration tests against staging
      STAGING_URL="https://staging.example.com"
      curl -f $STAGING_URL/health || exit 1
  environment:
    name: staging
  only:
    - staging

promote-to-production:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - |
      cd apps/web-app/overlays/production
      kustomize edit set image web-app=$CI_REGISTRY_IMAGE/web-app:$CI_COMMIT_SHA
      git add kustomization.yaml
      git commit -m "Promote web-app $CI_COMMIT_SHA to production"
      git push https://oauth2:$CI_JOB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git HEAD:main
  environment:
    name: production
  only:
    - staging
  when: on_success

ArgoCD Integration

Trigger ArgoCD Sync

sync-argocd:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - |
      kubectl patch application web-app \
        -n argocd \
        --type merge \
        -p '{"operation":{"initiatedBy":{"username":"gitlab-ci"},"sync":{"revision":"'$CI_COMMIT_SHA'"}}}'
  only:
    - main
  environment:
    name: production

ArgoCD Webhook

trigger-argocd:
  stage: deploy
  image: curlimages/curl:latest
  script:
    - |
      curl -X POST $ARGOCD_WEBHOOK_URL \
        -H "Authorization: Bearer $ARGOCD_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{"revision":"'$CI_COMMIT_SHA'"}'
  only:
    - main

Flux Integration

Update Flux Image Policy

update-flux:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - |
      kubectl patch imagepolicy web-app \
        -n flux-system \
        --type json \
        -p='[{"op": "replace", "path": "/spec/policy/semver/range", "value": ">='$CI_COMMIT_SHA'"}]'
  only:
    - main

Complete Pipeline Example

# .gitlab-ci.yml
stages:
  - build
  - test
  - scan
  - update
  - deploy

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"

build-image:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - |
      docker build \
        -t $CI_REGISTRY_IMAGE/web-app:$CI_COMMIT_SHA \
        -t $CI_REGISTRY_IMAGE/web-app:latest \
        ./apps/web-app
    - docker push $CI_REGISTRY_IMAGE/web-app:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE/web-app:latest
  only:
    - main
    - develop

unit-tests:
  stage: test
  image: node:18
  script:
    - cd apps/web-app
    - npm install
    - npm test
  only:
    - merge_requests
    - main

scan-image:
  stage: scan
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - |
      trivy image \
        --format template \
        --template "@contrib/gitlab.tpl" \
        --output gl-container-scanning-report.json \
        $CI_REGISTRY_IMAGE/web-app:$CI_COMMIT_SHA
  artifacts:
    reports:
      container_scanning: gl-container-scanning-report.json
  only:
    - main
    - develop

update-manifest:
  stage: update
  image: bitnami/kustomize:latest
  before_script:
    - apk add --no-cache git
    - git config user.name "GitLab CI"
    - git config user.email "[email protected]"
  script:
    - |
      NEW_IMAGE="$CI_REGISTRY_IMAGE/web-app:$CI_COMMIT_SHA"
      cd apps/web-app/overlays/production
      kustomize edit set image web-app=$NEW_IMAGE
      git add kustomization.yaml
      git commit -m "chore: update web-app image to $CI_COMMIT_SHA"
      git push https://oauth2:$CI_JOB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git HEAD:$CI_COMMIT_REF_NAME
  only:
    - main
  when: on_success

sync-argocd:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - |
      kubectl patch application web-app \
        -n argocd \
        --type merge \
        -p '{"operation":{"initiatedBy":{"username":"gitlab-ci"},"sync":{"revision":"'$CI_COMMIT_SHA'"}}}'
  only:
    - main
  environment:
    name: production

GitLab CI Variables

Using CI/CD Variables

variables:
  REGISTRY: registry.example.com
  IMAGE_NAME: web-app

build:
  script:
    - docker build -t $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA .

Protected Variables

Store sensitive data in GitLab CI/CD variables:

  • Settings → CI/CD → Variables
  • Mark as “Protected” for protected branches
  • Mark as “Masked” to hide in logs

Best Practices

1. Use GitLab Container Registry

variables:
  CI_REGISTRY: $CI_REGISTRY
  CI_REGISTRY_IMAGE: $CI_REGISTRY_IMAGE
  CI_REGISTRY_USER: $CI_REGISTRY_USER
  CI_REGISTRY_PASSWORD: $CI_REGISTRY_PASSWORD

2. Cache Dependencies

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/
    - .m2/

build:
  script:
    - npm install  # Uses cache if available

3. Use Parallel Jobs

test:
  parallel:
    matrix:
      - TEST_SUITE: [unit, integration, e2e]
  script:
    - ./run-tests.sh $TEST_SUITE

4. Limit Job Scope

update-manifest:
  only:
    - main
    changes:
      - apps/web-app/**

5. Use Environments

deploy:
  environment:
    name: production
    url: https://prod.example.com
  script:
    - ./deploy.sh

6. Protect Production

deploy-production:
  only:
    - main
  when: manual  # Requires manual trigger
  environment:
    name: production

Troubleshooting

Pipeline Not Running

  • Check .gitlab-ci.yml syntax
  • Verify branch protection rules
  • Check CI/CD settings

Permission Denied

# Add deploy token or use CI_JOB_TOKEN
git push https://oauth2:$CI_JOB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git

Secrets Not Available

  • Verify variables are set in CI/CD settings
  • Check if variables are protected/masked
  • Ensure correct variable names

Summary

GitLab CI + GitOps integration:

  • Build Images - Automated container builds
  • Update Manifests - Update GitOps manifests automatically
  • Validate - Test manifests before deployment
  • Scan - Security scanning for images and manifests
  • Promote - Environment promotion workflows
  • Sync - Trigger GitOps controller syncs

Best practices:

  • Use GitLab Container Registry
  • Cache dependencies
  • Use parallel jobs
  • Limit job scope
  • Use environments
  • Protect production deployments

GitLab CI provides a complete CI/CD solution that integrates seamlessly with GitOps workflows, providing automation while maintaining GitOps principles.