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/docker@2.4
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