- name
- 03-appkit-deploy
- description
- Deploy a Databricks AppKit application to Databricks Apps. Covers config validation, build verification, deployment, UI verification, error diagnosis with fix loop, and workspace app limit handling. Use when asked to deploy an AppKit app, push to production, ship the app, or troubleshoot a failed deploy. Triggers on "deploy app", "push to databricks", "ship app", "deploy appkit", "databricks apps deploy", "fix deploy error", "app won't start".
- license
- Apache-2.0
- compatibility
- Requires a built AppKit project with Node.js v22+ and Databricks CLI >= 0.295.0
- allowed-tools
- Bash(databricks:*) Bash(npm:*) Bash(curl:*) Bash(node:*) Read
- clients
- ["ide_cli","genie_code"]
- bundle_resource
- apps
- deploy_verb
- apps_deploy
- deploy_note
- IDE: `databricks apps deploy --profile $PROFILE` (local Node build + bundle sync + start). Genie Code: run `databricks apps deploy` via `runDatabricksCli` where the project page allows it (omit `--profile` — pre-authenticated), else fall back to the SDK `w.apps.deploy(<name>, AppDeployment(source_code_path=…, mode=AppDeploymentMode.SNAPSHOT))` via `executeCode`. The frontend build runs **server-side** in the container, so no local `npm install` / `npm run build` is required on Genie Code (Gap 4 resolved).
- coverage
- full
- metadata
- {"author":"prashanth subrahmanyam","version":"1.2.0","domain":"apps","role":"deploy","standalone":true,"last_verified":"2026-06-02","volatility":"medium","upstream_sources":[]}
# Deploy Databricks AppKit Applications
Deploy an AppKit project to Databricks Apps, verify it runs, and fix common errors.
## When to Use
- Deploying an AppKit app to Databricks Apps (first deploy or redeployment)
- Verifying a deployed app loads correctly
- Diagnosing and fixing deploy failures
- Freeing workspace app slots when the limit is reached
**Not for scaffolding.** Use `01-appkit-scaffold` to create a new project.
**Not for building features.** Use `02-appkit-build` to implement UI and backend code.
## Prerequisites
Before deploying, ensure:
- The app builds locally (`npm run build` succeeds)
- `$APP_NAME` and `$PROFILE` are set by the calling prompt. **If a `.vibecoding-state.md` exists from a prior phase**, use the `APP_NAME`, `PROFILE`, and workspace URL values from it directly — do not re-derive them with `databricks current-user me` or `databricks auth profiles`.
- The app directory contains `app.yaml` and `databricks.yml`
- If deploying to a **different workspace** than where the app was scaffolded: update the `host` in `databricks.yml`, update `sql_warehouse_id` for the new workspace, and remove stale bundle state with `rm -rf $APP_NAME/.databricks`
- If no CLI profile exists for the target workspace, create one per **[PRE-REQUISITES §11](../../../PRE-REQUISITES.md)** before deploying (the IDE/CLI auth path; it uses `auth login`, not the interactive `configure` flow that fails in automated/agent contexts). **IDE/CLI only** — on Genie Code the runtime is already authenticated to its host workspace; skip profile creation and drop `--profile` from every command (see the routing note below).
- All commands in this skill assume the working directory is `apps_lakebase/`. Paths like `$APP_NAME/app.yaml` are relative to `apps_lakebase/`, not the repo root. On Genie Code, this is `<artifact_root>/apps_lakebase/` inside your git-cloned workshop project (NOT the read-only `.assistant/skills/` copy) — `cd` there first; never operate from `/tmp`.
- **Do NOT run `rm -f package-lock.json && npm install` locally before deploying.** The platform's `npm install` depends on lockfile stability; regenerating the lockfile locally causes `ENOTEMPTY` / `Exit handler never called` failures during platform install. See [references/lockfile-and-recreation.md](references/lockfile-and-recreation.md) for the full rule, scenario table, and recovery ladder — plus the Lakebase ownership consequences of app recreation.
### Working in Genie Code (deploy routing)
This skill is written for the **IDE/CLI path** — the commands below are correct as-is when you have a local terminal, Node.js, and a CLI profile. If you are running in **Genie Code** (no local toolchain, pre-authenticated to one workspace), apply these four substitutions to every step and you do not need to re-read this note per command:
| IDE/CLI (as written) | Genie Code substitution |
|----------------------|--------------------------|
| `databricks <cmd> … --profile $PROFILE` | run `databricks <cmd> …` via `runDatabricksCli`, **omit `--profile`** (ambient auth) |
| `databricks apps deploy …` (when the project page blocks it) | SDK fallback via `executeCode`: `w.apps.deploy(<name>, AppDeployment(source_code_path=<workspace path>, mode=AppDeploymentMode.SNAPSHOT))` |
| `npm run build` / `npm run dev` (local) | **skip** — the frontend build runs server-side in the container during deploy; the build error is **not readable from compute** (`databricks apps logs <name>` → OAuth error) — read it at `<app-url>/logz` in a browser instead |
| `databricks bundle deploy` (targetless) | add `--target dev` (a targetless bundle deploy is guardrail-blocked on Genie Code) |
Everything else (config validation, log streaming, the fix loop, error table) is identical across clients. Inline `> **Client note — Genie Code:**` callouts below flag the few steps where the behavior — not just the syntax — differs. See `skills/genie-code-environment` for the full behavioral manifest.
---
## Before You Begin
**The upstream Databricks agent-skills repo and AppKit docs are the source of truth for deploy commands, platform rules, and options.**
- **Agent Skills (deploy patterns, platform rules):** https://github.com/databricks/databricks-agent-skills/blob/main/skills/databricks-apps/SKILL.md
- **Platform guide (SP permissions, runtime constraints, errors):** https://github.com/databricks/databricks-agent-skills/blob/main/skills/databricks-apps/references/platform-guide.md
- **App management:** https://databricks.github.io/appkit/docs/app-management
- **Configuration:** https://databricks.github.io/appkit/docs/configuration
- **AppKit docs (in-terminal):** `npx @databricks/appkit docs "app-management"`
- **CLI help:** `databricks apps deploy --help`
The bundled reference at [references/app-management.md](references/app-management.md) covers commonly used commands as a fallback when live docs cannot be reached.
> **Do NOT improvise workarounds.** If a deployment fails, check the app logs and match
> the error against the Common Errors table below. Do NOT add npm lifecycle hooks
> (`preinstall`, `postinstall`), platform-detection conditionals, or workarounds that skip
> the platform's build pipeline. These consistently cause cascading failures that are harder
> to diagnose than the original error. When in doubt, consult the authoritative sources above.
---
## Authoritative References
The [databricks-agent-skills](https://github.com/databricks/databricks-agent-skills) repository contains the canonical AppKit deployment patterns. When in doubt, consult these references:
| Topic | Reference |
|-------|-----------|
| AppKit scaffolding, validation, workflow | [databricks-apps SKILL.md](https://github.com/databricks/databricks-agent-skills/blob/main/skills/databricks-apps/SKILL.md) |
| Platform rules (SP permissions, runtime limits, common errors) | [platform-guide.md](https://github.com/databricks/databricks-agent-skills/blob/main/skills/databricks-apps/references/platform-guide.md) |
| AppKit project structure and checklists | [appkit/overview.md](https://github.com/databricks/databricks-agent-skills/blob/main/skills/databricks-apps/references/appkit/overview.md) |
| Lakebase pool, CRUD, schema ownership | [appkit/lakebase.md](https://github.com/databricks/databricks-agent-skills/blob/main/skills/databricks-apps/references/appkit/lakebase.md) |
| Smoke tests and Playwright guidance | [testing.md](https://github.com/databricks/databricks-agent-skills/blob/main/skills/databricks-apps/references/testing.md) |
| Lakebase two-phase deploy (Phase 1/2 binding) | [plugin-lakebase.md](../04-appkit-plugin-add/references/plugin-lakebase.md) — **read before any Lakebase deploy** |
| AppKit docs (in-terminal) | `npx @databricks/appkit docs "app-management"` |
### Platform Constraints (from platform-guide.md)
These runtime constraints affect deployment and troubleshooting:
- **Startup timeout:** App must start within 10 minutes (including dependency installation)
- **HTTP proxy timeout:** 120 seconds per request (not configurable; use WebSockets for long operations)
- **Max apps per workspace:** 100
- **Max file size:** 10 MB per file in bundle
- **Filesystem:** Ephemeral — no persistent local storage; use UC Volumes or Lakebase
- **No shell in `command`:** `app.yaml` `command` does not run in a shell; env vars outside `app.yaml` are inaccessible
- **Graceful shutdown:** SIGTERM → 15 seconds → SIGKILL
- **Logging:** Only stdout/stderr captured; file-based logs are lost on container recycle
- **Destructive updates:** `bundle run` / `apps update` does full replacement and can wipe OBO scopes
### Platform Build Pipeline
When `databricks apps deploy` pushes code to the platform, the following sequence runs inside the container:
1. **Download source** — workspace files are extracted into `/home/app/`
2. **`npm install`** — installs dependencies from `package.json` / `package-lock.json`. Runs in **production mode** (`NODE_ENV=production`), so `devDependencies` are skipped.
3. **`npm run build`** (if the `build` script exists) — compiles the project. `prebuild` hooks fire automatically before this step.
4. **Run `command`** — executes the `command` from `app.yaml` (e.g., `npm run start`)
**Hard timeout:** The entire sequence (steps 1-4) must complete within **10 minutes**. If npm install or build exceeds this, the deploy fails with `App process did not start within 10 minutes`.
**Critical rules:**
- **NEVER** add `preinstall` or `postinstall` scripts that modify `node_modules`. These create infinite loops or corrupt the install.
- **NEVER** add platform-detection conditionals (e.g., `[ "$HOME" = '/home/app' ]`) to skip build steps.
- **NEVER** modify the scaffold's `package.json` dependency versions, aliases, or overrides. If `rolldown-vite`, `@playwright/test`, or other packages were included by `databricks apps init`, leave them as-is — the scaffold is tested to deploy on the platform.
- **DO** let scaffolded `prebuild` hooks run (`appkit sync`, `appkit generate-types`). Warnings about `@ast-grep/napi` are harmless and guarded by `2>/dev/null; true`.
- **DO** note that `databricks bundle deploy` (and `databricks apps deploy` which calls it internally) uses `.gitignore` patterns for file exclusion — NOT `.databricksignore`.
**Authoritative source:** [Databricks Apps deploy — deployment logic](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/deploy) and [post-deployment behavior](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/app-runtime).
> **Client note — Genie Code (verified):** because this build pipeline runs **server-side in the container**, a Genie Code user with **no local npm** can deploy by editing source directly in the workspace and triggering a deployment via the SDK — `w.apps.deploy(<name>, AppDeployment(source_code_path=<workspace path>, mode=AppDeploymentMode.SNAPSHOT))`. This was tested end-to-end: editing an un-built `client/src/App.tsx` and redeploying produced deploy status `Building app…` and the edited string appeared in the **server-built** JS bundle (`/assets/index-*.js`). No local `npm install`/`npm run build` and no pre-synced `dist/` are required on Genie Code.
### Package Lock Management
The platform's `npm install` depends on `package-lock.json` stability. Scenario table:
| Lockfile state | Platform behavior | Action |
|----------------|-------------------|--------|
| Present, matches platform cache | Fast install | Deploy |
| Regenerated locally with foreign registry URLs | Mixed URLs → `ENOTEMPTY` / `Exit handler never called` after ~3 min | Revert or delete lockfile, redeploy |
| Absent | Fresh resolve, slower but succeeds | Acceptable for first deploy |
| Refreshed via `npm install --package-lock-only` | Keeps lockfile coherent, no tarball install | Preferred when bumping dep versions |
> **NEVER run `rm -f package-lock.json && npm install` locally before deploying.** The regenerated lockfile picks up your local npm proxy URLs, breaking platform install. If you must refresh, use `--package-lock-only`, or delete the lockfile and let the platform resolve.
> **Client note — Genie Code:** the "Absent → acceptable for first deploy" row does **NOT** apply on the SDK SNAPSHOT path. With no `package-lock.json`, the deploy **hard-fails at the source-export phase in ~10s** (`RESOURCE_DOES_NOT_EXIST`), before `npm install` runs — so the lockfile is a **hard requirement**, not optional. Never delete it as a "reset"; change deps by editing `package.json` and keeping the lockfile consistent.
Full recovery ladder + prevention rules: [references/lockfile-and-recreation.md](references/lockfile-and-recreation.md).
### App Deletion Is Destructive (Lakebase Ownership)
Deleting and recreating a Databricks App assigns a **new Service Principal UUID**. Lakebase schemas owned by the old SP become inaccessible (`permission denied for schema`, `must be owner of table`) even though `psql` as admin still sees them.
Fix, as workspace admin via `databricks psql`:
```sql
DROP SCHEMA IF EXISTS <schema_name> CASCADE;
-- Then redeploy — the new SP's DDL will recreate and own the schema.
```
Prevention: avoid app deletion. If deploys are broken, use the lockfile recovery ladder first. App deletion is a last resort.
---
## Step 1: Validate Configuration
Verify `app.yaml` has a startup command:
```bash
grep -E "build/index\.mjs|npm.*start" $APP_NAME/app.yaml
```
Accepted patterns:
- `command: ['npm', 'run', 'start']` — AppKit default (scaffold output)
- `command: [node, build/index.mjs]` — legacy / alternative
If using `npm run start`, verify the `start` script in `package.json` points to the correct built output (e.g., `node dist/server.js`).
If no startup command is present, add the default:
```yaml
command:
- npm
- run
- start
```
Check that required environment bindings are present. At minimum, apps using the analytics plugin need:
```bash
grep "DATABRICKS_WAREHOUSE_ID" $APP_NAME/app.yaml
```
You should see `valueFrom: sql-warehouse`. If missing, add it:
```yaml
env:
- name: DATABRICKS_WAREHOUSE_ID
valueFrom: sql-warehouse
```
The calling prompt may require additional plugin-specific env vars (e.g., `LAKEBASE_ENDPOINT` for Lakebase). Validate those before proceeding.
Verify `databricks.yml` references the correct `$APP_NAME`:
```bash
grep "name:" $APP_NAME/databricks.yml | head -2
```
Note: The AppKit scaffold does not include a `name:` field in `app.yaml` — the app name is defined only in `databricks.yml` under `bundle.name` and `resources.apps`.
Run the AppKit validator to check `app.yaml` schema, resource bindings, and manifest validity:
```bash
cd $APP_NAME && databricks apps validate --profile $PROFILE
```
Fix any reported errors before proceeding.
> **Client note — Genie Code:** `databricks apps validate` is **hard-blocked** via `runDatabricksCli` (not allow-listed). Skip this gate on Genie Code. The platform runs the same validation server-side during the build pipeline, but those build logs are **not readable from compute** — `databricks apps logs <name>` returns an OAuth-token error. Read the authoritative schema/compile signal at **`<app-url>/logz` in a browser** (where you are already authenticated) instead.
**Cross-validate `valueFrom` references against `databricks.yml` resources.** Every `valueFrom:` in `app.yaml` must have a matching resource declaration in `databricks.yml`. If not, `databricks apps deploy` (which runs `bundle deploy` internally) will fail to resolve the resource and the env var will be empty at runtime.
```bash
for ref in $(grep 'valueFrom:' $APP_NAME/app.yaml | awk '{print $2}'); do
if ! grep -q "$ref" $APP_NAME/databricks.yml 2>/dev/null; then
echo "ERROR: app.yaml references valueFrom: $ref but no matching resource in databricks.yml"
echo " bundle deploy will strip manually-attached resources. Add the resource to databricks.yml."
fi
done
```
This catches the common failure where Lakebase `postgres` resources were attached via REST API or `databricks apps update` but not declared in `databricks.yml` — `bundle deploy` resets the resource list on every deploy, stripping anything not in the bundle config.
Voir sur GitHub