# 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