CI/CD with Bitbucket Pipelines: Build, Test, and Deploy
A practical guide to CI/CD concepts, pipeline triggers, YAML configuration, security checks, and deployment flow.

CI/CD can look complicated at first, but the main idea is simple:
Instead of building, testing, and deploying manually, we let an automated pipeline do it for us.
This reduces mistakes, catches problems earlier, and makes deployments more predictable.
What is CI/CD?
CI/CD usually means:
| Term | Meaning |
|---|---|
| CI | Continuous Integration |
| CD | Continuous Delivery or Continuous Deployment |
Continuous Integration
CI means every code change is automatically checked.
A typical CI process:
Developer pushes code
↓
Pipeline starts
↓
Code is built
↓
Tests run
↓
Problems are reported
The goal is to catch issues early.
Continuous Delivery
Continuous Delivery means the application is automatically prepared for deployment.
The pipeline may build artifacts, run tests, create Docker images, and make the application ready to release.
Deployment might still require manual approval.
Continuous Deployment
Continuous Deployment goes one step further.
If all checks pass, the application is deployed automatically.
This is powerful, but it requires strong tests, monitoring, and rollback strategies.
Why CI/CD Matters
Without CI/CD, deployment often depends on manual steps:
Build locally
Run some tests manually
Copy files
Deploy manually
Hope nothing breaks
With CI/CD:
Push code
Pipeline builds
Pipeline tests
Pipeline checks security
Pipeline packages the app
Pipeline deploys or prepares deployment
That gives the team more confidence.
Main Benefits
| Benefit | Why it matters |
|---|---|
| Faster feedback | Bugs are found minutes after pushing code |
| Safer releases | Tests and checks run automatically |
| Repeatable deployments | Same process every time |
| Less manual work | Developers focus more on building features |
| Better visibility | Pipeline logs show what happened |
What is Bitbucket Pipelines?
Bitbucket Pipelines is Bitbucket’s built-in CI/CD tool.
It runs automated workflows when code is pushed to a repository.
The pipeline configuration is stored in a file called:
bitbucket-pipelines.yml
This file usually lives in the root of the project.
Basic Pipeline Flow
A simple pipeline can look like this:
Code push
↓
Install dependencies
↓
Build project
↓
Run tests
↓
Create artifact
↓
Deploy
For a Java project, this might include Maven commands.
For a frontend project, it might include npm commands.
Basic Bitbucket Pipeline Example
image: node:20
pipelines:
default:
- step:
name: Install and Test
script:
- npm install
- npm test
This means:
Use a Node.js Docker image
Run the pipeline on every push
Install dependencies
Run tests
Java / Maven Pipeline Example
image: maven:3.9-eclipse-temurin-17
pipelines:
default:
- step:
name: Build and Test
caches:
- maven
script:
- mvn clean install
This pipeline:
uses Java 17
uses Maven
caches Maven dependencies
builds the project
runs tests
Important Pipeline Concepts
| Concept | Meaning |
|---|---|
| Pipeline | The full automation workflow |
| Step | One unit of work inside the pipeline |
| Script | Commands executed inside a step |
| Cache | Saved dependencies to speed up builds |
| Artifact | Files saved after a step |
| Service | Extra runtime dependency, like Docker |
| Trigger | Event that starts the pipeline |
Pipeline Triggers
Pipelines can run in different situations.
| Trigger | Example |
|---|---|
| Any push | Run tests on every branch |
| Specific branch | Deploy when code is merged to main |
| Git tag | Release version v1.2.0 |
| Manual trigger | Run a pipeline from the Bitbucket UI |
| Pull request | Validate code before merging |
Example:
pipelines:
default:
- step:
name: Run Tests
script:
- npm test
branches:
main:
- step:
name: Deploy
script:
- echo "Deploying..."
Here:
every branch runs tests
only
mainruns deployment
YAML Basics
Bitbucket Pipelines uses YAML.
A few basics:
name: Example
script:
- echo "First command"
- echo "Second command"
step:
name: Build
script:
- npm install
- npm run build
Indentation matters a lot in YAML.
Wrong indentation can break the pipeline.
Reusable Steps
Pipelines can use anchors to avoid repeating code.
definitions:
steps:
- step: &build-step
name: Build
script:
- npm install
- npm run build
pipelines:
default:
- step: *build-step
branches:
main:
- step: *build-step
&build-step defines a reusable step. *build-step uses it.
This is useful when multiple branches share the same build logic.
Caching
Caching makes pipelines faster.
For example:
caches:
- maven
or:
caches:
- node
Without cache, dependencies may be downloaded every time.
With cache, the pipeline can reuse previously downloaded dependencies.
Common caches:
| Ecosystem | Cache |
|---|---|
| Java / Maven | maven |
| Node.js | node |
| Python | custom pip cache |
| Security tools | custom cache |
Artifacts
Artifacts are files saved from one step.
Examples:
build files
test reports
coverage reports
JAR files
generated frontend assets
Example:
artifacts:
- target/*.jar
- target/surefire-reports/**
Artifacts are useful for debugging and for passing files between pipeline steps.
Services
Some pipeline steps need extra services.
Example:
services:
- docker
This is common when the pipeline needs to:
build Docker images
run integration tests
use Testcontainers
start temporary databases
Security Checks
A good pipeline should not only build and test the code.
It should also check for security problems.
Examples:
dependency vulnerability scanning
secret scanning
static analysis
container image scanning
For Java projects, OWASP Dependency Check is commonly used to scan dependencies for known vulnerabilities.
Example idea:
Pipeline
↓
Install dependencies
↓
Run tests
↓
Scan dependencies
↓
Build artifact
Security checks should happen before deployment, not after.
OIDC Authentication
Modern pipelines should avoid long-lived cloud credentials.
OIDC is a safer way for CI/CD systems to authenticate with cloud providers.
The idea:
Pipeline starts
↓
CI provider creates a short-lived identity token
↓
Cloud provider verifies the token
↓
Cloud provider gives temporary credentials
↓
Pipeline deploys safely
This is better than storing permanent AWS keys in repository variables.
Deployment with Tags
Some teams use Git tags to trigger deployments.
Example:
git tag v1.2.0
git push origin v1.2.0
Then the pipeline can detect the tag and run a release flow.
Example:
pipelines:
tags:
"v*":
- step:
name: Release
script:
- echo "Releasing version"
Tags are useful because they make releases explicit and traceable.
Debugging Failed Pipelines
When a pipeline fails, I usually check:
Which step failed?
What was the exact error message?
Did dependencies install correctly?
Did tests fail?
Did the build run out of memory?
Did authentication fail?
Are required environment variables missing?
A failed pipeline is not just a blocker. It is feedback from the automation system.
Useful Commands
Run tests locally before pushing:
npm test
For Java/Maven:
mvn test
Build a Java project:
mvn clean install
Create a Git tag:
git tag v1.2.0
git push origin v1.2.0
Delete a local tag:
git tag -d v1.2.0
Delete a remote tag:
git push origin --delete v1.2.0
My Takeaway
The most useful way to think about CI/CD is:
CI/CD is an automated safety net for code changes.
It does not replace good engineering judgment, but it reduces avoidable mistakes.
A good pipeline should:
build the project
run tests
check security risks
create deployable artifacts
make deployment repeatable
provide clear logs when something fails
Once I understood the pipeline as a sequence of small automated checks, Bitbucket Pipelines became much easier to reason about.





