Final Stage - Production Deployment and Smoke Testing Your Project

Estimated read: 10 min

deploy-prod is waiting on a reviewer’s approval. This closing lecture covers what happens the moment that approval lands – a fast smoke test, not a rerun of Lecture 5’s full integration suite, followed by the first real production run and what actually needs watching afterward.

Smoke test, not integration test: a deliberate distinction

Lecture 6 already established why the full integration suite never runs against prod – test_dq_check_failure_skips_transformation’s deliberately-broken seeded batch would be actively harmful against real data. A smoke test asks a much narrower question: did the deploy itself succeed, not does every piece of business logic work correctly (already proven twice, in unit tests and in uat’s integration suite). “Does the system turn on” is the entire scope – named for the literal hardware-engineering practice of powering on a newly assembled circuit and checking for smoke before testing anything more specific.

The smoke test script

# scripts/smoke_test.py
import os
import sys
from databricks.sdk import WorkspaceClient

def main():
    client = WorkspaceClient(
        host=os.environ["DATABRICKS_HOST"], token=os.environ["DATABRICKS_TOKEN"]
    )
    checks = []

    # 1. Catalog, schema, and volumes exist
    try:
        client.schemas.get(full_name="prod.step_right")
        checks.append(("schema exists", True))
    except Exception as e:
        checks.append(("schema exists", False, str(e)))

    # 2. Both pipelines are deployed and in a healthy state
    for name in ["steprightproject-bronze-cdc", "steprightproject-silver-gold"]:
        pipelines = list(client.pipelines.list_pipelines(filter=f"name LIKE '{name}'"))
        checks.append((f"pipeline '{name}' exists", len(pipelines) == 1))

    # 3. The daily job exists and is scheduled
    jobs = list(client.jobs.list(name="StepRight Daily Pipeline"))
    checks.append(("daily job exists", len(jobs) == 1))
    if jobs:
        checks.append(("daily job has a schedule", jobs[0].settings.schedule is not None))

    # 4. The dashboard resource deployed successfully
    dashboards = list(client.lakeview.list())
    checks.append((
        "dq monitoring dashboard exists",
        any(d.display_name == "StepRight Data Quality Monitoring" for d in dashboards),
    ))

    failures = [c for c in checks if not c[1]]
    for check in checks:
        status = "PASS" if check[1] else "FAIL"
        print(f"[{status}] {check[0]}")

    if failures:
        sys.exit(1)
    print("Smoke test passed -- prod deployment is healthy.")

if __name__ == "__main__":
    main()

Four categories, each answering “does this exist and look right” – never “is this specific number correct,” which is exactly the line separating a smoke test from an integration test. This runs in seconds, not the many minutes Lecture 5’s suite needs, since it never triggers an actual pipeline run at all.

Wiring it into deploy-prod

      - name: Deploy bundle to prod
        env:
          DATABRICKS_HOST: $
          DATABRICKS_TOKEN: $
        run: |
          databricks bundle validate --target prod
          databricks bundle deploy --target prod
          databricks bundle run environment_bootstrap --target prod
      - name: Smoke test prod deployment
        env:
          DATABRICKS_HOST: $
          DATABRICKS_TOKEN: $
        run: python scripts/smoke_test.py

A failed smoke test here fails the whole deploy-prod job – worth alerting on immediately, since it means the deploy itself is broken, not that some downstream business rule needs a closer look.

The first real production run

databricks bundle run stepright_daily_pipeline --target prod

Triggered manually, once, rather than waiting for the 3 AM schedule to fire on its own – watching this first prod run complete end to end, with real eyes on the Jobs UI, is worth doing once before trusting the schedule alone. Confirm report’s printed summary shows real production counts, not the small, synthetic batches uat’s integration suite seeds.

What actually needs watching after go-live

Nothing new gets built here – every piece already exists from earlier sections, now finally pointed at real production data:

What Where Built in
Daily pipeline failure Email to data-eng-team@company.com Section 5
Quarantine rate trending up Dashboard + alert Section 7
A single-run threshold breach dq_check’s gate, plus the breach alert Sections 5 and 7
Deploy-time failures GitHub Actions run status, this section Section 8

Go-live doesn’t add new monitoring surface area – it’s the moment every monitoring surface this capstone already built starts watching data that actually matters to the business, rather than dev’s development batches or uat’s deliberately-seeded test data.

Why the smoke test checks existence and configuration, not data

Every check in smoke_test.py asks “does this object exist and look configured correctly” – never “is today’s row count reasonable,” which is deliberately Section 5’s dq_check’s job, not this script’s. A smoke test that queried table row counts would duplicate a check that already runs, automatically, every single day the pipeline executes on its own schedule – there’s no need to re-verify at deploy time something the daily job already re-verifies at 3 AM regardless. Keeping the smoke test scoped to deployment health, and nothing broader, is what keeps it fast enough to run on every single prod deploy without becoming a second integration suite in disguise.

Rolling back a bad prod deploy

git log --oneline -- databricks.yml resources/
git checkout <previous-good-commit>
databricks bundle deploy --target prod

A bundle deploy is, at its core, a deployment of whatever code and configuration a specific Git commit holds – rolling back means checking out an earlier known-good commit and redeploying it, the same bundle deploy command as any forward deploy, just pointed at older code. This is exactly why every resource file and every transformation change in this project has lived in version control from Section 1, Lecture 2 onward: a rollback with no reliable prior version to redeploy isn’t a rollback at all, just a scramble to reconstruct what used to work.

What this capstone actually proved

Eight sections ago, this project started with an empty Git folder and a plan. What exists now: seven bronze and silver tables built on Auto Loader and AUTO CDC, five gold materialized views serving five real consumers, a scheduled job with a real data quality gate, a unit-tested transformation layer, a monitoring dashboard built on the pipeline’s own event log, and a full dev-to-uat-to-prod promotion path with automated deployment and testing at every stage. Nothing here was theoretical – every table, every test, every YAML file exists because an earlier lecture built it for a stated reason, and every later section depended on that earlier work being genuinely correct, not merely plausible-looking.

Where this leads

StepRight was built from a clean slate – no legacy system to migrate, no existing PL/SQL to translate, no cutover to plan around live production traffic. Part 3 removes that simplification: a real Oracle-or-Teradata-scale legacy warehouse, decades of stored procedures, and a migration that has to happen without ever taking the business offline. Every pattern this capstone built – medallion layering, quality gating, orchestration, testing, and now deployment – is the target architecture Part 3 migrates toward; what’s missing is everything Part 3 adds: how to profile a system nobody fully understands anymore, how to decide what to rehost versus re-architect, and how to prove the new system produces the same numbers as the old one before anyone’s allowed to turn the legacy system off.


| ← Previous: Develop and Trigger Your CI/CD Pipeline for Deployment and Integration Testing | Next: Legacy Migration to Databricks → | |:—|—:|


Back to top

Independent, self-authored Databricks companion notes.

This site uses Just the Docs, a documentation theme for Jekyll.