name: Deploy permissions: contents: read concurrency: group: website-starter-deploy-${{ github.event_name == 'schedule' && 'scheduled' || github.event.inputs.rollback_target || github.ref_name }} cancel-in-progress: false on: push: branches: - dev - master workflow_dispatch: inputs: rollback_target: description: "Rollback target: dev or master. Leave empty for a normal manual deploy." required: false app_image_tag: description: "Next app image tag for rollback." required: false schedule: - cron: "17 3 * * 1" env: DOCKER_BUILDKIT: 1 NODE_VERSION_FILE: .node-version TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 REGISTRY: ${{ vars.CONTAINER_REGISTRY }} APP_IMAGE_REPOSITORY: ${{ vars.APP_IMAGE_REPOSITORY }} APP_NAME: ${{ vars.APP_NAME }} COMPANY_NAME: ${{ vars.COMPANY_NAME }} CONTACT_EMAIL: ${{ vars.CONTACT_EMAIL }} CONTACT_PHONE: ${{ vars.CONTACT_PHONE }} CONTACT_ADDRESS: ${{ vars.CONTACT_ADDRESS }} jobs: validate-dispatch: runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 5 steps: - name: Validate workflow dispatch inputs env: EVENT_NAME: ${{ github.event_name }} REF_NAME: ${{ github.ref_name }} ROLLBACK_TARGET: ${{ github.event.inputs.rollback_target }} APP_IMAGE_TAG: ${{ github.event.inputs.app_image_tag }} run: | set -eu if [ "$EVENT_NAME" != "workflow_dispatch" ]; then exit 0 fi fail() { echo "$1" >&2 exit 1 } validate_tag() { key="$1" value="$2" case "$value" in [A-Za-z0-9_]* ) ;; *) fail "Invalid Docker image tag for $key." ;; esac case "$value" in *[!A-Za-z0-9_.-]* ) fail "Invalid Docker image tag for $key." ;; esac if [ "${#value}" -gt 128 ]; then fail "Docker image tag for $key exceeds 128 characters." fi } case "$REF_NAME" in dev|master) ;; *) fail "Manual deployments must be dispatched from dev or master." ;; esac if [ -z "$ROLLBACK_TARGET" ]; then if [ -n "$APP_IMAGE_TAG" ]; then fail "Image tags require rollback_target." fi exit 0 fi case "$ROLLBACK_TARGET" in dev|master) ;; *) fail "rollback_target must be dev or master." ;; esac if [ "$ROLLBACK_TARGET" != "$REF_NAME" ]; then fail "rollback_target must match the dispatched branch." fi validate_tag APP_IMAGE_TAG "$APP_IMAGE_TAG" changes: if: github.event_name != 'schedule' && (github.event_name != 'workflow_dispatch' || github.event.inputs.rollback_target == '') needs: [validate-dispatch] runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 5 outputs: deploy: ${{ steps.detect.outputs.deploy }} steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: lfs: true fetch-depth: 0 - name: Detect changed areas id: detect env: CHANGESET_BEFORE: ${{ github.event.before }} run: sh scripts/detect-ci-changes.sh app-quality: if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true' needs: [changes] runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 30 env: APP_URL: https://example.com steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: lfs: true - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: node-version-file: ${{ env.NODE_VERSION_FILE }} cache: npm cache-dependency-path: package-lock.json - name: Install dependencies run: npm ci - name: Restore and update image cache env: IMAGE_CACHE_NAMESPACE: ${{ github.ref_name }} run: sh scripts/prepare-image-cache.sh - name: Lint run: npm run lint - name: Test run: npm run test - name: Type check run: npm run typecheck - name: Build env: SKIP_IMAGE_OPTIMIZATION: true run: npm run build - name: E2E smoke test env: SKIP_IMAGE_OPTIMIZATION: true run: npm run test:e2e secret-scan: if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true' needs: [changes] runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 15 steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: lfs: true - name: Scan repository for committed secrets run: sh scripts/trivy-scan.sh secret-fs . security-deps: if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true' needs: [changes] runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: lfs: true - name: Report app dependency vulnerabilities run: sh scripts/trivy-scan.sh vuln-fs . - name: Block high and critical app dependency vulnerabilities env: TRIVY_SEVERITY: HIGH,CRITICAL TRIVY_EXIT_CODE: 1 run: sh scripts/trivy-scan.sh vuln-fs . context: if: > needs.changes.outputs.deploy == 'true' && ( (github.event_name == 'push' && github.event.pull_request == null && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/master')) || (github.event_name == 'workflow_dispatch' && github.event.pull_request == null && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/master')) ) needs: [changes] runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 5 outputs: environment_name: ${{ steps.resolve.outputs.environment_name }} primary_domain: ${{ steps.resolve.outputs.primary_domain }} app_url: ${{ steps.resolve.outputs.app_url }} cors_origin: ${{ steps.resolve.outputs.cors_origin }} deploy_stack_name: ${{ steps.resolve.outputs.deploy_stack_name }} traefik_certresolver: ${{ steps.resolve.outputs.traefik_certresolver }} traefik_network_name: ${{ steps.resolve.outputs.traefik_network_name }} steps: - name: Resolve deployment target id: resolve env: REF_NAME: ${{ github.ref_name }} DEV_PRIMARY_DOMAIN: ${{ vars.DEV_PRIMARY_DOMAIN }} DEV_APP_URL: ${{ vars.DEV_APP_URL }} DEV_CORS_ORIGIN: ${{ vars.DEV_CORS_ORIGIN }} DEV_DEPLOY_STACK_NAME: ${{ vars.DEV_DEPLOY_STACK_NAME }} DEV_TRAEFIK_CERTRESOLVER: ${{ secrets.DEV_TRAEFIK_CERTRESOLVER }} DEV_TRAEFIK_NETWORK_NAME: ${{ vars.DEV_TRAEFIK_NETWORK_NAME }} PROD_PRIMARY_DOMAIN: ${{ vars.PROD_PRIMARY_DOMAIN }} PROD_APP_URL: ${{ vars.PROD_APP_URL }} PROD_CORS_ORIGIN: ${{ vars.PROD_CORS_ORIGIN }} PROD_DEPLOY_STACK_NAME: ${{ vars.PROD_DEPLOY_STACK_NAME }} PROD_TRAEFIK_CERTRESOLVER: ${{ secrets.PROD_TRAEFIK_CERTRESOLVER }} PROD_TRAEFIK_NETWORK_NAME: ${{ vars.PROD_TRAEFIK_NETWORK_NAME }} run: | set -eu set_output() { printf '%s=%s\n' "$1" "$2" >> "$GITHUB_OUTPUT" } require_value() { key="$1" value="$2" if [ -z "$value" ]; then echo "Missing required workflow configuration: $key" >&2 exit 1 fi } case "$REF_NAME" in dev) config_prefix="DEV" environment_name="dev" primary_domain="$DEV_PRIMARY_DOMAIN" app_url="${DEV_APP_URL:-}" cors_origin="${DEV_CORS_ORIGIN:-}" deploy_stack_name="${DEV_DEPLOY_STACK_NAME:-website-starter-dev}" traefik_certresolver="$DEV_TRAEFIK_CERTRESOLVER" traefik_network_name="${DEV_TRAEFIK_NETWORK_NAME:-traefik-external}" ;; master) config_prefix="PROD" environment_name="production" primary_domain="$PROD_PRIMARY_DOMAIN" app_url="${PROD_APP_URL:-}" cors_origin="${PROD_CORS_ORIGIN:-}" deploy_stack_name="${PROD_DEPLOY_STACK_NAME:-website-starter-prod}" traefik_certresolver="$PROD_TRAEFIK_CERTRESOLVER" traefik_network_name="${PROD_TRAEFIK_NETWORK_NAME:-traefik}" ;; *) echo "Branch $REF_NAME is not configured for deployment." >&2 exit 1 ;; esac require_value "${config_prefix}_PRIMARY_DOMAIN" "$primary_domain" require_value "${config_prefix}_TRAEFIK_CERTRESOLVER" "$traefik_certresolver" if [ -z "$app_url" ]; then app_url="https://$primary_domain" fi if [ -z "$cors_origin" ]; then cors_origin="$app_url" fi set_output "environment_name" "$environment_name" set_output "primary_domain" "$primary_domain" set_output "app_url" "$app_url" set_output "cors_origin" "$cors_origin" set_output "deploy_stack_name" "$deploy_stack_name" set_output "traefik_certresolver" "$traefik_certresolver" set_output "traefik_network_name" "$traefik_network_name" app-image: if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true' needs: [changes, context, app-quality, secret-scan, security-deps] runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 30 env: APP_URL: ${{ needs.context.outputs.app_url }} APP_IMAGE_NAME: ${{ env.APP_IMAGE_REPOSITORY }}:${{ github.ref_name }}-${{ github.sha }}-${{ github.run_id }} APP_BRANCH_IMAGE_NAME: ${{ env.APP_IMAGE_REPOSITORY }}:${{ github.ref_name }} steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: lfs: true - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: node-version-file: ${{ env.NODE_VERSION_FILE }} cache: npm cache-dependency-path: package-lock.json - name: Install dependencies run: npm ci - name: Restore and update image cache env: IMAGE_CACHE_NAMESPACE: ${{ github.ref_name }} run: sh scripts/prepare-image-cache.sh - name: Build app runtime image run: | docker build --pull \ --build-arg BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --build-arg VCS_REF="${{ github.sha }}" \ --build-arg APP_URL="$APP_URL" \ --build-arg APP_NAME="${APP_NAME:-Example Website}" \ --build-arg COMPANY_NAME="${COMPANY_NAME:-Example Company}" \ --build-arg CONTACT_EMAIL="${CONTACT_EMAIL:-info@example.com}" \ --build-arg CONTACT_PHONE="${CONTACT_PHONE:-+49 000 000000}" \ --build-arg CONTACT_ADDRESS="${CONTACT_ADDRESS:-Example Street 1, 12345 Example City}" \ --build-arg SOURCE_URL="${{ github.server_url }}/${{ github.repository }}" \ --build-arg SKIP_IMAGE_OPTIMIZATION=true \ --build-arg IMAGE_CACHE_NAMESPACE="${{ github.ref_name }}" \ -f Dockerfile \ -t "$APP_IMAGE_NAME" \ -t "$APP_BRANCH_IMAGE_NAME" \ . - name: Report app runtime image vulnerabilities run: sh scripts/trivy-scan.sh vuln-image "$APP_IMAGE_NAME" - name: Block high and critical app runtime vulnerabilities env: TRIVY_SEVERITY: HIGH,CRITICAL TRIVY_EXIT_CODE: 1 run: sh scripts/trivy-scan.sh vuln-image "$APP_IMAGE_NAME" - name: Push app runtime image env: REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }} REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }} run: | set -eu if [ -z "${REGISTRY_USERNAME:-}" ] || [ -z "${REGISTRY_TOKEN:-}" ]; then echo "Missing required secrets: PACKAGES_USERNAME and PACKAGES_TOKEN" >&2 exit 1 fi docker_config="$(mktemp -d)" cleanup() { DOCKER_CONFIG="$docker_config" docker logout "$REGISTRY" >/dev/null 2>&1 || true rm -rf "$docker_config" } trap cleanup EXIT trap 'exit 1' HUP INT TERM export DOCKER_CONFIG="$docker_config" echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin docker push "$APP_IMAGE_NAME" docker push "$APP_BRANCH_IMAGE_NAME" deploy-dev: if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true' && github.ref == 'refs/heads/dev' needs: [changes, context, app-quality, app-image, secret-scan, security-deps] runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 30 env: CALENDAR_URL: ${{ secrets.CALENDAR_URL }} PRIMARY_DOMAIN: ${{ needs.context.outputs.primary_domain }} APP_URL: ${{ needs.context.outputs.app_url }} CORS_ORIGIN: ${{ needs.context.outputs.cors_origin }} COMPOSE_PROJECT_NAME: ${{ needs.context.outputs.deploy_stack_name }} TRAEFIK_STACK_NAME: ${{ needs.context.outputs.deploy_stack_name }} TRAEFIK_CERTRESOLVER: ${{ needs.context.outputs.traefik_certresolver }} TRAEFIK_NETWORK_NAME: ${{ needs.context.outputs.traefik_network_name }} DEPLOY_TARGET: dev APP_IMAGE_NAME: ${{ env.APP_IMAGE_REPOSITORY }}:dev-${{ github.sha }}-${{ github.run_id }} steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: lfs: true - name: Pull app runtime image env: REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }} REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }} run: | set -eu if [ -z "${REGISTRY_USERNAME:-}" ] || [ -z "${REGISTRY_TOKEN:-}" ]; then echo "Missing required secrets: PACKAGES_USERNAME and PACKAGES_TOKEN" >&2 exit 1 fi docker_config="$(mktemp -d)" cleanup() { DOCKER_CONFIG="$docker_config" docker logout "$REGISTRY" >/dev/null 2>&1 || true rm -rf "$docker_config" } trap cleanup EXIT trap 'exit 1' HUP INT TERM export DOCKER_CONFIG="$docker_config" echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin docker pull "$APP_IMAGE_NAME" - name: Validate runtime configuration run: | if [ -z "${CALENDAR_URL:-}" ]; then echo "Missing required secret: CALENDAR_URL" >&2 exit 1 fi - name: Print deployment target run: | echo "Environment: ${{ needs.context.outputs.environment_name }}" echo "Site host: $PRIMARY_DOMAIN" echo "Compose project: $COMPOSE_PROJECT_NAME" - name: Deploy and verify app run: sh scripts/deploy-and-verify.sh deploy-master: if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true' && github.ref == 'refs/heads/master' needs: [changes, context, app-quality, app-image, secret-scan, security-deps] runs-on: ${{ vars.PROD_RUNNER || vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 30 env: CALENDAR_URL: ${{ secrets.CALENDAR_URL }} PRIMARY_DOMAIN: ${{ needs.context.outputs.primary_domain }} APP_URL: ${{ needs.context.outputs.app_url }} CORS_ORIGIN: ${{ needs.context.outputs.cors_origin }} COMPOSE_PROJECT_NAME: ${{ needs.context.outputs.deploy_stack_name }} TRAEFIK_STACK_NAME: ${{ needs.context.outputs.deploy_stack_name }} TRAEFIK_CERTRESOLVER: ${{ needs.context.outputs.traefik_certresolver }} TRAEFIK_NETWORK_NAME: ${{ needs.context.outputs.traefik_network_name }} DEPLOY_TARGET: master APP_IMAGE_NAME: ${{ env.APP_IMAGE_REPOSITORY }}:master-${{ github.sha }}-${{ github.run_id }} steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: lfs: true - name: Pull app runtime image env: REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }} REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }} run: | set -eu if [ -z "${REGISTRY_USERNAME:-}" ] || [ -z "${REGISTRY_TOKEN:-}" ]; then echo "Missing required secrets: PACKAGES_USERNAME and PACKAGES_TOKEN" >&2 exit 1 fi docker_config="$(mktemp -d)" cleanup() { DOCKER_CONFIG="$docker_config" docker logout "$REGISTRY" >/dev/null 2>&1 || true rm -rf "$docker_config" } trap cleanup EXIT trap 'exit 1' HUP INT TERM export DOCKER_CONFIG="$docker_config" echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin docker pull "$APP_IMAGE_NAME" - name: Validate runtime configuration run: | if [ -z "${CALENDAR_URL:-}" ]; then echo "Missing required secret: CALENDAR_URL" >&2 exit 1 fi - name: Print deployment target run: | echo "Environment: ${{ needs.context.outputs.environment_name }}" echo "Site host: $PRIMARY_DOMAIN" echo "Compose project: $COMPOSE_PROJECT_NAME" - name: Deploy and verify app run: sh scripts/deploy-and-verify.sh scheduled-rebuild-dev: if: github.event_name == 'schedule' runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 45 env: DEPLOY_TARGET: dev CALENDAR_URL: ${{ secrets.CALENDAR_URL }} PRIMARY_DOMAIN: ${{ vars.DEV_PRIMARY_DOMAIN }} APP_URL: ${{ vars.DEV_APP_URL }} CORS_ORIGIN: ${{ vars.DEV_CORS_ORIGIN }} COMPOSE_PROJECT_NAME: ${{ vars.DEV_DEPLOY_STACK_NAME }} TRAEFIK_STACK_NAME: ${{ vars.DEV_DEPLOY_STACK_NAME }} TRAEFIK_CERTRESOLVER: ${{ secrets.DEV_TRAEFIK_CERTRESOLVER }} TRAEFIK_NETWORK_NAME: ${{ vars.DEV_TRAEFIK_NETWORK_NAME }} steps: - name: Checkout dev branch uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: ref: dev lfs: true - name: Scan repository for committed secrets run: sh scripts/trivy-scan.sh secret-fs . - name: Report app dependency vulnerabilities run: sh scripts/trivy-scan.sh vuln-fs . - name: Block high and critical app dependency vulnerabilities env: TRIVY_SEVERITY: HIGH,CRITICAL TRIVY_EXIT_CODE: 1 run: sh scripts/trivy-scan.sh vuln-fs . - name: Rebuild latest base image and deploy dev env: REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }} REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }} run: sh scripts/scheduled-rebuild-deploy.sh scheduled-rebuild-master: if: github.event_name == 'schedule' runs-on: ${{ vars.PROD_RUNNER || vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 45 env: DEPLOY_TARGET: master CALENDAR_URL: ${{ secrets.CALENDAR_URL }} PRIMARY_DOMAIN: ${{ vars.PROD_PRIMARY_DOMAIN }} APP_URL: ${{ vars.PROD_APP_URL }} CORS_ORIGIN: ${{ vars.PROD_CORS_ORIGIN }} COMPOSE_PROJECT_NAME: ${{ vars.PROD_DEPLOY_STACK_NAME }} TRAEFIK_STACK_NAME: ${{ vars.PROD_DEPLOY_STACK_NAME }} TRAEFIK_CERTRESOLVER: ${{ secrets.PROD_TRAEFIK_CERTRESOLVER }} TRAEFIK_NETWORK_NAME: ${{ vars.PROD_TRAEFIK_NETWORK_NAME }} steps: - name: Checkout master branch uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: ref: master lfs: true - name: Scan repository for committed secrets run: sh scripts/trivy-scan.sh secret-fs . - name: Report app dependency vulnerabilities run: sh scripts/trivy-scan.sh vuln-fs . - name: Block high and critical app dependency vulnerabilities env: TRIVY_SEVERITY: HIGH,CRITICAL TRIVY_EXIT_CODE: 1 run: sh scripts/trivy-scan.sh vuln-fs . - name: Rebuild latest base image and deploy master env: REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }} REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }} run: sh scripts/scheduled-rebuild-deploy.sh rollback-dev: if: github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_target == 'dev' && github.ref == 'refs/heads/dev' runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 30 env: DEPLOY_TARGET: dev CALENDAR_URL: ${{ secrets.CALENDAR_URL }} PRIMARY_DOMAIN: ${{ vars.DEV_PRIMARY_DOMAIN }} APP_URL: ${{ vars.DEV_APP_URL }} CORS_ORIGIN: ${{ vars.DEV_CORS_ORIGIN }} COMPOSE_PROJECT_NAME: ${{ vars.DEV_DEPLOY_STACK_NAME }} TRAEFIK_STACK_NAME: ${{ vars.DEV_DEPLOY_STACK_NAME }} TRAEFIK_CERTRESOLVER: ${{ secrets.DEV_TRAEFIK_CERTRESOLVER }} TRAEFIK_NETWORK_NAME: ${{ vars.DEV_TRAEFIK_NETWORK_NAME }} APP_IMAGE_TAG: ${{ github.event.inputs.app_image_tag }} steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: lfs: true - name: Deploy rollback and verify app env: REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }} REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }} run: sh scripts/manual-rollback-deploy.sh rollback-master: if: github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_target == 'master' && github.ref == 'refs/heads/master' runs-on: ${{ vars.PROD_RUNNER || vars.CI_RUNNER || 'ubuntu-latest' }} timeout-minutes: 30 env: DEPLOY_TARGET: master CALENDAR_URL: ${{ secrets.CALENDAR_URL }} PRIMARY_DOMAIN: ${{ vars.PROD_PRIMARY_DOMAIN }} APP_URL: ${{ vars.PROD_APP_URL }} CORS_ORIGIN: ${{ vars.PROD_CORS_ORIGIN }} COMPOSE_PROJECT_NAME: ${{ vars.PROD_DEPLOY_STACK_NAME }} TRAEFIK_STACK_NAME: ${{ vars.PROD_DEPLOY_STACK_NAME }} TRAEFIK_CERTRESOLVER: ${{ secrets.PROD_TRAEFIK_CERTRESOLVER }} TRAEFIK_NETWORK_NAME: ${{ vars.PROD_TRAEFIK_NETWORK_NAME }} APP_IMAGE_TAG: ${{ github.event.inputs.app_image_tag }} steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: lfs: true - name: Deploy rollback and verify app env: REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }} REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }} run: sh scripts/manual-rollback-deploy.sh