CI/CD integration
This guide covers how to authenticate flyte deploy from a CI/CD pipeline (GitHub Actions, Jenkins, GitLab CI, etc.) against a self-hosted Union.ai deployment.
In serverless and BYOC deployments, flyte create api-key mints an API key automatically. Self-hosted deployments don’t have access to the identity service that backs this command. Instead, you create a dedicated OAuth application in your identity provider and encode its credentials as an API key manually.
For flyte deploy usage, flags, and workflow examples, see the
CI/CD deployments guide.
Prerequisites
- Authentication is configured and working (Apps 1-5)
- The
flyteCLI is installed (pip install flyteoruv pip install flyte) - You have admin access to your identity provider to create a new OAuth application
Step 1: Create a CI/CD OAuth application
Create a new confidential (service) application in your identity provider. This is the same type of application as the service-to-service app (App 3) documented in the authentication guide, but dedicated to CI/CD so you can manage its lifecycle and permissions independently.
- In the Okta Admin Console, go to Applications > Create App Integration
- Select API Services (machine-to-machine)
- Name it descriptively (e.g.,
union-cicdorunion-jenkins) - After creation, note the Client ID and Client Secret
- Go to your custom authorization server (Security > API > Authorization Servers)
- Under Access Policies, ensure the CI/CD app is allowed the
client_credentialsgrant with theallscope
If you want per-team or per-project CI/CD keys, create separate OAuth apps for each and assign different access policies.
- In the Azure portal, go to Microsoft Entra ID > App registrations > New registration
- Name it descriptively (e.g.,
union-cicd) - Set Supported account types to Single tenant
- No redirect URI is needed — this app uses client credentials only
- After creation, go to Certificates & secrets > New client secret and save the secret value
- Go to the Union API app registration (the one with “Expose an API” configured — typically App 1):
- Under Expose an API > Authorized client applications, add the CI/CD app’s Client ID
- Under App roles, ensure an
allrole exists
- Back on the CI/CD app registration:
- Go to API permissions > Add a permission > My APIs
- Select the Union API app and grant the
allApplication permission
- Grant admin consent: Go to Enterprise Applications > CI/CD app > Permissions > Grant admin consent for <tenant>
Without admin consent, client_credentials token requests will fail with an AADSTS error. This is the most common setup issue.
- Create a new confidential client in your identity provider
- Enable the
client_credentialsgrant type - Assign the appropriate scope (typically
allor the scope configured on your authorization server) - Note the Client ID and Client Secret
If your provider requires explicit audience configuration, set the audience to match the allowedAudience configured in your control plane Helm values.
Step 2: Build the API key
Encode the credentials as a base64 string in the format <domain>:<client-id>:<client-secret>: — note the trailing colon:
echo -n "<your-domain>:<client-id>:<client-secret>:" | base64For example:
echo -n "union.example.com:abc123:secret456:" | base64
# dW5pb24uZXhhbXBsZS5jb206YWJjMTIzOnNlY3JldDQ1Njo=The four fields are:
- Domain — your control plane ingress domain (without
https://) - Client ID — from the OAuth app you just created
- Client secret — from the OAuth app you just created
- Organization — leave empty for self-hosted (the trailing colon is still required)
Step 3: Store in your CI secret manager
Add the base64 string to your CI system’s secret store and expose it as the FLYTE_API_KEY environment variable:
- Go to Settings > Secrets and variables > Actions > New repository secret
- Name:
FLYTE_API_KEY - Value: the base64 string from Step 2
In your workflow:
- name: Deploy workflows
env:
FLYTE_API_KEY: ${{ secrets.FLYTE_API_KEY }}
run: flyte deploy ...- Go to Manage Jenkins > Credentials > Add Credentials
- Kind: Secret text
- Secret: the base64 string from Step 2
- ID:
flyte-api-key
In your Jenkinsfile:
environment {
FLYTE_API_KEY = credentials('flyte-api-key')
}
stages {
stage('Deploy') {
steps {
sh 'flyte deploy ...'
}
}
}- Go to Settings > CI/CD > Variables > Add variable
- Key:
FLYTE_API_KEY - Value: the base64 string from Step 2
- Check Mask variable and Protect variable
In your .gitlab-ci.yml:
deploy:
script:
- flyte deploy ...The FLYTE_API_KEY variable is automatically available to all jobs.
Step 4: Configure flyte deploy
Create a config.yaml in your repository pointing at your self-hosted deployment:
admin:
endpoint: dns:///<your-domain>
insecure: false # Set to true if using self-signed certificates
image:
builder: remote # Or "local" if you pre-build images
task:
project: <your-project>
domain: <your-domain-name>When FLYTE_API_KEY is set, the CLI uses it for authentication automatically — it overrides any other auth mode configured in config.yaml (including ExternalCommand-based SSO flows). No config changes are needed to switch between interactive and CI authentication.
Step 5: Test
Verify the credentials work before wiring them into your pipeline:
# 1. Test token acquisition (replace with your IdP's token endpoint)
curl -s -X POST "<token-endpoint>" \
-d "grant_type=client_credentials" \
-d "client_id=<client-id>" \
-d "client_secret=<client-secret>" \
-d "scope=<scope>" | jq .access_tokencurl -s -X POST "https://<okta-domain>/oauth2/<auth-server-id>/v1/token" \
-d "grant_type=client_credentials" \
-d "client_id=<client-id>" \
-d "client_secret=<client-secret>" \
-d "scope=all" | jq .access_tokencurl -s -X POST "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-d "grant_type=client_credentials" \
-d "client_id=<client-id>" \
-d "client_secret=<client-secret>" \
-d "scope=api://<app-name>/.default" | jq .access_tokencurl -s -X POST "<issuer-url>/token" \
-d "grant_type=client_credentials" \
-d "client_id=<client-id>" \
-d "client_secret=<client-secret>" \
-d "scope=all" | jq .access_tokenIf you receive a valid JWT, test the full flow:
export FLYTE_API_KEY="<base64-string>"
flyte deploy --config config.yaml --copy-style none --version test-$(date +%s) \
--project <project> --domain <domain> path/to/tasks.pyPermissions and RBAC
The CI/CD app’s access depends on your authorization configuration:
- Noop mode: The app has full access to all projects and domains
- External authorization: Configure your external authz service to grant the CI/CD app’s identity appropriate permissions
- Union RBAC: Create a role scoped to the target project/domain and bind it to the CI/CD app’s identity
For teams sharing a cluster, create separate OAuth apps per team or per repository so that one team’s CI key cannot deploy to another team’s project. See the CI/CD deployments guide for more on permission scoping.
Key rotation
Rotate CI/CD credentials on a regular schedule (90 days recommended):
- Create a new client secret in your identity provider (don’t delete the old one yet)
- Re-encode with the new secret:
echo -n "<domain>:<client-id>:<new-secret>:" | base64 - Update the
FLYTE_API_KEYsecret in your CI system - Verify a deploy succeeds with the new key
- Delete the old client secret from your identity provider