CI/CD integration
Automate container image builds and pushes using your existing CI/CD platform. This guide provides configuration examples for popular CI/CD systems.
Prerequisites
Before configuring your CI/CD pipeline:
Create an API token with the Administrator role (required for pushing images). See API tokens.
Store credentials securely using your platform’s secret management feature. Never commit tokens to source control.
Note your registry URL:
YOUR_ORG_ID.REGION.registry.cloudfleet.dev(regions:europe,northamerica,apac)
GitHub Actions
GitHub Actions is the recommended CI/CD platform for GitHub repositories.
Basic configuration
Create .github/workflows/build-and-push.yml:
name: Build and Push
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ${{ secrets.CF_ORG_ID }}.${{ secrets.CFCR_REGION }}.registry.cloudfleet.dev
IMAGE_NAME: myapp
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Cloudfleet Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.CF_TOKEN_ID }}
password: ${{ secrets.CF_TOKEN_SECRET }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=sha,prefix=sha-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Configure secrets
In your GitHub repository:
- Navigate to Settings > Secrets and variables > Actions
- Add the following secrets:
CF_ORG_ID: Your organization IDCFCR_REGION: Your registry region (europe,northamerica, orapac)CF_TOKEN_ID: API token IDCF_TOKEN_SECRET: API token secret
Multi-architecture builds
Build images for multiple architectures:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
Deploy to CFKE after push
Trigger a deployment after pushing the image:
deploy:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Cloudfleet CLI
run: |
curl -fsSL https://downloads.cloudfleet.ai/apt/pubkey.gpg | sudo tee /usr/share/keyrings/cloudfleet-archive-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudfleet-archive-keyring.gpg] https://downloads.cloudfleet.ai/apt stable main" | sudo tee /etc/apt/sources.list.d/cloudfleet.list
sudo apt-get update && sudo apt-get install -y cloudfleet
- name: Configure Cloudfleet CLI
env:
CLOUDFLEET_ORGANIZATION_ID: ${{ secrets.CF_ORG_ID }}
CLOUDFLEET_ACCESS_TOKEN_ID: ${{ secrets.CF_TOKEN_ID }}
CLOUDFLEET_ACCESS_TOKEN_SECRET: ${{ secrets.CF_TOKEN_SECRET }}
run: |
cloudfleet clusters kubeconfig ${{ secrets.CF_CLUSTER_ID }} > kubeconfig.yaml
- name: Deploy to CFKE
run: |
kubectl --kubeconfig=kubeconfig.yaml set image deployment/myapp \
myapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
GitLab CI
GitLab CI integrates natively with GitLab repositories.
Basic configuration
Create .gitlab-ci.yml:
stages:
- build
- deploy
variables:
REGISTRY: ${CF_ORG_ID}.${CFCR_REGION}.registry.cloudfleet.dev
IMAGE_NAME: myapp
build:
stage: build
image: docker:24
services:
- docker:24-dind
before_script:
- docker login $REGISTRY -u $CF_TOKEN_ID -p $CF_TOKEN_SECRET
script:
- docker build -t $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA .
- docker push $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA
- |
if [ "$CI_COMMIT_BRANCH" == "main" ]; then
docker tag $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA $REGISTRY/$IMAGE_NAME:latest
docker push $REGISTRY/$IMAGE_NAME:latest
fi
rules:
- if: $CI_COMMIT_BRANCH
deploy:
stage: deploy
image: bitnami/kubectl:latest
before_script:
- |
curl -fsSL https://downloads.cloudfleet.ai/cli/latest/cloudfleet-linux-amd64.zip -o cloudfleet.zip
unzip cloudfleet.zip
chmod +x cloudfleet
script:
- |
export CLOUDFLEET_ORGANIZATION_ID=$CF_ORG_ID
export CLOUDFLEET_ACCESS_TOKEN_ID=$CF_TOKEN_ID
export CLOUDFLEET_ACCESS_TOKEN_SECRET=$CF_TOKEN_SECRET
./cloudfleet clusters kubeconfig $CF_CLUSTER_ID > kubeconfig.yaml
- kubectl --kubeconfig=kubeconfig.yaml set image deployment/myapp myapp=$REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main"
Configure variables
In your GitLab project:
- Navigate to Settings > CI/CD > Variables
- Add the following variables (mark as protected and masked):
CF_ORG_ID: Your organization IDCFCR_REGION: Your registry region (europe,northamerica, orapac)CF_TOKEN_ID: API token IDCF_TOKEN_SECRET: API token secretCF_CLUSTER_ID: Target cluster ID (for deployments)
Using Kaniko for rootless builds
If your GitLab runners do not support Docker-in-Docker:
build:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.19.2-debug
entrypoint: [""]
script:
- |
cat > /kaniko/.docker/config.json << EOF
{
"auths": {
"${REGISTRY}": {
"username": "${CF_TOKEN_ID}",
"password": "${CF_TOKEN_SECRET}"
}
}
}
EOF
- /kaniko/executor
--context $CI_PROJECT_DIR
--dockerfile $CI_PROJECT_DIR/Dockerfile
--destination $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA
--destination $REGISTRY/$IMAGE_NAME:latest
Jenkins
Jenkins provides flexible CI/CD pipelines for enterprise environments.
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
environment {
REGISTRY = "${CF_ORG_ID}.${CFCR_REGION}.registry.cloudfleet.dev"
IMAGE_NAME = 'myapp'
REGISTRY_CREDS = credentials('cloudfleet-registry')
}
stages {
stage('Build') {
steps {
script {
docker.build("${REGISTRY}/${IMAGE_NAME}:${env.BUILD_NUMBER}")
}
}
}
stage('Push') {
steps {
script {
docker.withRegistry("https://${REGISTRY}", 'cloudfleet-registry') {
docker.image("${REGISTRY}/${IMAGE_NAME}:${env.BUILD_NUMBER}").push()
docker.image("${REGISTRY}/${IMAGE_NAME}:${env.BUILD_NUMBER}").push('latest')
}
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
withCredentials([
string(credentialsId: 'cf-org-id', variable: 'CF_ORG_ID'),
string(credentialsId: 'cf-token-id', variable: 'CF_TOKEN_ID'),
string(credentialsId: 'cf-token-secret', variable: 'CF_TOKEN_SECRET'),
string(credentialsId: 'cf-cluster-id', variable: 'CF_CLUSTER_ID')
]) {
sh '''
export CLOUDFLEET_ORGANIZATION_ID=$CF_ORG_ID
export CLOUDFLEET_ACCESS_TOKEN_ID=$CF_TOKEN_ID
export CLOUDFLEET_ACCESS_TOKEN_SECRET=$CF_TOKEN_SECRET
cloudfleet clusters kubeconfig $CF_CLUSTER_ID > kubeconfig.yaml
kubectl --kubeconfig=kubeconfig.yaml set image deployment/myapp myapp=${REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}
'''
}
}
}
}
}
Configure credentials
In Jenkins:
- Navigate to Manage Jenkins > Manage Credentials
- Add a Username with password credential:
- ID:
cloudfleet-registry - Username: Your API token ID
- Password: Your API token secret
- ID:
- Add Secret text credentials for:
cf-org-idcfcr-regioncf-token-idcf-token-secretcf-cluster-id
CircleCI
CircleCI provides cloud-native CI/CD with Docker support.
Configuration
Create .circleci/config.yml:
version: 2.1
orbs:
docker: circleci/[email protected]
jobs:
build-and-push:
docker:
- image: cimg/base:current
steps:
- checkout
- setup_remote_docker:
version: 24.0.7
- run:
name: Build image
command: |
docker build -t $CF_ORG_ID.$CFCR_REGION.registry.cloudfleet.dev/myapp:$CIRCLE_SHA1 .
- run:
name: Push image
command: |
echo $CF_TOKEN_SECRET | docker login $CF_ORG_ID.$CFCR_REGION.registry.cloudfleet.dev \
--username $CF_TOKEN_ID --password-stdin
docker push $CF_ORG_ID.$CFCR_REGION.registry.cloudfleet.dev/myapp:$CIRCLE_SHA1
if [ "$CIRCLE_BRANCH" == "main" ]; then
docker tag $CF_ORG_ID.$CFCR_REGION.registry.cloudfleet.dev/myapp:$CIRCLE_SHA1 \
$CF_ORG_ID.$CFCR_REGION.registry.cloudfleet.dev/myapp:latest
docker push $CF_ORG_ID.$CFCR_REGION.registry.cloudfleet.dev/myapp:latest
fi
deploy:
docker:
- image: cimg/base:current
steps:
- checkout
- run:
name: Install Cloudfleet CLI
command: |
curl -fsSL https://downloads.cloudfleet.ai/cli/latest/cloudfleet-linux-amd64.zip -o cloudfleet.zip
unzip cloudfleet.zip
sudo mv cloudfleet /usr/local/bin/
- run:
name: Deploy to CFKE
command: |
export CLOUDFLEET_ORGANIZATION_ID=$CF_ORG_ID
export CLOUDFLEET_ACCESS_TOKEN_ID=$CF_TOKEN_ID
export CLOUDFLEET_ACCESS_TOKEN_SECRET=$CF_TOKEN_SECRET
cloudfleet clusters kubeconfig $CF_CLUSTER_ID > kubeconfig.yaml
kubectl --kubeconfig=kubeconfig.yaml set image deployment/myapp \
myapp=$CF_ORG_ID.$CFCR_REGION.registry.cloudfleet.dev/myapp:$CIRCLE_SHA1
workflows:
build-deploy:
jobs:
- build-and-push
- deploy:
requires:
- build-and-push
filters:
branches:
only: main
Configure environment variables
In CircleCI project settings:
- Navigate to Project Settings > Environment Variables
- Add:
CF_ORG_IDCFCR_REGIONCF_TOKEN_IDCF_TOKEN_SECRETCF_CLUSTER_ID
Azure DevOps
Azure Pipelines integrates with Azure DevOps repositories.
Pipeline configuration
Create azure-pipelines.yml:
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
registry: $(CF_ORG_ID).$(CFCR_REGION).registry.cloudfleet.dev
imageName: myapp
stages:
- stage: Build
jobs:
- job: BuildAndPush
steps:
- task: Docker@2
displayName: Login to registry
inputs:
command: login
containerRegistry: cloudfleet-registry
- task: Docker@2
displayName: Build and push
inputs:
command: buildAndPush
repository: $(imageName)
dockerfile: '$(Build.SourcesDirectory)/Dockerfile'
containerRegistry: cloudfleet-registry
tags: |
$(Build.BuildId)
$(Build.SourceVersion)
latest
- stage: Deploy
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- job: DeployToCFKE
steps:
- script: |
curl -fsSL https://downloads.cloudfleet.ai/cli/latest/cloudfleet-linux-amd64.zip -o cloudfleet.zip
unzip cloudfleet.zip
chmod +x cloudfleet
export CLOUDFLEET_ORGANIZATION_ID=$(CF_ORG_ID)
export CLOUDFLEET_ACCESS_TOKEN_ID=$(CF_TOKEN_ID)
export CLOUDFLEET_ACCESS_TOKEN_SECRET=$(CF_TOKEN_SECRET)
./cloudfleet clusters kubeconfig $(CF_CLUSTER_ID) > kubeconfig.yaml
kubectl --kubeconfig=kubeconfig.yaml set image deployment/myapp myapp=$(registry)/$(imageName):$(Build.BuildId)
displayName: Deploy to CFKE
Configure service connection
- Navigate to Project Settings > Service connections
- Create a new Docker Registry service connection:
- Registry type: Others
- Docker Registry:
https://YOUR_ORG_ID.REGION.registry.cloudfleet.dev(replace REGION witheurope,northamerica, orapac) - Docker ID: Your API token ID
- Docker Password: Your API token secret
- Service connection name:
cloudfleet-registry
- Add pipeline variables for
CF_ORG_IDandCFCR_REGION
Best practices
Tagging strategy
Implement consistent image tagging across pipelines:
# Semantic version tags for releases
v1.0.0, v1.0, v1
# Branch-based tags for development
main, develop, feature-xyz
# Commit SHA for traceability
sha-a1b2c3d4
# Build number for CI/CD systems
build-123
Caching
Enable Docker layer caching to speed up builds:
# GitHub Actions
- uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
# GitLab CI
build:
variables:
DOCKER_BUILDKIT: 1
script:
- docker build --cache-from $REGISTRY/$IMAGE_NAME:latest -t $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA .
Security scanning
Integrate vulnerability scanning before pushing images:
# GitHub Actions with Trivy
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
Token rotation
Periodically rotate CI/CD tokens:
- Create a new token with the same role
- Update CI/CD secrets with the new credentials
- Verify pipelines work with the new token
- Delete the old token
Consider automating this process using infrastructure as code.
Troubleshooting
Authentication failures
Error: unauthorized: authentication required
Verify:
- Token ID and secret are correct
- Token has Administrator role (for pushing)
- Registry URL matches your organization
Image push denied
Error: denied: requested access to the resource is denied
The token may have User role (pull-only). Create a new token with Administrator role.
Rate limiting
If you experience rate limiting during high-volume builds:
- Implement image layer caching
- Use a single base image across builds
- Consider parallel job limits
Next steps
- Configure access control for your CI/CD tokens
- Learn about managing artifacts for advanced operations
- Learn about Helm chart storage for GitOps workflows
← Access control