Skip to main content
Version: Next

Build from a Private Repository and Deploy via a Private Registry

Overview​

This tutorial covers a fully private end-to-end pipeline: your source code lives in a private Git repository, the container image is built inside OpenChoreo's Workflow Plane, pushed to a private container registry, and then pulled and deployed on the Data Plane.

Each stage of the pipeline requires its own credentials:

StageCredential Required
Clone private Git repoGit personal access token (PAT)
Push image to private registryRegistry push credentials
Pull image from private registryRegistry pull credentials (image pull secret)

This tutorial uses GitHub as the Git host and Docker Hub as the container registry.

Prerequisites​

Before you begin, ensure you have:

  • OpenChoreo installed in your Kubernetes cluster
  • kubectl configured to access your cluster
  • A private GitHub repository containing your application source code.
  • A private Docker Hub repository to push and pull your built image
  • A Docker Hub personal access token (or password) for both push (Workflow Plane) and pull (Data Plane)
  • A GitHub personal access token with at least repo read access
  • External secret store accessible from the Workflow Plane and Data Plane (each plane may have its own store, or they may share one β€” e.g., OpenBao for local dev, or Vault/AWS Secrets Manager for production)

Step 1 β€” Store Git Credentials​

Store your Git credentials in your secret backend so the Workflow Plane can clone the private repository.

note

This example uses OpenBao, the default secret store in a local OpenChoreo development setup. For production, see Secret Management.

Store a username and personal access token (PAT) under the key secret/git/github-token:

kubectl exec -n openbao openbao-0 -- sh -c '
export BAO_ADDR=http://127.0.0.1:8200 BAO_TOKEN=root
bao kv put secret/git/github-token \
username="your-github-username" \
token="your-personal-access-token"
'

Replace your-github-username and your-personal-access-token with your actual Git credentials. The token must have at least read access to the repository.


Step 2 β€” Create a SecretReference for the Git Repository​

Create a SecretReference custom resource that syncs your Git credentials from the secret store into the Workflow Plane namespace.

kubectl apply -f - <<EOF
apiVersion: openchoreo.dev/v1alpha1
kind: SecretReference
metadata:
name: github-credentials
namespace: default
spec:
template:
type: kubernetes.io/basic-auth
data:
- secretKey: username
remoteRef:
key: secret/git/github-token
property: username
- secretKey: password
remoteRef:
key: secret/git/github-token
property: token
refreshInterval: 1h
EOF

Step 3 β€” Store Registry Push Credentials​

Store credentials that allow the Workflow Plane to push the built image to your private registry.

Generate base64-encoded credentials​

echo -n 'your-registry-username:your-registry-password' | base64

Example output: ZGVtby11c2VyOmRlbW8tcGFzcw==

Store in OpenBao​

kubectl exec -n openbao openbao-0 -- sh -c '
export BAO_ADDR=http://127.0.0.1:8200 BAO_TOKEN=root
bao kv put secret/registry-push-secret \
value="{\"auths\":{\"<REGISTRY-HOST>\":{\"auth\":\"<BASE64-TOKEN>\"}}}"
'

Replace:

  • <REGISTRY-HOST> β€” your registry host (e.g., https://index.docker.io/v1/ for Docker Hub, ghcr.io for GHCR)
  • <BASE64-TOKEN> β€” the base64 string from the previous step

Example for Docker Hub:

kubectl exec -n openbao openbao-0 -- sh -c '
export BAO_ADDR=http://127.0.0.1:8200 BAO_TOKEN=root
bao kv put secret/registry-push-secret \
value="{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"ZGVtby11c2VyOmRlbW8tcGFzcw==\"}}}"
'


Step 4 β€” Configure the Publish Step to Use Your Private Registry​

Platform Engineer Setup

This step is a one-time platform-level configuration performed by a cluster admin. You do not need to repeat this for every application.

Replace the default publish-image ClusterWorkflowTemplate with one pointing to your private registry endpoint. Before applying, determine your REGISTRY_ENDPOINT value:

RegistryREGISTRY_ENDPOINT
Docker Hubdocker.io/your-username
Amazon ECR123456789.dkr.ecr.us-east-1.amazonaws.com/openchoreo-builds
Google Artifact Registryus-central1-docker.pkg.dev/my-project/openchoreo-builds
Azure Container Registrymyregistry.azurecr.io
GitHub Container Registryghcr.io/my-org/openchoreo

Then apply, replacing your-registry-host/your-repo-path with your value from the table above:

kubectl apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: ClusterWorkflowTemplate
metadata:
name: publish-image
spec:
templates:
- name: publish-image
inputs:
parameters:
- name: git-revision
outputs:
parameters:
- name: image
valueFrom:
path: /tmp/image.txt
volumes:
- name: registry-push-secret
secret:
optional: true
secretName: '{{workflow.parameters.registry-push-secret}}'
container:
image: ghcr.io/openchoreo/podman-runner:v1.0
command: [sh, -c]
args:
- |-
set -e

GIT_REVISION={{inputs.parameters.git-revision}}
IMAGE_NAME={{workflow.parameters.image-name}}
IMAGE_TAG={{workflow.parameters.image-tag}}
SRC_IMAGE="\${IMAGE_NAME}:\${IMAGE_TAG}-\${GIT_REVISION}"

# Replace with your registry endpoint
REGISTRY_ENDPOINT="your-registry-host/your-repo-path"
AUTH_FILE="/etc/secrets/registry-push-secret/.dockerconfigjson"

mkdir -p /etc/containers
cat <<CONF > /etc/containers/storage.conf
[storage]
driver = "overlay"
runroot = "/run/containers/storage"
graphroot = "/var/lib/containers/storage"
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
CONF

podman load -i /mnt/vol/app-image.tar
podman tag \$SRC_IMAGE \$REGISTRY_ENDPOINT/\$SRC_IMAGE

if [ -f "\$AUTH_FILE" ]; then
podman push --tls-verify=true --authfile "\$AUTH_FILE" \$REGISTRY_ENDPOINT/\$SRC_IMAGE
else
podman push --tls-verify=true \$REGISTRY_ENDPOINT/\$SRC_IMAGE
fi

echo -n "\$REGISTRY_ENDPOINT/\$SRC_IMAGE" > /tmp/image.txt
securityContext:
privileged: true
volumeMounts:
- mountPath: /mnt/vol
name: workspace
- mountPath: /etc/secrets/registry-push-secret
name: registry-push-secret
readOnly: true
EOF

Step 5 β€” Store Registry Pull Credentials​

The Data Plane also needs credentials to pull the image from your private registry. Store them in OpenBao with the key registry-credentials:

kubectl exec -n openbao openbao-0 -- sh -c '
export BAO_ADDR=http://127.0.0.1:8200 BAO_TOKEN=root
bao kv put secret/registry-credentials \
value="{\"auths\":{\"<REGISTRY-HOST>\":{\"auth\":\"<BASE64-TOKEN>\"}}}"
'
note

If your push and pull credentials are the same (e.g., same Docker Hub account), you can reuse the same base64 token. The separate keys (registry-push-secret and registry-credentials) exist because push credentials are used by the Workflow Plane and pull credentials are used by the Data Plane, which may be separate clusters.


Step 6 β€” Configure Your ClusterComponentType to Pull from the Private Registry​

Platform Engineer Setup

This is another one-time platform-level change performed by a cluster admin. Once configured, all components using this ClusterComponentType will automatically get the image pull secret injected β€” no per-component changes are needed.

Edit the deployment/service ClusterComponentType (or your custom type) to add two pieces:

1. An ExternalSecret that syncs pull credentials into the workload namespace:

- id: registry-pull-secret
template:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: registry-pull-secret
namespace: ${metadata.namespace}
spec:
refreshInterval: 15s
secretStoreRef:
name: ${dataplane.secretStore}
kind: ClusterSecretStore
target:
name: registry-pull-secret
creationPolicy: Owner
template:
type: kubernetes.io/dockerconfigjson
data:
- secretKey: .dockerconfigjson
remoteRef:
key: registry-credentials
property: value

2. imagePullSecrets in the Deployment template so pods can authenticate when pulling:

- id: deployment
template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${metadata.name}
namespace: ${metadata.namespace}
spec:
template:
spec:
imagePullSecrets:
- name: registry-pull-secret
containers:
- name: main
image: ${workload.container.image}
# ... rest of container config

For the full ClusterComponentType configuration, see Deploy from a Private Registry.


Step 7 β€” Create the Component​

Create the Component that points to your private Git repository:

kubectl apply -f - <<EOF
apiVersion: openchoreo.dev/v1alpha1
kind: Component
metadata:
name: private-go-service
namespace: default
spec:
autoDeploy: true
componentType:
kind: ClusterComponentType
name: deployment/service
owner:
projectName: default
workflow:
kind: ClusterWorkflow
name: dockerfile-builder
parameters:
repository:
url: https://github.com/openchoreo/sample-workloads.git # Replace with your private repo URL
secretRef: github-credentials
revision:
branch: main # Replace with your branch
appPath: /service-go-greeter # Replace with the path to your app within the repo
docker:
context: /service-go-greeter # Replace with your Docker build context
filePath: /service-go-greeter/Dockerfile # Replace with your Dockerfile path
EOF

Replace url with your private repository's clone URL, branch with your target branch, appPath with the path to your application within the repository, and context and filePath with the Docker build context and Dockerfile path relative to the repository root.


Step 8 β€” Trigger a Build​

To build the image, create a WorkflowRun that references the Component:

kubectl apply -f - <<EOF
apiVersion: openchoreo.dev/v1alpha1
kind: WorkflowRun
metadata:
name: private-go-service-run-01
namespace: default
labels:
openchoreo.dev/component: private-go-service
openchoreo.dev/project: default
spec:
workflow:
kind: ClusterWorkflow
name: dockerfile-builder
parameters:
repository:
url: https://github.com/openchoreo/sample-workloads.git # Replace with your private repo URL
secretRef: github-credentials
revision:
branch: main # Replace with your branch
appPath: /service-go-greeter # Replace with the path to your app within the repo
docker:
context: /service-go-greeter # Replace with your Docker build context
filePath: /service-go-greeter/Dockerfile # Replace with your Dockerfile path
EOF

Replace url, branch, appPath, context, and filePath with the values matching your private repository.

Monitor the Build​

# Check WorkflowRun conditions
kubectl get workflowrun private-go-service-run-01 -n default \
-o jsonpath='{.status.conditions}' | jq .

# Watch workflow pods in the Workflow Plane namespace
kubectl get pods -n workflows-default | grep private-go-service

# Stream logs from the workflow pod (replace <pod-name> with the actual pod name)
kubectl logs -n workflows-default <pod-name> -f

The WorkflowRun goes through these stages:

ConditionDescription
WorkflowRunningArgo Workflow is executing (clone β†’ build β†’ push)
WorkflowSucceededImage pushed to registry; Workload CR created
WorkflowFailedCheck step logs in the Workflow Plane

Wait for both WorkflowCompleted: True and WorkflowSucceeded: True before proceeding.


Step 9 β€” Verify the Deployment​

Once the WorkflowRun succeeds, OpenChoreo automatically creates a Workload CR with the pushed image reference and deploys it on the Data Plane.

Check the component and workload:

kubectl get component private-go-service
kubectl get workload -A | grep private-go-service

Check that pods are running:

kubectl get pods -A | grep private-go-service

If pods are in ImagePullBackOff, verify that the pull secret is synced correctly:

kubectl get externalsecret -A | grep registry-pull-secret
kubectl describe externalsecret registry-pull-secret -n <workload-namespace>

Test Your Application​

Once the deployment is ready, test the service:

curl http://development-default.openchoreoapis.localhost:19080/private-go-service-greeter-api/greeter/greet

# Expected response: Hello, Stranger!

Troubleshooting​

SymptomLikely CauseFix
WorkflowRun stuck in Running with clone step failingInvalid Git credentials or secret not foundCheck SecretReference status and verify credentials in secret store
WorkflowRun fails at publish-image step with "unauthorized"Push credentials missing or wrong registry hostRe-verify registry-push-secret in OpenBao and the REGISTRY_ENDPOINT in the CWT
Pod in ImagePullBackOff after successful buildPull secret not configured or syncedVerify ExternalSecret status and imagePullSecrets in ClusterComponentType
"x509: certificate signed by unknown authority"Self-signed registry TLS certSet --tls-verify=false in the CWT or configure the CA cert

Summary​

You have set up a fully private end-to-end pipeline in OpenChoreo:

  • Source code is cloned from a private Git repository using a SecretReference
  • The image is built in the Workflow Plane and pushed to a private registry using a push secret
  • The Data Plane pulls the image using an ExternalSecret-backed image pull secret and deploys it

Next Steps​