CI/CD for Databricks Apps with GitHub Actions

This page explains how to automate the deployment of a Databricks app from GitHub using GitHub Actions and Databricks Asset Bundles (DABs). It covers workload identity federation, the workflow YAML, and a health check that confirms the app is serving the latest code after each deployment.

For generic GitHub Actions guidance for Azure Databricks jobs and pipelines, see GitHub Actions. For the workload identity federation setup, see Enable workload identity federation for GitHub Actions.

Requirements

Step 1. Configure workload identity federation

Workload identity federation lets the GitHub Actions runner authenticate with Azure Databricks using a short-lived OIDC token instead of storing credentials in your repository.

Follow the steps in Enable workload identity federation for GitHub Actions to create a GitHub Actions federation policy on your service principal. Note the service principal application ID (UUID) and your workspace URL. You need both as variables in the workflow.

Then grant the service principal CAN MANAGE permission on the app, or workspace permission to create apps if the app doesn't exist yet. See Configure permissions for a Databricks app.

Step 2. Configure the GitHub repository

In your GitHub repository, create a deployment environment to store the workspace connection variables. Using an environment also lets you require manual approval before deployments run.

  1. In Settings > Environments, create an environment named prod (or any name your workflow references).
  2. For Environment variables, add the following:
Variable Value
DATABRICKS_HOST Your workspace URL, for example https://my-workspace.cloud.databricks.com
DATABRICKS_CLIENT_ID The service principal application ID from Step 1

Neither value is a credential. The federation policy on the service principal controls who can authenticate as it, so the client ID alone doesn't grant access. You don't need a client secret.

Step 3. Configure your bundle for production deployments

In databricks.yml, declare an explicit workspace host and root_path on your prod target. This ensures the bundle deploys to the same location on every run. Production-mode validation requires both fields unless run_as is set to a service principal. See Declarative Automation Bundles deployment modes.

targets:
  prod:
    mode: production
    workspace:
      host: https://my-workspace.cloud.databricks.com
      root_path: /Workspace/Users/<service-principal-or-owner>/.bundle/${bundle.name}/${bundle.target}
    resources:
      apps:
        my_app:
          name: my-app
          source_code_path: ./app

Replace <service-principal-or-owner> with the workspace user that owns the bundle artifacts, typically the service principal application ID.

Replace ./app with the path to your app's source code relative to databricks.yml. The source_code_path field is required when app code lives in the same repository as the bundle. If your app code lives in a separate repository, use git_source instead. See app.

Step 4. Add the deploy workflow

Add .github/workflows/deploy.yml to your repository:

name: Deploy to Databricks Apps

on:
  workflow_dispatch:
  # Uncomment to deploy on every push to main once the workflow is validated.
  # push:
  #   branches: [main]

permissions:
  id-token: write # required for OIDC federation
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: prod
    env:
      DATABRICKS_AUTH_TYPE: github-oidc
      DATABRICKS_HOST: ${{ vars.DATABRICKS_HOST }}
      DATABRICKS_CLIENT_ID: ${{ vars.DATABRICKS_CLIENT_ID }}
    steps:
      - uses: actions/checkout@v4

      - name: Install Databricks CLI
        uses: databricks/setup-cli@main

      - name: Validate bundle
        run: databricks bundle validate --target prod

      - name: Deploy bundle
        run: databricks bundle deploy --target prod

      - name: Start or restart app
        run: databricks bundle run my_app --target prod

Replace my_app in the last step with the resource key your databricks.yml uses under resources.apps.

The runner needs the id-token: write permission to request an OIDC token. The databricks/setup-cli action reads DATABRICKS_AUTH_TYPE=github-oidc and handles authentication automatically.

Warning

databricks bundle deploy uploads source code and updates resources, but it doesn't restart the app process. If you skip the final databricks bundle run step, the deploy passes in CI while the app keeps serving the previous code. Always run the bundle resource after deploying.

Step 5. Wait for the app to be healthy

Databricks recommends adding a status-polling step after deployment. databricks bundle run exits as soon as it signals the app to start, but the app might not be running yet. It can still fail during startup due to issues like missing dependencies, a missing environment variable, or a port conflict. Adding a polling step ensures a failed startup also fails the workflow:

- name: Wait for app to be running
  env:
    APP_NAME: my-app
  run: |
    for i in $(seq 1 20); do
      STATE=$(databricks apps get "$APP_NAME" --output json | jq -r '.app_status.state')
      echo "Attempt $i/20: state=$STATE"
      if [ "$STATE" = "RUNNING" ]; then
        exit 0
      fi
      sleep 15
    done
    echo "App did not reach RUNNING state within 5 minutes" >&2
    exit 1

Set APP_NAME to the value your databricks.yml declares under resources.apps.<key>.name, not the bundle resource key.

Handling an existing app

App names are unique across the workspace. The bundle deploy step fails with An app with the same name already exists when another bundle (or a manually-created app) already owns an app of that name. Bind your bundle to the existing app instead of recreating it.

Run this once locally to attach the bundle to the existing app:

databricks bundle deployment bind my_app <existing-app-name> --target prod --auto-approve

Then re-run the workflow. Subsequent deployments reuse the binding.

If the existing app has server-side configuration (such as budget_policy_id) that is not in your databricks.yml, copy it into the bundle file before re-deploying. Mismatches appear as a Terraform "inconsistent result" error during the bundle deploy step.

Choosing a trigger

Start with workflow_dispatch so the first deployment is manual. Once a few runs succeed, add push: branches: [main] to deploy on every merge.

For an additional safety gate, configure the prod environment with required reviewers in Settings > Environments > prod > Deployment protection rules. Each workflow run waits for an approver before the deploy job starts.

Next steps