I’m using FluxCD for GitOps: Flux (or an automation) updates container image tags in Git, and the cluster reconciles from what’s merged. The nice part is you get auditability and rollbacks “for free”. The annoying part is the small manual step that keeps repeating:

  • Flux updates something on an environment branch
  • Someone still has to go to Bitbucket and open a Pull Request into main

After doing this a few times, I decided to automate the boring part (creating the PR), while still keeping the important part (review/approval) manual.

This post shares a small Bitbucket Pipelines step that:

  • Detects the current branch (BITBUCKET_BRANCH)
  • Checks if there is already an open PR from that branch
  • If not, creates a PR via Bitbucket REST API
  • Uses an access token (Bearer) only

What the pipeline does

  • Set source/destination branch
    • SRC_BRANCH = current pipeline branch
    • DEST_BRANCH = main by default (override with DESTINATION_BRANCH)
  • Authenticate safely
    • Requires BITBUCKET_ACCESS_TOKEN
    • Sends Authorizatio n: Bearer ...
  • Avoid PR spam
    • Query PRs filtered by state=OPEN and source.branch.name=${SRC_BRANCH}
    • If one exists, exit successfully
  • Create the PR
    • POST to /pullrequests with a simple JSON payload

The bitbucket-pipelines.yml snippet

Below is the exact step I’m using (you can paste it under definitions.steps and call it from your pipeline). It uses alpine tooling and installs curl + jq.

- step: &create-flux-pr
    name: Create Flux image update PR
    script:
      - apk add --no-cache curl jq
      - SRC_BRANCH="${BITBUCKET_BRANCH}"   # current env branch
      - DEST_BRANCH=${DESTINATION_BRANCH:-"main"}
      - TITLE="FluxCD Image Update - ${SRC_BRANCH}"
      - DESCRIPTION="Automated image update from Flux for ${SRC_BRANCH}"

      - REPO_OWNER=${REPO_OWNER:-$BITBUCKET_REPO_OWNER}
      - REPO_SLUG=${REPO_SLUG:-$BITBUCKET_REPO_SLUG}

      # Auth: access token ONLY (Bearer)
      - |
        if [ -z "${BITBUCKET_ACCESS_TOKEN:-}" ]; then
          echo "ERROR: BITBUCKET_ACCESS_TOKEN is required (access-token-only mode)."
          exit 1
        fi
        AUTH_HEADER="Authorization: Bearer ${BITBUCKET_ACCESS_TOKEN}"        

      # Check existing PR
      - |
        EXISTING_PR_SIZE=$(curl -s -H "${AUTH_HEADER}" \
          "https://api.bitbucket.org/2.0/repositories/${REPO_OWNER}/${REPO_SLUG}/pullrequests?state=OPEN&source.branch.name=${SRC_BRANCH}" \
          | jq '.size')
        if [ "${EXISTING_PR_SIZE}" -gt 0 ]; then
          echo "Open PR from ${SRC_BRANCH} already exists, exiting."
          exit 0
        fi        

      # Create PR
      - |
        curl -s -H "${AUTH_HEADER}" \
          -H "Content-Type: application/json" \
          -X POST \
          "https://api.bitbucket.org/2.0/repositories/${REPO_OWNER}/${REPO_SLUG}/pullrequests" \
          -d "{
            \"title\": \"${TITLE}\",
            \"description\": \"${DESCRIPTION}\",
            \"source\": { \"branch\": { \"name\": \"${SRC_BRANCH}\" } },
            \"destination\": { \"branch\": { \"name\": \"${DEST_BRANCH}\" } },
            \"close_source_branch\": false
          }"        

Why I like this approach

  • Small automation, big payoff: it removes repetitive UI work.
  • Idempotent: rerunning the pipeline won’t create duplicate PRs.
  • Still human-reviewed: it doesn’t auto-merge; it just prepares the PR.
  • Fits GitOps well: Flux can keep doing updates, and Git keeps being the control plane.

Notes / tips

  • Store BITBUCKET_ACCESS_TOKEN as a secured repository variable.
  • Scope the token to the minimum permissions required to list and create PRs.
  • If your workflow targets another branch, set DESTINATION_BRANCH (e.g., release/* or env/*).