name: Deploy (staging -> E2E gate -> production) on: push: branches: - main env: REGISTRY: privaterepo.sitaru.org BACKEND_IMAGE_NAME: ${{ gitea.repository }}-backend FRONTEND_IMAGE_NAME: ${{ gitea.repository }}-frontend PIPELINE_IMAGE_NAME: ${{ gitea.repository }}-pipeline jobs: build-backend: 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 uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile push: true 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: 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 uses: docker/build-push-action@v5 with: context: ./nextjs-app file: ./nextjs-app/Dockerfile push: true tags: ${{ steps.meta-frontend.outputs.tags }} labels: ${{ steps.meta-frontend.outputs.labels }} build-args: | FASTAPI_URL=http://backend:80/api # Cache disabled due to registry size limits build-pipeline: 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 uses: docker/build-push-action@v5 with: context: ./pipeline file: ./pipeline/Dockerfile push: true 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: [build-backend, build-frontend, build-pipeline] steps: - name: Trigger staging stack update run: curl -fsSk -X POST "${{ secrets.PORTAINER_STAGING_WEBHOOK }}" - name: Wait for staging to become healthy run: | echo "Polling ${STAGING_BASE_URL} for up to 5 minutes..." for i in $(seq 1 60); do if curl -fsS -o /dev/null --max-time 10 "${STAGING_BASE_URL}/"; then echo "Staging is up (attempt $i)" exit 0 fi sleep 5 done echo "Staging did not become healthy in time" >&2 exit 1 env: STAGING_BASE_URL: ${{ secrets.STAGING_BASE_URL }} e2e-staging: name: E2E Journeys against Staging runs-on: ubuntu-latest needs: [deploy-staging] 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: Run E2E journeys working-directory: e2e run: npx playwright test env: BASE_URL: ${{ secrets.STAGING_BASE_URL }} promote-prod: name: Promote to Production runs-on: ubuntu-latest needs: [e2e-staging] steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Gitea Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ gitea.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Retag verified images as prod run: | SHORT_SHA="sha-$(echo "${{ gitea.sha }}" | cut -c1-7)" for IMAGE in \ "${REGISTRY}/${BACKEND_IMAGE_NAME}" \ "${REGISTRY}/${FRONTEND_IMAGE_NAME}" \ "${REGISTRY}/${PIPELINE_IMAGE_NAME}"; do # Keep a rollback pointer before moving :prod docker buildx imagetools create -t "${IMAGE}:prod-previous" "${IMAGE}:prod" || true docker buildx imagetools create -t "${IMAGE}:prod" "${IMAGE}:${SHORT_SHA}" echo "Promoted ${IMAGE}:${SHORT_SHA} -> :prod" done - name: Trigger production stack update run: curl -fsSk -X POST "${{ secrets.PORTAINER_PROD_WEBHOOK }}" - name: Wait for production to become healthy run: | echo "Polling ${PROD_BASE_URL} for up to 5 minutes..." for i in $(seq 1 60); do if curl -fsS -o /dev/null --max-time 10 "${PROD_BASE_URL}/"; then echo "Production is up (attempt $i)" exit 0 fi sleep 5 done echo "Production did not become healthy in time" >&2 exit 1 env: PROD_BASE_URL: ${{ secrets.PROD_BASE_URL }}