name: Master Production Deploy on: push: branches: - master workflow_dispatch: concurrency: group: deploy-production cancel-in-progress: false env: REGISTRY: ${{ vars.REGISTRY }} WEB_IMAGE_REPO: ${{ vars.IMAGE_REPO }}-web INGESTION_IMAGE_REPO: ${{ vars.IMAGE_REPO }}-ingestion RELEASE_ID: ${{ github.sha }}-${{ github.run_number }} NPM_CONFIG_AUDIT: "false" NPM_CONFIG_FUND: "false" NPM_CONFIG_UPDATE_NOTIFIER: "false" jobs: production: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 22 - name: Setup npm run: npm install -g npm@11.6.4 - name: Install dependencies run: npm ci --prefer-offline --no-audit --no-fund - name: Quality gate run: npm run ci:quality - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Gitea Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - name: Build and push web image id: build-web uses: docker/build-push-action@v6 with: context: . file: deploy/docker/Dockerfile.web push: true tags: ${{ env.WEB_IMAGE_REPO }}:${{ github.sha }} labels: | org.opencontainers.image.revision=${{ github.sha }} org.opencontainers.image.version=${{ env.RELEASE_ID }} - name: Build and push ingestion image id: build-ingestion uses: docker/build-push-action@v6 with: context: . file: ingestion/Dockerfile push: true tags: ${{ env.INGESTION_IMAGE_REPO }}:${{ github.sha }} labels: | org.opencontainers.image.revision=${{ github.sha }} org.opencontainers.image.version=${{ env.RELEASE_ID }} - name: Production pre-deploy checks env: ENVIRONMENT: production STACK_NAME: ${{ vars.STACK_NAME }} RELEASE_ID: ${{ env.RELEASE_ID }} COMMIT_SHA: ${{ github.sha }} WEB_IMAGE: ${{ env.WEB_IMAGE_REPO }}@${{ steps.build-web.outputs.digest }} INGESTION_IMAGE: ${{ env.INGESTION_IMAGE_REPO }}@${{ steps.build-ingestion.outputs.digest }} POSTGRES_PASSWORD: ${{ secrets.INGESTION_POSTGRES_PASSWORD }} INGESTION_ADMIN_PASSWORD: ${{ secrets.INGESTION_ADMIN_PASSWORD }} PUBLIC_HOST: ${{ vars.DOMAIN }} TRAEFIK_NETWORK: ${{ vars.TRAEFIK_NETWORK }} SWARM_MANAGER_HOST: ${{ secrets.SWARM_MANAGER_HOST }} SWARM_SSH_USER: ${{ secrets.SWARM_SSH_USER }} run: node deploy/scripts/predeploy-check.mjs - name: Deploy production env: ENVIRONMENT: production STACK_NAME: ${{ vars.STACK_NAME }} RELEASE_ID: ${{ env.RELEASE_ID }} COMMIT_SHA: ${{ github.sha }} WEB_IMAGE: ${{ env.WEB_IMAGE_REPO }}@${{ steps.build-web.outputs.digest }} INGESTION_IMAGE: ${{ env.INGESTION_IMAGE_REPO }}@${{ steps.build-ingestion.outputs.digest }} PUBLIC_HOST: ${{ vars.DOMAIN }} TRAEFIK_NETWORK: ${{ vars.TRAEFIK_NETWORK }} SWARM_MANAGER_HOST: ${{ secrets.SWARM_MANAGER_HOST }} SWARM_SSH_USER: ${{ secrets.SWARM_SSH_USER }} SWARM_SSH_PORT: ${{ secrets.SWARM_SSH_PORT }} REGISTRY: ${{ env.REGISTRY }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} WEB_REPLICAS: ${{ vars.WEB_REPLICAS }} WEB_PLACEMENT_CONSTRAINT: ${{ vars.WEB_PLACEMENT_CONSTRAINT }} TRAEFIK_ENTRYPOINTS: ${{ vars.TRAEFIK_ENTRYPOINT }} TRAEFIK_TLS: ${{ vars.TRAEFIK_TLS }} TRAEFIK_CERTRESOLVER: ${{ vars.TRAEFIK_CERTRESOLVER }} REGION: ${{ vars.REGION }} TEAM_ID: ${{ vars.TEAM_ID }} DEPLOY_MIN_FREE_MB: ${{ vars.DEPLOY_MIN_FREE_MB }} DEPLOY_PRUNE_BEFORE_PULL: ${{ vars.DEPLOY_PRUNE_BEFORE_PULL }} DEPLOY_PRUNE_UNTIL: ${{ vars.DEPLOY_PRUNE_UNTIL }} DEPLOY_CLEANUP_FAILED_IMAGES: ${{ vars.DEPLOY_CLEANUP_FAILED_IMAGES }} VIDEOREG_DB_MEMORY_TTL_MS: ${{ vars.VIDEOREG_DB_MEMORY_TTL_MS }} VIDEOREG_NEXT_CACHE_REVALIDATE_SECONDS: ${{ vars.VIDEOREG_NEXT_CACHE_REVALIDATE_SECONDS }} POSTGRES_DB: ${{ vars.INGESTION_POSTGRES_DB }} POSTGRES_USER: ${{ vars.INGESTION_POSTGRES_USER }} POSTGRES_PASSWORD: ${{ secrets.INGESTION_POSTGRES_PASSWORD }} INGESTION_ADMIN_USERNAME: ${{ vars.INGESTION_ADMIN_USERNAME }} INGESTION_ADMIN_PASSWORD: ${{ secrets.INGESTION_ADMIN_PASSWORD }} YANDEX_MARKET_DASHCAM_CATEGORY_URL: ${{ vars.YANDEX_MARKET_DASHCAM_CATEGORY_URL }} MVIDEO_DASHCAM_CATEGORY_URL: ${{ vars.MVIDEO_DASHCAM_CATEGORY_URL }} INGESTION_CATEGORY_DAILY_BUDGET: ${{ vars.INGESTION_CATEGORY_DAILY_BUDGET }} INGESTION_PRODUCT_DAILY_BUDGET: ${{ vars.INGESTION_PRODUCT_DAILY_BUDGET }} INGESTION_SITEMAP_DAILY_BUDGET: ${{ vars.INGESTION_SITEMAP_DAILY_BUDGET }} INGESTION_FETCH_DELAY_MIN_MS: ${{ vars.INGESTION_FETCH_DELAY_MIN_MS }} INGESTION_FETCH_DELAY_MAX_MS: ${{ vars.INGESTION_FETCH_DELAY_MAX_MS }} INGESTION_WORKER_BATCH_SIZE: ${{ vars.INGESTION_WORKER_BATCH_SIZE }} INGESTION_WORKER_IDLE_MS: ${{ vars.INGESTION_WORKER_IDLE_MS }} INGESTION_WORKER_REPLICAS: ${{ vars.INGESTION_WORKER_REPLICAS }} TRAEFIK_ADMIN_PRIORITY: ${{ vars.TRAEFIK_ADMIN_PRIORITY }} run: deploy/scripts/deploy-over-ssh.sh - name: Production smoke checks id: smoke continue-on-error: true env: BASE_URL: https://${{ vars.DOMAIN }} EXPECTED_RELEASE_ID: ${{ env.RELEASE_ID }} SMOKE_RESULT_PATH: smoke-result.json run: node deploy/scripts/smoke-check.mjs - name: Roll back production after failed smoke checks if: steps.smoke.outcome == 'failure' env: ENVIRONMENT: production STACK_NAME: ${{ vars.STACK_NAME }} SWARM_MANAGER_HOST: ${{ secrets.SWARM_MANAGER_HOST }} SWARM_SSH_USER: ${{ secrets.SWARM_SSH_USER }} SWARM_SSH_PORT: ${{ secrets.SWARM_SSH_PORT }} run: deploy/scripts/rollback-over-ssh.sh - name: Fail after production smoke rollback if: steps.smoke.outcome == 'failure' run: exit 1 - name: Write release manifest env: COMMIT_SHA: ${{ github.sha }} RELEASE_ID: ${{ env.RELEASE_ID }} WEB_IMAGE_DIGEST: ${{ env.WEB_IMAGE_REPO }}@${{ steps.build-web.outputs.digest }} INGESTION_IMAGE_DIGEST: ${{ env.INGESTION_IMAGE_REPO }}@${{ steps.build-ingestion.outputs.digest }} SMOKE_RESULT_PATH: smoke-result.json WORKFLOW_RUN_ID: ${{ github.run_id }} run: node deploy/scripts/write-release-manifest.mjs > release-manifest.json - name: Upload release manifest uses: actions/upload-artifact@v4 with: name: release-manifest-${{ env.RELEASE_ID }} path: release-manifest.json