Skip to content

GitHub Actions Deploy Workflow

Each service repository that the IDP deploys needs a workflow_dispatch workflow the IDP can trigger. This page shows the exact deploy.yml used by the demo repositories (orders-service, payments-service, frontend-webapp in the idpnextdemo org).

The contract

The IDP dispatches the workflow with three inputs and then tracks the run:

InputMeaning
environmenttarget environment (dev / staging / prod) — comes from the self-service form
versionversion to deploy — comes from the self-service form
port_run_idthe IDP step callback id (sr_...). When set, the workflow reports its final status back to the IDP

Status flows back to the IDP two ways — a push callback (fast) and externalId-correlation polling (fallback), so a failed callback never leaves a step stuck; it just syncs slower.

Full working example — .github/workflows/deploy.yml

yaml
name: deploy
on:
  workflow_dispatch:
    inputs:
      environment:
        description: Target environment
        required: true
        type: choice
        options: [dev, staging, prod]
      version:
        description: Version to deploy
        required: true
        type: string
      port_run_id:
        description: IDP step callback id (sr_...) — when set, the workflow reports its status back to the IDP
        required: false
        type: string
        default: ''
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        run: |
          echo "Deploying $GITHUB_REPOSITORY version ${{ inputs.version }} to ${{ inputs.environment }}"
          sleep 20
          echo "Deployment of ${{ inputs.version }} to ${{ inputs.environment }} complete"
      # Port-style callback: report the final status to the IDP so the step
      # completes immediately. The IDP also polls this run's status (externalId
      # correlation), so a failed/skipped callback is only a slower sync, never
      # a stuck step.
      - name: Report status to IDP
        if: ${{ always() && inputs.port_run_id != '' }}
        continue-on-error: true
        env:
          IDP_BASE_URL: ${{ vars.IDP_BASE_URL || 'https://idpnext.wazyneo.com' }}
          IDP_CLIENT_ID: ${{ secrets.IDP_CLIENT_ID }}
          IDP_CLIENT_SECRET: ${{ secrets.IDP_CLIENT_SECRET }}
          PORT_RUN_ID: ${{ inputs.port_run_id }}
          JOB_STATUS: ${{ job.status }}
        run: |
          if [ -z "$IDP_CLIENT_ID" ] || [ -z "$IDP_CLIENT_SECRET" ]; then
            echo "IDP_CLIENT_ID/IDP_CLIENT_SECRET not configured; skipping IDP callback"
            exit 0
          fi
          TOKEN=$(curl -sf -X POST "$IDP_BASE_URL/v1/auth/access_token" \
            -H 'Content-Type: application/json' \
            -d "{\"clientId\":\"$IDP_CLIENT_ID\",\"clientSecret\":\"$IDP_CLIENT_SECRET\"}" | jq -r .accessToken)
          STATUS=$([ "$JOB_STATUS" = "success" ] && echo SUCCESS || echo FAILURE)
          curl -sf -X PATCH "$IDP_BASE_URL/v1/actions/runs/$PORT_RUN_ID" \
            -H "Authorization: Bearer $TOKEN" \
            -H 'Content-Type: application/json' \
            -d "{\"status\":\"$STATUS\",\"statusLabel\":\"GitHub workflow $JOB_STATUS (reported by callback)\",\"externalRunId\":\"$GITHUB_RUN_ID\"}"
          echo "Reported $STATUS for $PORT_RUN_ID to $IDP_BASE_URL"

Replace the Deploy step's echo/sleep with your real deployment commands (helm upgrade, terraform apply, etc.) — everything else stays as-is.

Step by step

  1. Inputs — declared exactly as the IDP sends them. port_run_id must default to '' so the workflow also works when triggered by a human.
  2. Deploy job — your real deployment logic. Its job.status is what gets reported.
  3. Report status to IDP:
    • if: always() && inputs.port_run_id != '' — runs on success and failure, but only for IDP-triggered runs.
    • continue-on-error: true — a broken callback must not fail your deployment.
    • Authenticates with a service-account (IDP_CLIENT_ID/IDP_CLIENT_SECRET repo secrets — same account as the ingestion).
    • PATCH /v1/actions/runs/{port_run_id} with SUCCESS/FAILURE and the externalRunId (the GitHub run id, used for correlation).

Repository secrets

Secret / VariableValue
IDP_CLIENT_ID, IDP_CLIENT_SECRET (secrets)IDP service-account credentials
IDP_BASE_URL (variable, optional)your IDP URL — defaults to the demo URL in the example

Copy deploy.yml into every repository your workflows will deploy.

Next: the GitLab CI equivalent →

IDP Next — Internal Developer Platform