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 PRfrom 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 branchDEST_BRANCH=mainby default (override withDESTINATION_BRANCH)
- Authenticate safely
- Requires
BITBUCKET_ACCESS_TOKEN - Sends
Authorizatio n: Bearer ...
- Requires
- Avoid PR spam
- Query PRs filtered by
state=OPENandsource.branch.name=${SRC_BRANCH} - If one exists, exit successfully
- Query PRs filtered by
- Create the PR
- POST to
/pullrequestswith a simple JSON payload
- POST to
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_TOKENas 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/*orenv/*).