How to Build and Deploy App to Kubernetes Using Bitbucket Pipelines

Step-by-step guide to building a Docker image and deploying to Kubernetes with Bitbucket Pipelines. Configure repo variables, Helm, and a two-step pipeline for build and deploy.

Automating build and deployment from Bitbucket to Kubernetes lets you push code and have a new image built and deployed without manual steps. Bitbucket Pipelines is a built-in CI/CD service that runs pipelines defined in YAML in your repository.

In this guide you will:

  • Configure repository variables in Bitbucket for Docker Hub and Kubernetes access
  • Use a two-step pipeline: build a Docker image and push it, then deploy to Kubernetes with Helm
  • Optionally run the deploy step manually so you control when releases go out

Pipelines can be simple or complex; splitting work into steps helps because each step can be re-run on its own, and only the repo and any declared artifacts are passed between steps.

Prerequisites

Before you start:

  1. Bitbucket repository – Your application code is in a Bitbucket repo (not necessarily Bitbucket Server; Pipelines runs on Bitbucket Cloud).
  2. Kubernetes cluster – A cluster you can deploy to (e.g. kubeadm, K3s, or a managed cluster). kubectl should be configured locally so you can export a kubeconfig.
  3. Docker Hub (or another registry) – An account to push images. The pipeline below uses Docker Hub; you can switch to another registry by changing the login and image name.
  4. Helm – The deploy step installs Helm in the pipeline; your chart can live in the repo or in a Helm chart repository.

For more on Pipelines configuration, see Bitbucket: Configure Pipelines variables.

Configure Repository Variables in Bitbucket

Store credentials and the Kubernetes kubeconfig as repository variables so the pipeline can push images and deploy without hardcoding secrets.

  1. In your Bitbucket repo, go to Repository settingsPipelinesRepository variables.
  2. Add the following variables. For secrets, enable Secured so they are masked in logs.
VariableSecuredDescription
DOCKERHUB_USERNAMENoDocker Hub username for pushing images.
DOCKERHUB_PASSWORDYesDocker Hub token or password; prefer access token.
KUBECONFIGYesBase64-encoded kubeconfig so the pipeline can authenticate to the cluster.

Getting the KUBECONFIG value

Use a kubeconfig that has access only to the cluster (and namespace) you need. From your workstation:

  1. Switch to the right context (if you use multiple clusters):

    1
    2
    
    kubectl config use-context dev-ello-platform-k3s
    # or: k ctx dev-ello-platform-k3s   # if you use kubectx
    
  2. Export the current context, minified and raw:

    1
    
    kubectl config view --minify --raw
    
  3. Base64-encode it (Bitbucket expects the variable to be the encoded string):

    1
    2
    
    kubectl config view --minify --raw | base64
    # or on Alpine: openssl base64
    
  4. Copy the output and paste it as the value of the KUBECONFIG repository variable (Secured).

The pipeline will decode this and write it to a file, then set KUBECONFIG so kubectl and Helm use it.

Pipeline Overview

The example pipeline has two steps:

  1. Build Docker image – Build the image, log in to Docker Hub, push the image with a tag derived from branch and commit (e.g. main-abc1234).
  2. Deploy to Kubernetes – Install Helm, decode KUBECONFIG, add your Helm repo, and run helm upgrade --install with the new image tag and a values file (e.g. deploy/helm/dev.yml).

The pipeline runs on every push to main; you can change the branch or add a manual trigger for the deploy step (see below).

Example: bitbucket-pipelines.yml

Place this file at the root of your repository as bitbucket-pipelines.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
image: alpine:3.21
options:
  docker: true
definitions:
  services:
    docker:
      memory: 2048
pipelines:
  branches:
    main:
      - step:
          name: Build Docker Image
          script:
            - export IMAGE_NAME="ektowett/nestsimql:${BITBUCKET_BRANCH}-${BITBUCKET_COMMIT::7}"
            - docker build -t $IMAGE_NAME .
            - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
            - docker push $IMAGE_NAME
          services:
            - docker
      - step:
          name: Deploy to Kubernetes
          script:
            - apk add --update --no-cache yq bash curl openssl
            - curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
            - export IMAGE_TAG="${BITBUCKET_BRANCH}-${BITBUCKET_COMMIT::7}"
            - echo "$KUBECONFIG" | base64 -d > kubeconfig.yml
            - export KUBECONFIG=./kubeconfig.yml
            - helm repo add mycharts https://etowett.github.io/helm-charts
            - helm repo update
            - >-
              helm upgrade --install --debug nestsimql mycharts/app
              --version 0.1.8
              --namespace=dev
              --create-namespace
              --timeout 300s
              --wait
              --set image.tag=${IMAGE_TAG}
              -f deploy/helm/dev.yml

Notes:

  • IMAGE_NAME / IMAGE_TAG – Uses BITBUCKET_BRANCH and the first 7 characters of BITBUCKET_COMMIT so each build has a unique tag (e.g. main-abc1234).
  • Docker – The build step runs with the Docker service so docker build and docker push work.
  • Helm – The deploy step installs Helm and uses a chart from https://etowett.github.io/helm-charts; replace with your own repo and chart name.
  • Manual deploy – To require a manual approval before deploy, add trigger: manual under the deploy step (see Bitbucket Pipelines triggers).

Example: Helm Values File (dev.yml)

The pipeline passes a values file, e.g. deploy/helm/dev.yml. The chart is expected to support at least image.repository, image.tag, and your app’s settings (replicas, service, ingress, env). Example:

deploy/helm/dev.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
replicaCount: 1

image:
  repository: ektowett/nestsimql
  pullPolicy: IfNotPresent
  tag: latest

serviceAccount:
  create: true

service:
  type: ClusterIP
  name: nestsimql
  port: 4027

resources:
  limits:
    cpu: 2000m
    memory: 2048Mi
  requests:
    cpu: 100m
    memory: 64Mi

ingress:
  enabled: true
  className: traefik
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod-issuer
  hosts:
    - host: nestsimql.in.citizix.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: nestsimql-internal-tls
      hosts:
        - nestsimql.in.citizix.com

env:
  PORT: "4027"
  LOG_LEVEL: debug
  DB_URI: mongodb://root:[email protected]:27017
  DB_NAME: dev_nestsimql
  ENV: dev

The pipeline overrides image.tag with --set image.tag=${IMAGE_TAG}, so the value of tag here is only a default. For production, avoid committing secrets (e.g. DB_URI with password) in this file; use Kubernetes Secrets or a secrets manager (e.g. HashiCorp Vault) and reference them in the chart. For Ingress TLS, see How to Configure Ingress TLS/SSL in Kubernetes.

Running the Pipeline

  1. Commit and push bitbucket-pipelines.yml (and deploy/helm/dev.yml if needed) to the main branch.
  2. Bitbucket Pipelines will run the pipeline automatically on push.
  3. Watch progress under Pipelines in the repo. If the deploy step has trigger: manual, run that step from the Pipelines UI when you want to deploy.

After a successful run, the new image tag will be deployed to the dev namespace (or whatever namespace and values you use).

Troubleshooting

  • Docker login failed – Check DOCKERHUB_USERNAME and DOCKERHUB_PASSWORD. Use a Docker Hub access token and ensure the variable is not truncated (no newlines in the base64 if you ever encode something else).
  • Helm / kubectl: permission denied or 403 – The kubeconfig in KUBECONFIG must have RBAC permissions to create/update resources in the target namespace. Test locally with the same kubeconfig.
  • Image pull back-off – The cluster must be able to pull the image (e.g. from Docker Hub). If the repo is private, create an imagePullSecret and reference it in the Helm chart.
  • Pipeline variable not set – Ensure the variable is defined under Repository variables (or Workspace/Deployment variables if you use those) and that the pipeline runs in a context where those variables are available.

Summary

You now have a Bitbucket Pipelines setup that:

  1. Builds a Docker image and pushes it to Docker Hub (or another registry) with a tag like main-<commit-sha>.
  2. Deploys to Kubernetes with Helm, using a values file and overriding the image tag.

Store Docker and Kubernetes credentials as secured repository variables, and prefer Kubernetes Secrets or a secrets manager for application secrets (e.g. DB credentials) instead of putting them in the values file in the repo. For more advanced Kubernetes deployment (e.g. GitOps with Argo CD), see How to Deploy and Configure Argo CD in Kubernetes.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy