Pipelines is Bitbucket’s built-in CI/CD. Each step runs in a fresh container on Atlassian’s cloud runners (or on your own runners), configured by a bitbucket-pipelines.yml file in the repository root. Because the pipeline lives in the repository, changes to it are reviewed like code.
Versions in use on this date. Node.js 18 (active LTS) and Docker 23.0 (released 7 February 2023, which enables BuildKit by default on Linux). Build minutes and memory limits depend on your plan, so check the current plan page.
Pipeline design
flowchart TB
PRS[Pull request opened or updated] --> T1[Step Test<br/>npm ci and npm test, cache node_modules]
T1 --> STATUS[Build status shown on the pull request<br/>merge check]
MAIN[Merge to main] --> T2[Step Test]
T2 --> BUILD[Step Build and push image<br/>docker service]
BUILD --> AUTH[Assume AWS role with OIDC<br/>no stored keys]
AUTH --> ECR[(Amazon ECR repository)]
ECR --> STG[Step Deploy staging - automatic]
STG --> GATE{Manual trigger}
GATE -->|approved by authorised person| PROD[Step Deploy production]
TAG[Git tag v*] --> REL[Release pipeline]
A fuller pipeline
image: node:18
definitions:
caches:
npm: ~/.npm
services:
docker:
memory: 2048 # give image builds 2 GB instead of the default
steps:
- step: &test
name: Test
caches: [node]
script:
- npm ci
- npm run lint
- npm test -- --ci
artifacts:
- coverage/**
pipelines:
pull-requests:
'**':
- step: *test
branches:
main:
- step: *test
- step:
name: Build and push image
oidc: true
services: [docker]
caches: [docker]
script:
- export IMAGE=$REGISTRY/shop-api:$BITBUCKET_COMMIT
- docker build -t $IMAGE .
- pipe: atlassian/aws-ecr-push-image:<current-version>
variables:
AWS_OIDC_ROLE_ARN: $AWS_ROLE_ARN
AWS_DEFAULT_REGION: ap-south-1
IMAGE_NAME: shop-api
TAGS: $BITBUCKET_COMMIT
- step:
name: Deploy to staging
deployment: staging
script:
- ./scripts/deploy.sh staging $BITBUCKET_COMMIT
- step:
name: Deploy to production
deployment: production
trigger: manual
script:
- ./scripts/deploy.sh production $BITBUCKET_COMMIT
tags:
'v*':
- step:
name: Release
script:
- ./scripts/release.sh $BITBUCKET_TAG
Check the exact pipe version on the Bitbucket pipes page before you copy it, since pipes are versioned and updated. If you prefer plain commands, use the AWS CLI inside the step after OIDC login.
Reading it:
pull-requestsruns the tests on every pull request branch, so the merge check has something to require.definitionswith YAML anchors (&testand*test) share a step between pipelines, avoiding copy-paste.caches: [node]is a built-in cache fornode_modules; adddockerto reuse image layers.artifactspass files (test reports, a built package) to later steps.deploymentties a step to an environment so the Deployments dashboard shows what is where, and it supports environment-specific variables and deployment permissions (who may deploy to production).trigger: manualadds a button, a simple approval gate.condition: changesets: includePaths: ['services/api/**']on a step skips it when unrelated files change.
Authenticate without stored keys
Pipelines can present a signed OpenID Connect token for each run. In AWS create an IAM identity provider for your workspace’s Bitbucket OIDC URL (shown under Repository settings, OpenID Connect), and a role whose trust policy matches the repository’s UUID. The step sets oidc: true and the pipe or CLI exchanges the token for temporary credentials. This avoids long-lived AWS_ACCESS_KEY_ID secrets, which are a standing risk if they leak in logs or a compromised dependency.
If you must use passwords, store REGISTRY_USER and REGISTRY_PASSWORD as secured variables at repository or workspace level (their values are masked in logs and cannot be read back), and always pipe the password in:
echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY"
Image hygiene
- Tag with
$BITBUCKET_COMMIT(traceable to a commit), never rely onlatestfor deployment. - Add a scan step, for example
trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE, and fail the build on findings you cannot accept. - Use a minimal base image and a multi-stage Dockerfile to keep images small and the build fast.
- Do not bake secrets into the image; inject them at runtime.
Cost and speed
Build minutes are metered per plan, and the Free plan includes a small monthly allowance, so efficiency matters:
- Cache dependencies and Docker layers.
- Fail fast: run lint and unit tests first, slow integration tests later or in parallel (
parallel:block). - Use
condition: changesetsto skip unrelated work in a monorepo. - Set
max-timeon steps so a hung job cannot burn minutes. - Consider self-hosted runners for heavy builds or private network access, remembering that you then patch and secure them.
Troubleshooting
| Symptom | Likely cause |
|---|---|
Cannot connect to the Docker daemon |
Step missing services: [docker] |
| Docker step killed, exit code 137 | Out of memory; raise the docker service memory or use a larger step size: 2x |
| Variables empty in a pull request from a fork | Secured variables are not shared with forks, by design |
| Manual step never appears | The previous step failed, or the deployment environment is locked or needs permission |
| Pipeline did not trigger | Branch or tag pattern does not match, or the file is on a different branch |
| Image pull rate-limit errors | Use an image from your registry or a mirror instead of anonymous Docker Hub pulls |
Validate YAML with the editor’s built-in validator in Pipelines, Edit before committing.