Continuous Integration (CI) and Continuous Deployment/Delivery (CD) is a DevOps practice that automates software development processes, making it easier to build, test, and deploy applications quickly and reliably.
This guide explains how to set up a CI/CD pipeline for Kubernetes using GitHub Actions (or Bitbucket Pipelines). The pipeline will automatically build, test, and deploy a containerized application to a Kubernetes cluster.
Benefits of using CI/CD:
✅ Faster Development → Reduces time from coding to production.
✅ Fewer Bugs → Catches errors early in the development cycle.
✅ Reliable Deployments → Automates testing and verification.
✅ Efficient Teams → Developers spend less time on manual testing and deployment.
Pipeline example for Bitbucket
require repository variables:
...
on application deployment and select Quick View (JSON)
locate id":
<tenant>
.manage.cto2b.eudev-master
image:
ubuntu:20.04
definitions:
services:
docker:
memory: 3072
steps:
- step: &scan-docker-image
name: "Scan Docker image"
image: docker:stable
services:
- docker
caches:
- docker
script:
- apk add --no-cache curl
- curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
- docker load --input ${ECR_IMAGE_NAME}.docker
- trivy image scan --severity HIGH,CRITICAL -f json -o vulnerability.json --exit-code 0 --input ${ECR_IMAGE_NAME}.docker
- trivy convert -f table vulnerability.json
allow_failure: true
artifacts:
- vulnerability.json
- step: &build-docker-image
name: "Build Docker image"
image: docker:stable
services:
- docker
caches:
- docker
script:
- echo $BITBUCKET_COMMIT > image_tag_version.txt
- export DOCKER_BUILDKIT=1
- docker build -t $ECR_IMAGE_NAME --ssh default=$BITBUCKET_SSH_KEY_FILE -f docker/Dockerfile .
- docker save --output ${ECR_IMAGE_NAME}.docker $ECR_IMAGE_NAME
- docker images |grep $ECR_IMAGE_NAME
artifacts:
- pipeline-test.docker
- image_tag_version.txt
- step: &push-to-ecr-dev
name: "Push to ECR DEV"
image: docker:stable
script:
- |
if [ -n "$BITBUCKET_TAG" ]; then
IMAGE_TAG="$BITBUCKET_TAG";
else
IMAGE_TAG="$BITBUCKET_COMMIT";
fi
echo "Using IMAGE_TAG: dev-${IMAGE_TAG}"
docker load --input ${ECR_IMAGE_NAME}.docker
docker tag $ECR_IMAGE_NAME $ECR_IMAGE_NAME:dev-${IMAGE_TAG}
- pipe: atlassian/aws-ecr-push-image:2.2.0
variables:
AWS_ACCESS_KEY_ID: $AWS_DEV_ECR_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_DEV_ECR_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION
IMAGE_NAME: $ECR_IMAGE_NAME
TAGS: "dev-${IMAGE_TAG}"
- step: &push-to-ecr-prod
name: "Push to ECR PROD"
image: docker:stable
script:
- |
if [ -n "$BITBUCKET_TAG" ]; then
IMAGE_TAG="$BITBUCKET_TAG";
else
IMAGE_TAG="$BITBUCKET_COMMIT";
fi
echo "Using IMAGE_TAG: prod-${IMAGE_TAG}"
docker load --input ${ECR_IMAGE_NAME}.docker
docker tag $ECR_IMAGE_NAME $ECR_IMAGE_NAME:prod-${IMAGE_TAG}
- pipe: atlassian/aws-ecr-push-image:2.2.0
variables:
AWS_ACCESS_KEY_ID: $AWS_PROD_ECR_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_PROD_ECR_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION
IMAGE_NAME: $ECR_IMAGE_NAME
TAGS: "prod-${IMAGE_TAG}"
- step: &publish-image-dev
name: "Publish to Sview DEV"
image: docker:stable
services:
- docker
script:
- |
if [ -f "vulnerability.json" ]; then
export VULNERABILITY_FILE="vulnerability.json"
else
echo "File 'vulnerability.json' does not exist."
export VULNERABILITY_FILE=None
fi
- |
if [ -n "$BITBUCKET_TAG" ]; then
IMAGE_TAG="dev-${BITBUCKET_TAG}";
else
IMAGE_TAG="dev-${BITBUCKET_COMMIT}";
fi
echo "Using IMAGE_TAG: $IMAGE_TAG"
apk add git
export AUTHOR=$(git log -n 1 --format=format:'%an')
export
docker run --rm -e LOGLEVEL=debug -e IMAGE_TAG=$IMAGE_TAG -e AUTHOR="${AUTHOR}" -e SVIEW_TOKEN=$SVIEW_TOKEN -e SVIEW_APP=$SVIEW_APP_DEV -e SVIEW_URL=$SVIEW_URL -e VULNERABILITY_FILE=$VULNERABILITY_FILE -v $BITBUCKET_CLONE_DIR:/app_origin cto2b/sview-tag:0.0.3 python /app/pipeline.py "/app_origin/$VULNERABILITY_FILE"
trigger: manual
- step: &publish-image-prod
name: "Publish to Sview PROD"
image: docker:stable
services:
- docker
script:
- |
if [ -f "vulnerability.json" ]; then
export VULNERABILITY_FILE="vulnerability.json"
else
echo "File 'vulnerability.json' does not exist."
export VULNERABILITY_FILE=None
fi
- |
if [ -n "$BITBUCKET_TAG" ]; then
IMAGE_TAG="dev-${BITBUCKET_TAG}";
else
IMAGE_TAG="dev-${BITBUCKET_COMMIT}";
fi
echo "Using IMAGE_TAG: $IMAGE_TAG"
apk add git
export AUTHOR=$(git log -n 1 --format=format:'%an')
export
docker run --rm -e LOGLEVEL=debug -e IMAGE_TAG=$IMAGE_TAG -e AUTHOR="${AUTHOR}" -e SVIEW_TOKEN=$SVIEW_TOKEN -e SVIEW_APP=$SVIEW_APP_PROD -e SVIEW_URL=$SVIEW_URL -e VULNERABILITY_FILE=$VULNERABILITY_FILE -v $BITBUCKET_CLONE_DIR:/app_origin cto2b/sview-tag:0.0.3 python /app/pipeline.py "/app_origin/$VULNERABILITY_FILE"
trigger: manual
pipelines:
pull-requests:
'dev-master*':
- step: *build-docker-image
- step: *scan-docker-image
'stage-master*':
- step: *build-docker-image
- step: *scan-docker-image
'**':
- step: *build-docker-image
- step: *scan-docker-image
branches:
dev-master:
- step: *build-docker-image
- step: *scan-docker-image
- step: *push-to-ecr-dev
- step: *publish-image-dev
trigger: manual
master:
- step: *build-docker-image
- step: *scan-docker-image
- step: *push-to-ecr-prod
- step: *publish-image-prod
trigger: manual
🚀 Automation: Reduces manual work by running tests and deployments automatically.
🔍 Early Bug Detection: Finds issues early through automated testing.
🔄 Faster Releases: Speeds up the development cycle by deploying code frequently.
🔐 Security: Uses GitHub Secrets to store credentials safely.
Continuous Integration Pipelinepipeline-ci
- builds and scans image on pull request created to dev-master
branch. This one does not push new versions to ECR and not release anything as result. pipeline-ci
ensures that code changes are tested and meet quality and security standards before merging into dev-master.
Trigger: Runs when a pull request (PR) is created or updated for the dev-master branch.
pipeline-ci
Steps:
Continuous Deployment Pipeline pipeline-cd
- does the same what pipeline-ci
do and additionally pushes new application version to ECR and updates SVIEW controller with new version of the application, so it is released. pipeline-cd
automates the release process by deploying the validated application.
Trigger: Runs when code is merged into the dev-master branch (or when a new release tag is created).
pipeline-cd
Steps:
Variable Name | Description |
---|---|
APP_NAME | The name of your application. |
SVIEW_APP_NAME | The SVIEW APP name for your application. Can be taken from the SVIEW UI. |
SVIEW_URL | The company's SVIEW API URL. |
SVIEW_TOKEN | Token to authorize access to SVIEW. Must be stored in a secure place, such as secrets. |
SVIEW_IMAGE | SVIEW image to operate on SVIEW-related tasks. |
MODIFIED_VALUES | Comma-separated list of values to push to SVIEW for update. |
AWS_ACCESS_KEY_ID | Specifies an AWS access key associated with an IAM account. |
AWS_SECRET_ACCESS_KEY | Specifies the secret key associated with the access key. This is essentially the "password" for the access key. |
AWS_REGION | AWS region. |
AWS_ACCOUNT_ID | AWS account ID for ECR. |
name: Pipeline-CI
on:
pull_request:
branches:
- 'dev-master'
env:
APP_NAME: some_name
APP_IMAGE_TAG: ${{ github.sha }}
AWS_REGION: eu-west-1
jobs:
CI-Build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker Build
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: eu-west-1
AWS_ACCOUNT_ID: 231342434
run: |
aws ecr get-login-password --region ${{ env.AWS_REGION }} | docker login --username AWS --password-stdin ${{ env.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com
docker build -t ${{ env.APP_NAME }}:${{ github.sha }} -f Dockerfile .
- name: Install Trivy
run: |
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
- name: Scan Image
run: |
trivy image --exit-code 0 --cache-dir .trivycache/ --no-progress --severity HIGH,CRITICAL --format json -o vulnerability-report.json ${{ env.APP_NAME }}:${{ github.sha }}
trivy convert --format table vulnerability-report.json
- name: Archive vulnerability scan results
uses: actions/upload-artifact@v4
with:
name: vulnerability-report
path: vulnerability-report.json
- name: License Scan
run: |
trivy image --scanners license --severity UNKNOWN,HIGH,CRITICAL --exit-code 0 --cache-dir .trivycache/ --no-progress --license-full --format json -o license-report.json ${{ env.APP_NAME }}:${{ github.sha }}
trivy convert --format table license-report.json
- name: Archive license scan results
uses: actions/upload-artifact@v4
with:
name: license-report
path: license-report.json
name: Pipeline-CD
on:
pull_request:
branches:
- 'master'
types:
- 'closed'
env:
APP_NAME: some_name
APP_IMAGE_TAG: ${{ github.sha }}
AWS_REGION: eu-west-1
AWS_ACCOUNT_ID: 231342434
jobs:
CI-Build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker Build
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws ecr get-login-password --region ${{ env.AWS_REGION }} | docker login --username AWS --password-stdin ${{ env.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com
docker build -t ${{ env.APP_NAME }}:${{ github.sha }} -f Dockerfile .
- name: Install Trivy
run: |
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
- name: Scan Image
run: |
trivy image --exit-code 0 --cache-dir .trivycache/ --no-progress --severity HIGH,CRITICAL --format json -o vulnerability-report.json ${{ env.APP_NAME }}:${{ github.sha }}
trivy convert --format table vulnerability-report.json
- name: Archive vulnerability scan results
uses: actions/upload-artifact@v4
with:
name: vulnerability-report
path: vulnerability-report.json
- name: License Scan
run: |
trivy image --scanners license --severity UNKNOWN,HIGH,CRITICAL --exit-code 0 --cache-dir .trivycache/ --no-progress --license-full --format json -o license-report.json ${{ env.APP_NAME }}:${{ github.sha }}
trivy convert --format table license-report.json
- name: Archive license scan results
uses: actions/upload-artifact@v4
with:
name: license-report
path: license-report.json
- name: Push to ECR
id: ecr
uses: jwalton/gh-ecr-push@v2
with:
access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
region: ${{ env.AWS_REGION }}
image: ${{ env.APP_NAME }}:${{ env.APP_IMAGE_TAG }}
local-image: app_app
- name: Push to SVIEW
env:
SVIEW_APP_NAME: "dev-ytdevnp-app-k8s-generic-dev-yt-test"
SVIEW_URL: "https://api.sview.youtech.manage.cto2b.eu"
SVIEW_TOKEN: ${{ secrets.SVIEW_TOKEN }}
SVIEW_IMAGE: "cto2b/sview-tag:0.0.5"
MODIFIED_VALUES: "image.tag=${{ env.APP_IMAGE_TAG }},github.actor=${{ github.actor }}"
VULNERABILITY_FILE: "vulnerability-report.json"
run: |
docker run --rm -e MODIFIED_VALUES=${MODIFIED_VALUES} -e SVIEW_TOKEN=${{ secrets.SVIEW_TOKEN }} -e SVIEW_APP=${SVIEW_APP_NAME} -e SVIEW_URL=${SVIEW_URL} -e VULNERABILITY_FILE=${VULNERABILITY_FILE} -v ${{ github.workspace }}:/app_origin ${SVIEW_IMAGE} python /app/pipeline.py "/app_origin/${VULNERABILITY_FILE}"