From fe41c13dc92160b45994cfb9a7d0d0b98f8559a0 Mon Sep 17 00:00:00 2001 From: fropa Date: Thu, 29 Jan 2026 11:12:37 +0400 Subject: [PATCH 1/6] Add GitHub Actions workflow for Azure deployment --- .github/workflows/prod-stg-deploy.yml | 170 ++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 .github/workflows/prod-stg-deploy.yml diff --git a/.github/workflows/prod-stg-deploy.yml b/.github/workflows/prod-stg-deploy.yml new file mode 100644 index 00000000..17dc4467 --- /dev/null +++ b/.github/workflows/prod-stg-deploy.yml @@ -0,0 +1,170 @@ +name: Container App Prod/Staging Deploy + +on: + push: + branches: + - containerapp_deploy + workflow_dispatch: + inputs: + target_env: + description: "Where to deploy" + required: true + type: choice + options: + - staging + - prod + default: staging + branch: + description: "Branch to build (default = containerapp_deploy)" + required: true + default: containerapp_deploy + +env: + ACR_NAME: corecontainers + IMAGE_NAME: core-frontend-about + + RESOURCE_GROUP: core-frontend + PROD_APP_NAME: core-frontend-about + + STAGING_RESOURCE_GROUP: core-frontend-stage + STAGING_APP_NAME: core-frontend-about-stage + + PORT: 8080 + +permissions: + id-token: write + contents: read + +jobs: + build-and-push: + runs-on: ubuntu-latest + environment: azure + outputs: + tag: ${{ steps.vars.outputs.tag }} + deploy_env: ${{ steps.vars.outputs.deploy_env }} + app_name: ${{ steps.vars.outputs.app_name }} + ref_to_build: ${{ steps.vars.outputs.ref_to_build }} + branch_tag: ${{ steps.vars.outputs.branch_tag }} + image_tag: ${{ steps.vars.outputs.image_tag }} + + steps: + - name: Compute variables + id: vars + shell: bash + run: | + SHA7="$(echo "${GITHUB_SHA}" | cut -c1-7)" + + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + DEPLOY_ENV="${{ inputs.target_env }}" + REF_TO_BUILD="${{ inputs.branch }}" + else + DEPLOY_ENV="staging" + REF_TO_BUILD="${GITHUB_REF_NAME}" + fi + + BRANCH_TAG="$(echo "${REF_TO_BUILD}" | tr '[:upper:]' '[:lower:]' \ + | sed -E 's#[^a-z0-9_.-]+#-#g' | sed -E 's#^-+##; s#-+$##' | cut -c1-40)" + + if [[ "${DEPLOY_ENV}" == "prod" ]]; then + APP_NAME="${{ env.PROD_APP_NAME }}" + IMAGE_TAG="prod" + else + APP_NAME="${{ env.STAGING_APP_NAME }}" + IMAGE_TAG="stg-${BRANCH_TAG}" + fi + + echo "tag=${SHA7}" >> "$GITHUB_OUTPUT" + echo "deploy_env=${DEPLOY_ENV}" >> "$GITHUB_OUTPUT" + echo "app_name=${APP_NAME}" >> "$GITHUB_OUTPUT" + echo "ref_to_build=${REF_TO_BUILD}" >> "$GITHUB_OUTPUT" + echo "branch_tag=${BRANCH_TAG}" >> "$GITHUB_OUTPUT" + echo "image_tag=${IMAGE_TAG}" >> "$GITHUB_OUTPUT" + + - name: Summary + run: | + { + echo "## Deploy plan" + echo "- **Env:** ${{ steps.vars.outputs.deploy_env }}" + echo "- **Branch:** ${{ steps.vars.outputs.ref_to_build }}" + echo "- **App:** ${{ steps.target.outputs.APP || steps.vars.outputs.app_name }}" + echo "- **RG:** ${{ steps.target.outputs.RG }}" + echo "- **Image:** ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ env.TAG }}" + echo "- **Tag:** ${{ steps.vars.outputs.image_tag }}" + } >> $GITHUB_STEP_SUMMARY + + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ steps.vars.outputs.ref_to_build }} + + - name: Azure Login + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Docker ACR Login + run: az acr login --name ${{ env.ACR_NAME }} + + - name: Docker Build + shell: bash + run: | + if [[ "${{ steps.vars.outputs.deploy_env }}" == "staging" ]]; then + INSTALL_DEV=true + else + INSTALL_DEV=false + fi + + docker build \ + --build-arg APP_SECRET=${{ secrets.APP_SECRET }} \ + --build-arg INSTALL_DEV=$INSTALL_DEV \ + -t ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.tag }} \ + -t ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.image_tag }} . + + - name: Docker Push + run: | + docker push ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.tag }} + docker push ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.image_tag }} + + deploy: + needs: build-and-push + runs-on: ubuntu-latest + environment: azure + env: + TAG: ${{ needs.build-and-push.outputs.tag }} + IMAGE_TAG: ${{ needs.build-and-push.outputs.image_tag }} + + steps: + - name: Azure Login + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Resolve deploy target + id: target + shell: bash + run: | + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + ENV="${{ inputs.target_env }}" + else + ENV="staging" + fi + + if [[ "$ENV" == "prod" ]]; then + echo "APP=${{ env.PROD_APP_NAME }}" >> $GITHUB_OUTPUT + echo "RG=${{ env.RESOURCE_GROUP }}" >> $GITHUB_OUTPUT + else + echo "APP=${{ env.STAGING_APP_NAME }}" >> $GITHUB_OUTPUT + echo "RG=${{ env.STAGING_RESOURCE_GROUP }}" >> $GITHUB_OUTPUT + fi + + - name: Deploy to Azure Container App + run: | + az containerapp update \ + --name ${{ steps.target.outputs.APP }} \ + --resource-group ${{ steps.target.outputs.RG }} \ + --image ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ env.TAG }} \ + --set configuration.ingress.targetPort=${{ env.PORT }} From fcc748e1f8dbcfb2e27077d0a0c59a72a2e8e1af Mon Sep 17 00:00:00 2001 From: fropa Date: Thu, 29 Jan 2026 11:17:54 +0400 Subject: [PATCH 2/6] Fix build arguments in prod-stg-deploy.yml --- .github/workflows/prod-stg-deploy.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prod-stg-deploy.yml b/.github/workflows/prod-stg-deploy.yml index 17dc4467..9dc10971 100644 --- a/.github/workflows/prod-stg-deploy.yml +++ b/.github/workflows/prod-stg-deploy.yml @@ -117,8 +117,10 @@ jobs: fi docker build \ - --build-arg APP_SECRET=${{ secrets.APP_SECRET }} \ - --build-arg INSTALL_DEV=$INSTALL_DEV \ + --build-arg GA_CODE=${{ secrets.GA_CODE }}} \ + --build-arg NPM_TOKEN=${{ secrets.NPM_TOKEN }} \ + --build-arg NODE_ENV=${{ env.NODE_ENV }}\ + --build-arg API_KEY=${{ secrets.API_KEY }}\ -t ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.tag }} \ -t ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.image_tag }} . From 69798d25cc4f9c6a2ce44d6b22b2073780e4b0e4 Mon Sep 17 00:00:00 2001 From: fropa Date: Thu, 29 Jan 2026 11:25:58 +0400 Subject: [PATCH 3/6] Add GA_CODE and NODE_ENV to deployment config --- .github/workflows/prod-stg-deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/prod-stg-deploy.yml b/.github/workflows/prod-stg-deploy.yml index 9dc10971..04bc3fbe 100644 --- a/.github/workflows/prod-stg-deploy.yml +++ b/.github/workflows/prod-stg-deploy.yml @@ -29,6 +29,8 @@ env: STAGING_RESOURCE_GROUP: core-frontend-stage STAGING_APP_NAME: core-frontend-about-stage + GA_CODE: ${{ secrets.GA_CODE }} + NODE_ENV: production PORT: 8080 permissions: From a77e7bbbcdc3325044f3cb7f02a82641d9c03793 Mon Sep 17 00:00:00 2001 From: fropa Date: Thu, 29 Jan 2026 11:32:33 +0400 Subject: [PATCH 4/6] Update staging app name in deployment workflow --- .github/workflows/prod-stg-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prod-stg-deploy.yml b/.github/workflows/prod-stg-deploy.yml index 04bc3fbe..2cc98550 100644 --- a/.github/workflows/prod-stg-deploy.yml +++ b/.github/workflows/prod-stg-deploy.yml @@ -27,7 +27,7 @@ env: PROD_APP_NAME: core-frontend-about STAGING_RESOURCE_GROUP: core-frontend-stage - STAGING_APP_NAME: core-frontend-about-stage + STAGING_APP_NAME: core-frontend-stage-about GA_CODE: ${{ secrets.GA_CODE }} NODE_ENV: production From 94360177764e9432818904f956f6ec1dc2ed6bbe Mon Sep 17 00:00:00 2001 From: fropa Date: Wed, 4 Feb 2026 14:30:47 +0400 Subject: [PATCH 5/6] Rename prod-deploy.yml to prod-deploy.yml.old --- .github/workflows/{prod-deploy.yml => prod-deploy.yml.old} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{prod-deploy.yml => prod-deploy.yml.old} (100%) diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml.old similarity index 100% rename from .github/workflows/prod-deploy.yml rename to .github/workflows/prod-deploy.yml.old From 00a086a8a7d2a552be06afd418650fe1eac887a9 Mon Sep 17 00:00:00 2001 From: fropa Date: Wed, 4 Feb 2026 15:09:37 +0400 Subject: [PATCH 6/6] Update prod-stg-deploy.yml --- .github/workflows/prod-stg-deploy.yml | 58 +++++++++++++++++++++------ 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/.github/workflows/prod-stg-deploy.yml b/.github/workflows/prod-stg-deploy.yml index 2cc98550..658faf7e 100644 --- a/.github/workflows/prod-stg-deploy.yml +++ b/.github/workflows/prod-stg-deploy.yml @@ -4,6 +4,10 @@ on: push: branches: - containerapp_deploy + + schedule: + - cron: "0 1 * * *" + workflow_dispatch: inputs: target_env: @@ -29,10 +33,11 @@ env: STAGING_RESOURCE_GROUP: core-frontend-stage STAGING_APP_NAME: core-frontend-stage-about - GA_CODE: ${{ secrets.GA_CODE }} NODE_ENV: production PORT: 8080 + ALLOWED_PROD_BRANCH: containerapp_deploy + permissions: id-token: write contents: read @@ -56,9 +61,20 @@ jobs: run: | SHA7="$(echo "${GITHUB_SHA}" | cut -c1-7)" - if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + # ✅ ADDED: schedule always deploys PROD from the frozen branch + if [[ "${GITHUB_EVENT_NAME}" == "schedule" ]]; then + DEPLOY_ENV="prod" + REF_TO_BUILD="${{ env.ALLOWED_PROD_BRANCH }}" + + elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then DEPLOY_ENV="${{ inputs.target_env }}" REF_TO_BUILD="${{ inputs.branch }}" + + # existing: Freeze PROD to only one branch; fail if any other branch is selected for PROD + if [[ "${DEPLOY_ENV}" == "prod" && "${REF_TO_BUILD}" != "${{ env.ALLOWED_PROD_BRANCH }}" ]]; then + echo "::error title=Blocked prod deployment::Prod deployments are only allowed from '${{ env.ALLOWED_PROD_BRANCH }}'. You selected '${REF_TO_BUILD}'." + exit 1 + fi else DEPLOY_ENV="staging" REF_TO_BUILD="${GITHUB_REF_NAME}" @@ -82,18 +98,22 @@ jobs: echo "branch_tag=${BRANCH_TAG}" >> "$GITHUB_OUTPUT" echo "image_tag=${IMAGE_TAG}" >> "$GITHUB_OUTPUT" + - name: Summary run: | - { - echo "## Deploy plan" - echo "- **Env:** ${{ steps.vars.outputs.deploy_env }}" - echo "- **Branch:** ${{ steps.vars.outputs.ref_to_build }}" - echo "- **App:** ${{ steps.target.outputs.APP || steps.vars.outputs.app_name }}" - echo "- **RG:** ${{ steps.target.outputs.RG }}" - echo "- **Image:** ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ env.TAG }}" - echo "- **Tag:** ${{ steps.vars.outputs.image_tag }}" - } >> $GITHUB_STEP_SUMMARY - + { + echo "## Deploy plan" + echo "- **Trigger:** ${{ github.event_name }}" + echo "- **Env:** ${{ steps.vars.outputs.deploy_env }}" + echo "- **Branch:** ${{ steps.vars.outputs.ref_to_build }}" + echo "- **App:** ${{ steps.vars.outputs.app_name }}" + echo "- **ACR:** ${{ env.ACR_NAME }}" + echo "- **Image:** ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}" + echo "- **Tag (sha7):** ${{ steps.vars.outputs.tag }}" + echo "- **Tag (env):** ${{ steps.vars.outputs.image_tag }}" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Checkout code uses: actions/checkout@v4 with: @@ -151,7 +171,9 @@ jobs: id: target shell: bash run: | - if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + if [[ "${GITHUB_EVENT_NAME}" == "schedule" ]]; then + ENV="prod" + elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then ENV="${{ inputs.target_env }}" else ENV="staging" @@ -165,6 +187,16 @@ jobs: echo "RG=${{ env.STAGING_RESOURCE_GROUP }}" >> $GITHUB_OUTPUT fi + - name: Enforce prod branch freeze + shell: bash + run: | + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + if [[ "${{ inputs.target_env }}" == "prod" && "${{ needs.build-and-push.outputs.ref_to_build }}" != "${{ env.ALLOWED_PROD_BRANCH }}" ]]; then + echo "::error title=Blocked prod deployment::Prod deployments are only allowed from '${{ env.ALLOWED_PROD_BRANCH }}'." + exit 1 + fi + fi + - name: Deploy to Azure Container App run: | az containerapp update \