| 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 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 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.
The bundled reference at 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 repository contains the canonical AppKit deployment patterns. When in doubt, consult these references:
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:
- Download source — workspace files are extracted into
/home/app/
npm install — installs dependencies from package.json / package-lock.json. Runs in production mode (NODE_ENV=production), so devDependencies are skipped.
npm run build (if the build script exists) — compiles the project. prebuild hooks fire automatically before this step.
- 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 and post-deployment behavior.
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.
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:
DROP SCHEMA IF EXISTS <schema_name> CASCADE;
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:
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:
command:
- npm
- run
- start
Check that required environment bindings are present. At minimum, apps using the analytics plugin need:
grep "DATABRICKS_WAREHOUSE_ID" $APP_NAME/app.yaml
You should see valueFrom: sql-warehouse. If missing, add it:
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:
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:
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.
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.