name: Stage (build -> staging -> E2E gate) on: push: branches: - main # Serialise the entire build/deploy/test cycle: no other run can move staging tags. concurrency: group: staging-release cancel-in-progress: false env: REGISTRY: privaterepo.sitaru.org BACKEND_IMAGE_NAME: ${{ gitea.repository }}-backend FRONTEND_IMAGE_NAME: ${{ gitea.repository }}-frontend PIPELINE_IMAGE_NAME: ${{ gitea.repository }}-pipeline jobs: prepare: runs-on: ubuntu-latest outputs: build_id: ${{ steps.identity.outputs.build_id }} steps: - id: identity run: python3 -c 'import uuid; print("build_id=" + uuid.uuid4().hex)' >> "$GITHUB_OUTPUT" build-backend: needs: [prepare] outputs: digest: ${{ steps.build.outputs.digest }} name: Build Backend (FastAPI) runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: buildkitd-config-inline: | [registry."docker.io"] mirrors = ["10.0.1.224:6000"] [registry."10.0.1.224:6000"] http = true insecure = true - name: Log in to Gitea Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ gitea.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Extract metadata for Backend Docker image id: meta-backend uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }} tags: | type=sha type=raw,value=staging - name: Build and push Backend Docker image id: build uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile push: true build-args: | BUILD_SHA=${{ gitea.sha }} BUILD_ID=${{ needs.prepare.outputs.build_id }} tags: ${{ steps.meta-backend.outputs.tags }} labels: ${{ steps.meta-backend.outputs.labels }} cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:buildcache cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:buildcache,mode=max build-frontend: needs: [prepare] outputs: digest: ${{ steps.build.outputs.digest }} name: Build Frontend (Next.js) runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: buildkitd-config-inline: | [registry."docker.io"] mirrors = ["10.0.1.224:6000"] [registry."10.0.1.224:6000"] http = true insecure = true - name: Log in to Gitea Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ gitea.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Extract metadata for Frontend Docker image id: meta-frontend uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }} tags: | type=sha type=raw,value=staging - name: Build and push Frontend Docker image id: build uses: docker/build-push-action@v5 with: context: ./nextjs-app file: ./nextjs-app/Dockerfile push: true build-args: | BUILD_SHA=${{ gitea.sha }} BUILD_ID=${{ needs.prepare.outputs.build_id }} tags: ${{ steps.meta-frontend.outputs.tags }} labels: ${{ steps.meta-frontend.outputs.labels }} # Cache disabled due to registry size limits build-pipeline: needs: [prepare] outputs: digest: ${{ steps.build.outputs.digest }} name: Build Pipeline (Meltano + dbt + Airflow) runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: buildkitd-config-inline: | [registry."docker.io"] mirrors = ["10.0.1.224:6000"] [registry."10.0.1.224:6000"] http = true insecure = true - name: Log in to Gitea Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ gitea.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Extract metadata for Pipeline Docker image id: meta-pipeline uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.PIPELINE_IMAGE_NAME }} tags: | type=sha type=raw,value=staging - name: Build and push Pipeline Docker image id: build uses: docker/build-push-action@v5 with: context: ./pipeline file: ./pipeline/Dockerfile push: true build-args: | BUILD_SHA=${{ gitea.sha }} BUILD_ID=${{ needs.prepare.outputs.build_id }} tags: ${{ steps.meta-pipeline.outputs.tags }} labels: ${{ steps.meta-pipeline.outputs.labels }} cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.PIPELINE_IMAGE_NAME }}:buildcache cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.PIPELINE_IMAGE_NAME }}:buildcache,mode=max deploy-staging: name: Deploy to Staging runs-on: ubuntu-latest needs: [prepare, build-backend, build-frontend, build-pipeline] steps: - name: Trigger staging stack update run: curl -fsSk -X POST "${{ secrets.PORTAINER_STAGING_WEBHOOK }}" - uses: actions/checkout@v4 - name: Verify deployed release identity run: python3 scripts/ci/release.py wait env: BASE_URL: ${{ secrets.STAGING_BASE_URL }} EXPECTED_SHA: ${{ gitea.sha }} EXPECTED_BUILD_ID: ${{ needs.prepare.outputs.build_id }} e2e-staging: name: E2E Journeys against Staging runs-on: ubuntu-latest needs: [prepare, deploy-staging, build-backend, build-frontend, build-pipeline] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 22 - name: Install Playwright working-directory: e2e run: | npm ci npx playwright install --with-deps chromium - name: Verify release before journeys run: python3 scripts/ci/release.py wait --timeout 10 env: BASE_URL: ${{ secrets.STAGING_BASE_URL }} EXPECTED_SHA: ${{ gitea.sha }} EXPECTED_BUILD_ID: ${{ needs.prepare.outputs.build_id }} - name: Run E2E journeys working-directory: e2e run: npx playwright test env: BASE_URL: ${{ secrets.STAGING_BASE_URL }} EXPECTED_SHA: ${{ gitea.sha }} EXPECTED_BUILD_ID: ${{ needs.prepare.outputs.build_id }} - name: Verify release after journeys run: python3 scripts/ci/release.py wait --timeout 10 env: BASE_URL: ${{ secrets.STAGING_BASE_URL }} EXPECTED_SHA: ${{ gitea.sha }} EXPECTED_BUILD_ID: ${{ needs.prepare.outputs.build_id }} - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ gitea.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Mark tested image digests as verified run: python3 scripts/ci/release.py verify env: EXPECTED_SHA: ${{ gitea.sha }} EXPECTED_BUILD_ID: ${{ needs.prepare.outputs.build_id }} BACKEND_DIGEST: ${{ needs.build-backend.outputs.digest }} FRONTEND_DIGEST: ${{ needs.build-frontend.outputs.digest }} PIPELINE_DIGEST: ${{ needs.build-pipeline.outputs.digest }} # Production deployment is a second, manual approval: see promote.yml # ("Promote to Production (manual)") and docs/DEPLOY.md.