Files
gh-ahmedasmar-devops-claude…/assets/templates/gitlab-ci/docker-build.yml
2025-11-29 17:51:12 +08:00

299 lines
7.7 KiB
YAML

# GitLab CI/CD Docker Build Pipeline
# Multi-platform build with DinD, security scanning, and Container Registry
stages:
- build
- scan
- sign
- deploy
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
# Use project's container registry
IMAGE_NAME: $CI_REGISTRY_IMAGE
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
# BuildKit for better caching
DOCKER_BUILDKIT: 1
# Build multi-platform Docker image
build:
stage: build
image: docker:24-cli
services:
- docker:24-dind
before_script:
# Login to GitLab Container Registry
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
# Set up buildx for multi-platform builds
- docker buildx create --use --name builder || docker buildx use builder
- docker buildx inspect --bootstrap
script:
# Build and push multi-platform image
- |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--cache-from type=registry,ref=$IMAGE_NAME:buildcache \
--cache-to type=registry,ref=$IMAGE_NAME:buildcache,mode=max \
--tag $IMAGE_NAME:$IMAGE_TAG \
--tag $IMAGE_NAME:latest \
--push \
--build-arg CI_COMMIT_SHA=$CI_COMMIT_SHA \
--build-arg CI_COMMIT_REF_NAME=$CI_COMMIT_REF_NAME \
.
- echo "IMAGE_FULL_NAME=$IMAGE_NAME:$IMAGE_TAG" >> build.env
artifacts:
reports:
dotenv: build.env
only:
- branches
- tags
tags:
- docker
# Alternative: Simple build without multi-arch
build:simple:
stage: build
image: docker:24-cli
services:
- docker:24-dind
before_script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
script:
# Pull previous image for layer caching
- docker pull $IMAGE_NAME:latest || true
# Build with cache
- |
docker build \
--cache-from $IMAGE_NAME:latest \
--tag $IMAGE_NAME:$IMAGE_TAG \
--tag $IMAGE_NAME:latest \
--build-arg BUILDKIT_INLINE_CACHE=1 \
.
# Push images
- docker push $IMAGE_NAME:$IMAGE_TAG
- docker push $IMAGE_NAME:latest
- echo "IMAGE_FULL_NAME=$IMAGE_NAME:$IMAGE_TAG" >> build.env
artifacts:
reports:
dotenv: build.env
only:
- branches
- tags
when: manual # Use this OR the multi-arch build above
tags:
- docker
# Trivy vulnerability scanning
trivy:scan:
stage: scan
image: aquasec/trivy:latest
needs: [build]
variables:
GIT_STRATEGY: none
script:
# Scan for HIGH and CRITICAL vulnerabilities
- trivy image --severity HIGH,CRITICAL --exit-code 0 --format json --output trivy-report.json $IMAGE_FULL_NAME
- trivy image --severity HIGH,CRITICAL --exit-code 1 $IMAGE_FULL_NAME
artifacts:
when: always
paths:
- trivy-report.json
expire_in: 30 days
allow_failure: false
only:
- branches
- tags
# Grype scanning (alternative/additional)
grype:scan:
stage: scan
image: anchore/grype:latest
needs: [build]
variables:
GIT_STRATEGY: none
before_script:
- echo $CI_REGISTRY_PASSWORD | grype registry login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
script:
- grype $IMAGE_FULL_NAME --fail-on high --output json --file grype-report.json
artifacts:
when: always
paths:
- grype-report.json
expire_in: 30 days
allow_failure: true
only:
- branches
- tags
# GitLab Container Scanning (uses Trivy)
container_scanning:
stage: scan
needs: [build]
variables:
CS_IMAGE: $IMAGE_FULL_NAME
GIT_STRATEGY: none
allow_failure: true
include:
- template: Security/Container-Scanning.gitlab-ci.yml
# Generate SBOM (Software Bill of Materials)
sbom:
stage: scan
image: anchore/syft:latest
needs: [build]
variables:
GIT_STRATEGY: none
before_script:
- echo $CI_REGISTRY_PASSWORD | syft registry login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
script:
- syft $IMAGE_FULL_NAME -o spdx-json > sbom.spdx.json
- syft $IMAGE_FULL_NAME -o cyclonedx-json > sbom.cyclonedx.json
artifacts:
paths:
- sbom.spdx.json
- sbom.cyclonedx.json
expire_in: 90 days
only:
- main
- tags
# Sign container image (requires cosign setup)
sign:image:
stage: sign
image: gcr.io/projectsigstore/cosign:latest
needs: [build, trivy:scan]
variables:
GIT_STRATEGY: none
before_script:
- echo $CI_REGISTRY_PASSWORD | cosign login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
script:
# Sign using keyless mode with OIDC
- cosign sign --yes $IMAGE_FULL_NAME
only:
- main
- tags
when: manual # Require manual approval for signing
# Deploy to Kubernetes
deploy:staging:
stage: deploy
image: bitnami/kubectl:latest
needs: [build, trivy:scan]
environment:
name: staging
url: https://staging.example.com
on_stop: stop:staging
before_script:
- kubectl config use-context staging-cluster
script:
- kubectl set image deployment/myapp myapp=$IMAGE_FULL_NAME --namespace=staging --record
- kubectl rollout status deployment/myapp --namespace=staging --timeout=5m
# Verify deployment
- |
POD=$(kubectl get pod -n staging -l app=myapp -o jsonpath="{.items[0].metadata.name}")
kubectl exec -n staging $POD -- curl -f http://localhost:8080/health || exit 1
only:
- develop
when: manual
stop:staging:
stage: deploy
image: bitnami/kubectl:latest
environment:
name: staging
action: stop
script:
- kubectl scale deployment/myapp --replicas=0 --namespace=staging
when: manual
only:
- develop
deploy:production:
stage: deploy
image: bitnami/kubectl:latest
needs: [build, trivy:scan, sign:image]
environment:
name: production
url: https://example.com
before_script:
- kubectl config use-context production-cluster
script:
- |
echo "Deploying version $IMAGE_TAG to production"
- kubectl set image deployment/myapp myapp=$IMAGE_FULL_NAME --namespace=production --record
- kubectl rollout status deployment/myapp --namespace=production --timeout=5m
# Health check
- sleep 10
- |
for i in {1..10}; do
POD=$(kubectl get pod -n production -l app=myapp -o jsonpath="{.items[0].metadata.name}")
if kubectl exec -n production $POD -- curl -f http://localhost:8080/health; then
echo "Health check passed"
exit 0
fi
echo "Attempt $i failed, retrying..."
sleep 10
done
echo "Health check failed"
exit 1
only:
- main
when: manual # Require manual approval for production
# Create GitLab release
release:
stage: deploy
image: registry.gitlab.com/gitlab-org/release-cli:latest
needs: [deploy:production]
script:
- echo "Creating release for $CI_COMMIT_TAG"
release:
tag_name: $CI_COMMIT_TAG
description: |
Docker Image: $IMAGE_FULL_NAME
Changes in this release:
$CI_COMMIT_MESSAGE
only:
- tags
# Cleanup old images from Container Registry
cleanup:registry:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache curl jq
script:
- |
# Keep last 10 images, delete older ones
echo "Cleaning up old container images..."
# Use GitLab API to manage container registry
# This is a placeholder - implement based on your retention policy
only:
- schedules
when: manual
# Workflow rules
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_BRANCH == "develop"'
- if: '$CI_COMMIT_TAG'
- if: '$CI_PIPELINE_SOURCE == "schedule"'
# Additional optimizations
.interruptible_jobs:
interruptible: true
build:
extends: .interruptible_jobs
trivy:scan:
extends: .interruptible_jobs