GitLab Deploy Pipeline | Complete CI/CD Deployment Guide
GitLab CI/CD deployment pipelines automate the process of deploying applications to various environments. This guide covers deploying Angular UIs, .NET Core microservices, and databases using Liquibase.
🅰️ How to Deploy Angular UI
Section titled “🅰️ How to Deploy Angular UI”Deploying Angular applications involves building the production bundle and serving it through web servers or cloud platforms.
Basic Angular Deployment to Static Hosting
Section titled “Basic Angular Deployment to Static Hosting”stages:  - build  - deploy
variables:  NODE_VERSION: "18"
deploy_angular_staging:  stage: deploy  image: node:${NODE_VERSION}  dependencies:    - build_angular  script:    - npm install -g @angular/cli    - echo "Deploying to staging environment"    - aws s3 sync dist/ s3://$STAGING_S3_BUCKET --delete    - aws cloudfront create-invalidation --distribution-id $STAGING_CLOUDFRONT_ID --paths "/*"  environment:    name: staging    url: https://staging.myapp.com  only:    - develop
deploy_angular_production:  stage: deploy  image: node:${NODE_VERSION}  dependencies:    - build_angular  script:    - npm install -g @angular/cli    - echo "Deploying to production environment"    - aws s3 sync dist/ s3://$PRODUCTION_S3_BUCKET --delete    - aws cloudfront create-invalidation --distribution-id $PRODUCTION_CLOUDFRONT_ID --paths "/*"  environment:    name: production    url: https://myapp.com  when: manual  only:    - mainAngular Deployment with Docker
Section titled “Angular Deployment with Docker”deploy_angular_docker:  stage: deploy  image: docker:20.10.16  services:    - docker:20.10.16-dind  variables:    DOCKER_TLS_CERTDIR: "/certs"  script:    # Build Docker image    - docker build -t $CI_REGISTRY_IMAGE/angular-ui:$CI_COMMIT_SHA .    - docker tag $CI_REGISTRY_IMAGE/angular-ui:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE/angular-ui:latest
    # Push to registry    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY    - docker push $CI_REGISTRY_IMAGE/angular-ui:$CI_COMMIT_SHA    - docker push $CI_REGISTRY_IMAGE/angular-ui:latest
    # Deploy to Kubernetes    - kubectl set image deployment/angular-ui angular-ui=$CI_REGISTRY_IMAGE/angular-ui:$CI_COMMIT_SHA    - kubectl rollout status deployment/angular-ui  environment:    name: production    url: https://myapp.comAngular Deployment with Environment-Specific Configurations
Section titled “Angular Deployment with Environment-Specific Configurations”.deploy_angular_template: &deploy_angular  image: node:18  dependencies:    - build_angular  script:    - echo "Deploying Angular UI to $ENVIRONMENT"    - |      if [ "$ENVIRONMENT" = "staging" ]; then        ng build --configuration=staging      else        ng build --configuration=production      fi    - aws s3 sync dist/ s3://$S3_BUCKET --delete    - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_ID --paths "/*"
deploy_angular_staging:  <<: *deploy_angular  stage: deploy  variables:    ENVIRONMENT: "staging"    S3_BUCKET: $STAGING_S3_BUCKET    CLOUDFRONT_ID: $STAGING_CLOUDFRONT_ID  environment:    name: staging    url: https://staging.myapp.com  only:    - develop
deploy_angular_production:  <<: *deploy_angular  stage: deploy  variables:    ENVIRONMENT: "production"    S3_BUCKET: $PRODUCTION_S3_BUCKET    CLOUDFRONT_ID: $PRODUCTION_CLOUDFRONT_ID  environment:    name: production    url: https://myapp.com  when: manual  only:    - mainAngular Deployment with Nginx
Section titled “Angular Deployment with Nginx”deploy_angular_nginx:  stage: deploy  image: nginx:alpine  dependencies:    - build_angular  script:    - cp -r dist/* /usr/share/nginx/html/    - |      cat > /etc/nginx/conf.d/default.conf << 'EOF'      server {          listen 80;          server_name localhost;          root /usr/share/nginx/html;          index index.html;
          location / {              try_files $uri $uri/ /index.html;          }
          location /api/ {              proxy_pass http://backend-service:8080/;              proxy_set_header Host $host;              proxy_set_header X-Real-IP $remote_addr;          }      }      EOF    - nginx -g 'daemon off;'  environment:    name: production    url: https://myapp.com🔷 How to Deploy .NET Core Microservices
Section titled “🔷 How to Deploy .NET Core Microservices”Deploying .NET Core microservices involves containerization, orchestration, and proper service mesh configuration.
Basic .NET Core Deployment
Section titled “Basic .NET Core Deployment”deploy_dotnet_service:  stage: deploy  image: mcr.microsoft.com/dotnet/sdk:8.0  dependencies:    - build_dotnet  script:    - cd publish/    - dotnet MyService.dll &    - echo "Service deployed successfully"  environment:    name: production    url: https://api.myapp.com.NET Core Microservices with Docker Deployment
Section titled “.NET Core Microservices with Docker Deployment”variables:  DOCKER_REGISTRY: "registry.gitlab.com/mygroup/myproject"  SERVICES: "userservice orderservice paymentservice"
.deploy_microservice_template: &deploy_microservice  stage: deploy  image: docker:20.10.16  services:    - docker:20.10.16-dind  variables:    DOCKER_TLS_CERTDIR: "/certs"  before_script:    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY  script:    - |      for service in $SERVICES; do        echo "Deploying $service..."
        # Build and tag image        docker build -f src/$service/Dockerfile -t $DOCKER_REGISTRY/$service:$CI_COMMIT_SHA src/$service/        docker tag $DOCKER_REGISTRY/$service:$CI_COMMIT_SHA $DOCKER_REGISTRY/$service:latest
        # Push to registry        docker push $DOCKER_REGISTRY/$service:$CI_COMMIT_SHA        docker push $DOCKER_REGISTRY/$service:latest
        # Deploy to Kubernetes        kubectl set image deployment/$service $service=$DOCKER_REGISTRY/$service:$CI_COMMIT_SHA        kubectl rollout status deployment/$service      done
deploy_microservices_staging:  <<: *deploy_microservice  environment:    name: staging    url: https://staging-api.myapp.com  only:    - develop
deploy_microservices_production:  <<: *deploy_microservice  environment:    name: production    url: https://api.myapp.com  when: manual  only:    - mainKubernetes Deployment with Helm
Section titled “Kubernetes Deployment with Helm”deploy_with_helm:  stage: deploy  image: alpine/helm:3.10.0  dependencies:    - build_dotnet  script:    - helm repo add stable https://charts.helm.sh/stable    - helm repo update    - |      helm upgrade --install my-microservices ./helm-chart \        --set image.tag=$CI_COMMIT_SHA \        --set environment=$ENVIRONMENT \        --set ingress.host=$INGRESS_HOST \        --namespace $NAMESPACE \        --create-namespace    - kubectl get pods -n $NAMESPACE  environment:    name: $ENVIRONMENT    url: https://$INGRESS_HOSTBlue-Green Deployment Strategy
Section titled “Blue-Green Deployment Strategy”deploy_blue_green:  stage: deploy  image: kubectl:latest  script:    - |      # Check current active color      CURRENT_COLOR=$(kubectl get service myapp-service -o jsonpath='{.spec.selector.color}')      if [ "$CURRENT_COLOR" = "blue" ]; then        NEW_COLOR="green"      else        NEW_COLOR="blue"      fi
      echo "Deploying to $NEW_COLOR environment"
      # Deploy to inactive color      kubectl set image deployment/myapp-$NEW_COLOR myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA      kubectl rollout status deployment/myapp-$NEW_COLOR
      # Health check      kubectl run health-check --image=curlimages/curl --rm -it --restart=Never \        -- curl -f http://myapp-$NEW_COLOR-service/health
      # Switch traffic      kubectl patch service myapp-service -p '{"spec":{"selector":{"color":"'$NEW_COLOR'"}}}'
      echo "Traffic switched to $NEW_COLOR"  environment:    name: production    url: https://api.myapp.comCanary Deployment with Istio
Section titled “Canary Deployment with Istio”deploy_canary:  stage: deploy  image: istio/istioctl:1.18.0  script:    - |      # Deploy canary version      kubectl set image deployment/myapp-canary myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA      kubectl rollout status deployment/myapp-canary
      # Configure traffic split (10% to canary)      cat <<EOF | kubectl apply -f -      apiVersion: networking.istio.io/v1beta1      kind: VirtualService      metadata:        name: myapp-vs      spec:        http:        - match:          - headers:              canary:                exact: "true"          route:          - destination:              host: myapp-canary-service        - route:          - destination:              host: myapp-service            weight: 90          - destination:              host: myapp-canary-service            weight: 10      EOF
      echo "Canary deployment with 10% traffic split completed"  environment:    name: production    url: https://api.myapp.com🗄️ Deploy Database using Liquibase
Section titled “🗄️ Deploy Database using Liquibase”Database deployments with Liquibase ensure consistent schema changes across environments.
Basic Liquibase Deployment
Section titled “Basic Liquibase Deployment”deploy_database:  stage: deploy  image: liquibase/liquibase:4.23  variables:    DB_URL: "jdbc:postgresql://postgres:5432/myapp"    DB_USERNAME: "dbuser"    DB_PASSWORD: "dbpass"  script:    - liquibase --url=$DB_URL --username=$DB_USERNAME --password=$DB_PASSWORD update    - liquibase --url=$DB_URL --username=$DB_USERNAME --password=$DB_PASSWORD status  environment:    name: productionMulti-Environment Database Deployment
Section titled “Multi-Environment Database Deployment”.deploy_database_template: &deploy_database  image: liquibase/liquibase:4.23  script:    - echo "Deploying database changes to $ENVIRONMENT"    - liquibase --url=$DB_URL --username=$DB_USERNAME --password=$DB_PASSWORD validate    - liquibase --url=$DB_URL --username=$DB_USERNAME --password=$DB_PASSWORD update    - liquibase --url=$DB_URL --username=$DB_USERNAME --password=$DB_PASSWORD status
deploy_database_staging:  <<: *deploy_database  stage: deploy  variables:    ENVIRONMENT: "staging"    DB_URL: $STAGING_DB_URL    DB_USERNAME: $STAGING_DB_USERNAME    DB_PASSWORD: $STAGING_DB_PASSWORD  environment:    name: staging  only:    - develop
deploy_database_production:  <<: *deploy_database  stage: deploy  variables:    ENVIRONMENT: "production"    DB_URL: $PRODUCTION_DB_URL    DB_USERNAME: $PRODUCTION_DB_USERNAME    DB_PASSWORD: $PRODUCTION_DB_PASSWORD  environment:    name: production  when: manual  only:    - mainDatabase Deployment with Rollback Support
Section titled “Database Deployment with Rollback Support”deploy_database_with_rollback:  stage: deploy  image: liquibase/liquibase:4.23  variables:    DB_URL: $PRODUCTION_DB_URL    DB_USERNAME: $PRODUCTION_DB_USERNAME    DB_PASSWORD: $PRODUCTION_DB_PASSWORD  script:    # Create rollback script before deployment    - liquibase --url=$DB_URL --username=$DB_USERNAME --password=$DB_PASSWORD rollbackSQL $(date +"%Y-%m-%d") > rollback-script.sql
    # Validate changes    - liquibase --url=$DB_URL --username=$DB_USERNAME --password=$DB_PASSWORD validate
    # Generate update SQL for review    - liquibase --url=$DB_URL --username=$DB_USERNAME --password=$DB_PASSWORD updateSQL > update-script.sql
    # Apply changes    - liquibase --url=$DB_URL --username=$DB_USERNAME --password=$DB_PASSWORD update
    # Verify deployment    - liquibase --url=$DB_URL --username=$DB_USERNAME --password=$DB_PASSWORD status  artifacts:    paths:      - rollback-script.sql      - update-script.sql    expire_in: 1 week  environment:    name: productionDatabase Deployment with Health Checks
Section titled “Database Deployment with Health Checks”deploy_database_with_health_check:  stage: deploy  image: liquibase/liquibase:4.23  services:    - postgres:13  variables:    POSTGRES_DB: myapp    POSTGRES_USER: dbuser    POSTGRES_PASSWORD: dbpass    DB_URL: "jdbc:postgresql://postgres:5432/myapp"  script:    # Wait for database to be ready    - |      for i in {1..30}; do        if liquibase --url=$DB_URL --username=$POSTGRES_USER --password=$POSTGRES_PASSWORD status; then          echo "Database is ready"          break        fi        echo "Waiting for database... ($i/30)"        sleep 10      done
    # Deploy changes    - liquibase --url=$DB_URL --username=$POSTGRES_USER --password=$POSTGRES_PASSWORD update
    # Run health checks    - |      cat > health_check.sql << 'EOF'      SELECT        table_name,        column_name,        data_type      FROM information_schema.columns      WHERE table_schema = 'public'      ORDER BY table_name, ordinal_position;      EOF
    - psql $DB_URL -f health_check.sql  environment:    name: productionDatabase Migration with Multiple Databases
Section titled “Database Migration with Multiple Databases”deploy_multi_database:  stage: deploy  image: liquibase/liquibase:4.23  parallel:    matrix:      - DATABASE: [userdb, orderdb, inventorydb]  script:    - |      case $DATABASE in        userdb)          DB_URL=$USER_DB_URL          DB_USER=$USER_DB_USERNAME          DB_PASS=$USER_DB_PASSWORD          ;;        orderdb)          DB_URL=$ORDER_DB_URL          DB_USER=$ORDER_DB_USERNAME          DB_PASS=$ORDER_DB_PASSWORD          ;;        inventorydb)          DB_URL=$INVENTORY_DB_URL          DB_USER=$INVENTORY_DB_USERNAME          DB_PASS=$INVENTORY_DB_PASSWORD          ;;      esac
    - echo "Deploying $DATABASE database changes"    - liquibase --url=$DB_URL --username=$DB_USER --password=$DB_PASS --changeLogFile=changelog-$DATABASE.xml update    - liquibase --url=$DB_URL --username=$DB_USER --password=$DB_PASS status  environment:    name: production🔧 Advanced Deployment Strategies
Section titled “🔧 Advanced Deployment Strategies”Progressive Deployment with Monitoring
Section titled “Progressive Deployment with Monitoring”deploy_with_monitoring:  stage: deploy  script:    - echo "Deploying application with monitoring"    - kubectl apply -f deployment.yaml    - kubectl rollout status deployment/myapp
    # Setup monitoring    - |      for i in {1..10}; do        HEALTH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://myapp-service/health)        if [ "$HEALTH_STATUS" = "200" ]; then          echo "Health check passed"          break        fi        echo "Health check failed, retrying... ($i/10)"        sleep 30      done
    # Monitor metrics for 5 minutes    - |      for i in {1..5}; do        ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(http_requests_total{status=~'5..'}[5m])" | jq -r '.data.result[0].value[1]')        if (( $(echo "$ERROR_RATE > 0.05" | bc -l) )); then          echo "Error rate too high: $ERROR_RATE"          kubectl rollout undo deployment/myapp          exit 1        fi        echo "Monitoring... Error rate: $ERROR_RATE"        sleep 60      done  environment:    name: production    url: https://myapp.comZero-Downtime Deployment
Section titled “Zero-Downtime Deployment”deploy_zero_downtime:  stage: deploy  script:    - |      # Scale up new version      kubectl scale deployment myapp-new --replicas=3      kubectl rollout status deployment myapp-new
      # Health check new version      kubectl run health-check --image=curlimages/curl --rm -it --restart=Never \        -- curl -f http://myapp-new-service/health
      # Gradually shift traffic      for weight in 25 50 75 100; do        echo "Shifting $weight% traffic to new version"        kubectl patch service myapp-service -p "{\"spec\":{\"selector\":{\"version\":\"new\",\"weight\":\"$weight\"}}}"        sleep 120
        # Monitor during traffic shift        ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(http_requests_total{status=~'5..'}[2m])" | jq -r '.data.result[0].value[1]')        if (( $(echo "$ERROR_RATE > 0.02" | bc -l) )); then          echo "Error rate increased, rolling back"          kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"old"}}}'          exit 1        fi      done
      # Scale down old version      kubectl scale deployment myapp-old --replicas=0  environment:    name: production    url: https://myapp.comThis comprehensive deployment guide covers the essential aspects of deploying Angular UIs, .NET Core microservices, and databases using Liquibase in GitLab CI/CD pipelines.