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.ymlsyntax - 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.