Skip to content

Deployment

Deployment packages a pricing pipeline for a serving target. For Databricks, Haute also registers the model and creates or updates a Model Serving endpoint. For the container target, Haute builds an image (and pushes it when a registry is configured); your platform team runs that image. For Azure Container Apps, AWS ECS, and GCP Cloud Run, Haute builds and pushes the image to your registry and finishes there; updating the service is a manual step until their adapters exist.

New to Haute? Start here.

If you haven't installed Haute yet, start with Getting Started - it covers installing everything and running your first haute serve. If you don't know what a pull request, CI/CD, or staging means, read Before You Start next - it explains every deployment concept in plain English.

Haven't built your pipeline yet?

These docs assume you already have a working pricing pipeline (rating/main.py). If you haven't created one yet, start with the Building Models guide first, then come back here when you're ready to deploy.

haute init generates CI examples, but they are ordinary workflow files that your team owns. They run Haute commands in sequence; they do not themselves provision infrastructure or create a staging environment. Review the generated workflows, and decide who can start production, before relying on them for a release process.


Your workflow

As a pricing analyst, your day-to-day workflow is:

  1. Edit your pipeline - change your Python file, update a model, adjust a transform
  2. Preview it - run haute serve to open the visual editor and check everything looks right
  3. Push and open a pull request - CI (an automated checker - see Before You Start) automatically validates your pipeline
  4. Merge to main - the generated workflow can run a deploy, smoke test, and impact analysis in that order
  5. Review the result - check logs and any impact report; choose a provider-level approval process appropriate for your team
  6. Promote deliberately - run the production workflow or deployment process your team has configured

You never need to run haute deploy, install Docker, or manage cloud credentials on your machine. The CI runner handles all of that.

What happens behind the scenes?

When a CI runner invokes haute deploy, Haute:

  1. Parses your pipeline - reads your Python file and builds a graph of all the steps
  2. Prunes to the scoring path - removes training steps, data exports, and anything not needed for live scoring
  3. Collects artifacts - finds all the model files (e.g. .cbm, .pkl) and data files your pipeline's scoring path references and bundles them, together with your pipeline's utility/ package. The data the quote output reads must be in the pipeline folder (rating/files/data/): a Data Input on the scoring path that reads from data/, or anywhere else outside rating/, stops the deploy and names the node. Anything the quote output doesn't depend on is dropped in step 2, so it can read data from anywhere: model training, optimisation, data exports, or the batch input of a Source Switch. A data file that is not parquet is copied into a fresh snapshot first, so the deployment carries the file as it is now. A lakehouse table ships as last imported: Haute can't detect its changes, so click Import on its Data Input before deploying. Results cached by running the pipeline in the editor are never deployed: the deployed model works out every step from the data and models it carries
  4. Validates - runs your test quotes through the pruned pipeline to make sure it works. Your preamble can import from utility/; an import of any other file in your project is refused, because the deployed pipeline would not have it, so keep shared helpers in utility/
  5. Packages and uploads - wraps everything into the format the selected target expects and uploads it where supported. The package pins the exact Haute and Polars versions the deploy ran, because a Polars release can change a pipeline's results, and a Databricks model refuses to load under another Polars
  6. Dispatches by target - Databricks creates or updates Model Serving; container returns the image for a separate hosting step; the Azure, ECS, and GCP targets build and push the image and finish there, because their service-update integrations are not implemented

Choosing a target

A target is where your pipeline will run in production. Haute supports several:

Target Best for What you need
Databricks Teams already using Databricks A Databricks workspace - the simplest option, no containers involved
Docker Companies without Databricks IT takes the package and deploys it on their infrastructure
AWS ECS Teams on AWS (with IT support) An AWS account, a registry and a manual ECS service-update handoff: Haute pushes the image and prints its tag
Azure Container Apps Teams on Azure (with IT support) An Azure subscription, a registry and a manual Container Apps revision handoff: Haute pushes the image and prints its tag
GCP Cloud Run Teams on GCP (with IT support) A registry and a manual Cloud Run service update: Haute pushes the image and prints its tag
SageMaker / Azure ML Planned targets Not offered by haute init; a haute.toml naming one is rejected before deployment with NotImplementedError

You pick your target once when you set up the project. The command is:

haute init --target databricks

--ci picks the CI/CD provider the workflow files are written for: github (the default), gitlab, azure-devops, or none for no workflow files. Run it in a terminal without them and haute init asks, as numbered lists: how the project prices quotes, where your repository is, and where it is hosted.

Answer Automated for a pipeline other systems call, which this page deploys. A Workbench project, where underwriters price quotes in an app, hosts no API: haute deploy refuses in one, and its workflows check and publish the underwriter app instead.

This generates all the deployment files you need. You don't write them by hand - haute init creates them for you. Here's what your project folder looks like before and after:

Before haute init:

my-project/
  main.py
  pyproject.toml

After haute init --target databricks --ci github:

my-project/
  .env.example
  .gitattributes
  .githooks/
  .githooks/pre-commit
  .github/
  .github/actions/
  .github/actions/setup/
  .github/actions/setup/action.yml
  .github/workflows/
  .github/workflows/ci.yml
  .github/workflows/deploy-production.yml
  .github/workflows/deploy-staging.yml
  .gitignore
  .python-version
  data/
  haute.toml
  pyproject.toml
  rating/
  rating/__init__.py
  rating/config/
  rating/files/
  rating/files/data/
  rating/files/models/
  rating/files/params/
  rating/main.py
  rating/utility/
  rating/utility/__init__.py
  rating/utility/features.py
  tests/
  tests/quotes/
  tests/quotes/example.json
  tests/test_pipeline.py

A root main.py that is still the placeholder uv init wrote is removed, since Haute uses rating/main.py as the project pipeline; a root main.py you have changed or written yourself is kept. pyproject.toml is updated to list haute as a dependency. Existing shared files such as .gitignore are updated in place rather than replaced, and a starter pipeline or test is written only where the folder has none, so a rating/main.py already there is kept.

To change your answers later (another CI provider, another deploy target, or the other kind of project), run haute init again. It asks the same questions with your project's current answers as the defaults, then writes haute.toml afresh, the workflow files for your answers (removing the previous provider's) and .env.example. It never changes rating/ or tests/. Anything you set in haute.toml yourself, such as the registry or the Databricks catalog, and the MLflow settings saved from the app, goes back to its default: copy what you need back from the previous version (git diff haute.toml).

The starter pipeline contains 0 nodes, giving you a blank canvas.

Not sure which target to pick?

If your organisation uses Databricks, start with the Databricks target - it's the most mature and requires the least infrastructure setup. If you don't have Databricks, use Docker to start and move to a cloud target later.


The configuration file

The most important generated file is haute.toml - a plain text file that says what gets deployed and where. You don't need to write it from scratch; haute init creates it pre-filled for your chosen target. Here's the one it writes for a Databricks project named motor-pricing on GitHub:

[project]
name = "motor-pricing"
pipeline = "rating/main.py"

[deploy]
target = "databricks"
model_name = "motor-pricing"
endpoint_name = "motor-pricing"

[deploy.databricks]
experiment_name = "/Shared/haute/motor-pricing"
catalog = "main"
schema = "pricing"
serving_workload_size = "Small"
serving_scale_to_zero = true

[safety]
# `haute impact` compares staging with production on these quotes. Without a
# sample it compares the test quotes; commit a Parquet sample of your book (not
# under data/, which is gitignored) and name it here to compare that instead:
# impact_dataset = "tests/impact/portfolio_sample.parquet"

[safety.approval]
min_approvers = 2

[ci]
provider = "github"

[ci.staging]
endpoint_suffix = "-staging"

Each section is explained in detail on the target-specific pages. The key idea is: haute.toml says what gets deployed and where. It never contains passwords or secrets.

One optional setting is shared by every target: output_fields under [deploy] lists the output columns the deployed API returns, for example output_fields = ["quote_id", "technical_price"]. Without it, the API returns every column of the Quote Response. A name that is not an output column fails the deploy, listing the columns there are.


Credentials

Every target needs credentials to authenticate - for example, a Databricks access token or a Docker registry password. These are never stored in haute.toml or committed to your repository.

Credentials live in two places:

  • On your laptop - in a .env file. Copy .env.example to .env and fill in the values you have. The editor loads it when haute serve starts, and so does every Haute command that reads haute.toml, such as haute ci secrets, which copies the deploy's credentials from it into your CI provider. This file is gitignored and never shared.
  • In your CI provider - as encrypted secrets (GitHub Secrets, GitLab CI/CD Variables, or Azure DevOps Variable Groups), so the automated deploy pipeline can use them. CI never sees .env, since it is never committed. Your IT team or tech lead usually sets these up once.

On Databricks, .env.example holds three pairs, kept apart because a token's scopes can be narrow (one token can serve all three when its scopes cover them):

Pair What reads it
DATABRICKS_HOST, DATABRICKS_TOKEN The editor, to browse the workspace and read and write its tables. No CI step reads it
DATABRICKS_MLFLOW_HOST, DATABRICKS_MLFLOW_TOKEN MLflow: the editor's experiment tracking and model registry, and the deploy's model registration
DATABRICKS_RATING_HOST, DATABRICKS_RATING_TOKEN The serving endpoints: the deploy, and haute smoke and haute impact

Both use the same credential values, but CI needs only some of them: .env.example also holds variables no CI step reads, such as Databricks' data-access pair, DATABRICKS_HOST and DATABRICKS_TOKEN. haute init ends by printing the secrets your CI needs - give those names to whoever sets up the CI secrets. On GitHub, haute ci secrets --check lists which of them the repository has.

The target-specific pages and the CI/CD setup guides explain exactly which secrets to add and how.


Test quotes

Before every deployment, Haute scores your test quotes - example requests your API will receive, with the answers you expect where you know them. If any of them fail, the deployment is blocked.

Test quotes live in tests/quotes/ as JSON files, each holding a list of cases:

[
  {
    "_description": "Typical 42-year-old driver",
    "input": {"IDpol": 99001, "VehPower": 7, "DrivAge": 42, "Area": "C", "VehBrand": "B12"},
    "expected": {"technical_price": 536.12},
    "tolerance_pct": 0.01
  }
]
  • input is the request, as your API receives it. For a Workbench Input, that includes the quote's quote_reference and quote_option in its quote_details, as {"quote_details": {"quote_reference": "TEST-1", "quote_option": 1}, ...}.
  • expected (optional) lists outputs the response must match. A number matches within tolerance_pct, a fraction (0.01 is 1%, at most 1; the default, 0, means exactly); any other value must be equal.
  • Keys starting with _ are notes and are ignored.

The smoke test sends each input to the deployed staging endpoint as a request of its own and checks its expected outputs the same way.

Replace the starter example

The tests/quotes/example.json that haute init writes is a placeholder in the shape above, with made-up fields and no expected output. Replace it with your own quotes, and the prices you expect for them, before your first deploy.

This catches problems early: schema mismatches, missing model files, runtime errors, and prices that drift from the ones you expect. Think of it as a sanity check that runs automatically before every deploy.


What a quote must give

A quote sent to a pipeline with a Quote Input must give every column the input's sample has: send null for a value the quote does not have. A quote that leaves a column out is refused with HTTP 422, naming the quote and the column, so a client that forgets a rating factor gets an error rather than a price. The one exception is an exposure that one of the pipeline's models applies (an offset under a log link): a quote may leave it out and is priced for a full policy term. A key the sample does not have is ignored.

On Databricks, Model Serving turns a request's quotes into one table before Haute reads them, so a column is refused only when every quote in the request leaves it out. In a request with several quotes, a quote that leaves out a column another quote gives is read as if it sent null. Send each quote's full set of columns.


Requests with several quotes

A request to a deployed pipeline can hold several quotes, sent as a JSON array. The answer then has one row per quote, in the order the quotes were sent, so the first row answers the first quote. haute impact sends a portfolio sample ([safety] impact_dataset) this way, in batches; without one, it sends each test quote as a request of its own.

Every deployment answers such a request, in one of two ways. When you deploy, Haute checks every node between the Quote Input and the Quote Response to make sure each quote keeps exactly one row, in its place, and that no quote's answer depends on the other quotes sent with it. haute deploy prints the result.

  • When the check passes, the quotes run through the pipeline together, which is fast. Two quotes whose answers come out identical are still answered with a row each.
  • When it doesn't, the deployment answers the quotes one at a time, each exactly as if it had been sent on its own, so the answer is the same either way and only slower. The deploy output names each node that stopped the check and what it does:
In a node Why it stops the check
Filter rows, Limit rows, Remove duplicates, or an inner, semi or anti join It can drop a quote
Sort rows, or a join whose order is not the quotes' (see below) It can reorder the quotes
Unpivot to rows, Append inputs, a full join, or an Expander It can repeat a quote or add rows
A join that starts from another input instead of the quotes It answers with that input's rows
Group and aggregate, Pivot to columns, a Fill missing values strategy that reads other rows, or a formula that does (a total, an average, a rank, a running total, the previous row's value) One quote's answer would depend on the others
An Apply Optimisation node in online mode, or one that loads its result from MLflow Online mode answers in quote-id order; an MLflow result can't be checked before it is loaded
A Quote Response that nests a quote's own rows, such as its drivers, under it Run together, each driver would go under every quote holding its key, so two quotes sharing a key would share drivers
Code the check can't follow It can't be checked yet

A join keeps each quote's row when it starts from the quotes, is a left join (or a cross join with a single-row Constant), and keeps the quotes' order: left input order or left, then right in a Join another input step, Left or Left then right in an Edge Join. The table it joins must also hold each key once:

  • A lookup the deployment carries (a Data Input) is checked when you deploy, after its post-load code runs. If a key appears twice, the deploy fails naming the lookup, the join and the key: remove the repeated rows. If a quote is meant to match several rows, set the join's validation to many to many (m:m in an Edge Join); the deployment then answers the quotes one at a time.
  • A table built from the quotes themselves, such as a node's result joined back to the quotes it came from, can pair one quote with another's rows, so the deployment answers the quotes one at a time. Add columns along one chain of nodes instead of splitting the quotes and joining them back.

Answered one at a time, each quote must still give one row, so that the answers line up with the quotes. A quote that gives no row or several (one a filter drops, say) fails the request with HTTP 422 (error code quote_answer_rows), naming its place in the request; sent on its own, it is answered with every row it gives. To decline a quote, keep its row and add a column saying so, for example declined set to true, instead of filtering the quote out.

A deployment that answers the quotes one at a time scores its test quotes one request each. A Workbench Input reads one quote per request; its project is not deployed but released as the underwriter app, whose build scores each test quote as a request of its own.


Validation and release controls

Haute validates a deployment graph and scores configured test quotes before a non-dry-run target dispatch. It also provides haute smoke and haute impact commands for an already-running endpoint. These are useful release building blocks, not a managed safety system:

Safety check What it does
Dry-run validation Parses the pipeline, checks all model files exist, scores test quotes
Staging deployment --endpoint-suffix chooses a different name; the target and your infrastructure must provide that endpoint
Smoke testing haute smoke scores configured test quotes against an existing Databricks or HTTP endpoint. For HTTP endpoints it reads the address from [ci.staging] endpoint_url in haute.toml and does not accept --endpoint-suffix (see Docker)
Impact analysis haute impact compares existing staging and production endpoints on the Parquet portfolio sample named by [safety] impact_dataset, or on the test quotes when none is named, as haute init leaves it. A sample must be committed to the repository for CI to read it, and haute init gitignores data/. The report is advisory: the changes it shows never fail the run, but an endpoint that cannot be reached, or answers with a different number of rows than quotes it was sent, does
Approval gate Name an environment for the production deploy and give it reviewers in your CI provider (Who can start production); min_approvers is configuration metadata, not an enforced gate
Rollback Use your target platform's model/image revision and rollback procedure

The CI files generated by haute init sequence the available commands. Whether they run, whether production requires approval, and how a failed stage is handled are CI-provider and repository-policy decisions. See the CI setup guides (GitHub Actions, GitLab, Azure DevOps) for the generated-job behaviour and required platform configuration.

Who can start production

As generated, the production deploy on GitHub and GitLab runs under no environment, so whoever may start it starts it: on GitHub, anyone with write access to the repository can click Run workflow; on GitLab, anyone allowed to merge to main can click the ▶ play button. On GitHub, branch protection does not change that: it gates merges, not a manual run. On GitLab, who may merge to main is who may start the job, but neither asks someone else to approve a deploy. This works on every plan, a free one included. Azure DevOps' production deployment always runs under an environment, production unless you name another, and the approval checks on that environment gate it.

To make a reviewer approve each production deploy, name an environment in haute.toml:

[ci.production]
environment = "production"

Then run haute ci update, commit the workflows, and give the environment its reviewers in your CI provider: required reviewers on a GitHub environment, a protected environment with required approvals on GitLab, an Approvals check on Azure DevOps. The production job then waits for one of them. GitHub offers required reviewers for a private repository only on GitHub Enterprise, and GitLab's protected environments need GitLab Premium: on a plan without them, leave environment unset, since an environment without reviewers gates nothing.

Keeping the generated files up to date

The workflow files and .env.example are generated by the Haute version that ran haute init. After upgrading Haute, and after changing [ci.staging] endpoint_suffix, [ci.production] environment or the sign-ins in connections.toml, run:

haute ci update

It writes the files for the [ci] provider in haute.toml, as the installed Haute generates them:

  • It rewrites each generated workflow file and .env.example. Changes you made to those files by hand are overwritten.
  • It deletes the files Haute generates for another CI provider, or for the other kind of project (an automated project's or a workbench project's).
  • On GitHub, it writes .python-version when the project has none, since the jobs run the Python that file names.
  • It changes no other file: haute.toml, rating/ and tests/ stay as they are.

It reports each file as updated or unchanged. A file it adds is recorded in version history for your working branch's next Commit. Review what changed with git status and git diff, then commit it. (git diff alone does not list new files.)


Which page should I read?

If you're a pricing analyst doing this for the first time, start with Databricks - it's the simplest target and doesn't require Docker or cloud infrastructure knowledge. The Docker, AWS, and Azure pages are designed for teams with IT support or technical colleagues who can help with the infrastructure setup.

I want to... Read this
Deploy my pipeline with the least setup possible Databricks
Build a portable package my IT team can deploy anywhere Docker
Deploy to our existing AWS infrastructure AWS ECS (with IT support)
Deploy to our existing Azure infrastructure Azure Container Apps (with IT support)
Set up automatic testing and deployment See "Where is your code hosted?" below
Understand the terminal, Git, and other new concepts Before You Start

Where is your code hosted?

To set up CI/CD (the automatic testing and deployment), you need to know where your team's repository lives. Ask your IT team or tech lead if you're not sure. Then pick the matching guide:

If your project is on... Set up CI/CD with...
github.com GitHub Actions
gitlab.com (or a company GitLab server) GitLab CI/CD
dev.azure.com Azure DevOps Pipelines

Next steps

  1. New to the command line and Git? Start with Before You Start

  2. Pick your target and follow the setup guide:

  3. Set up CI/CD - pick the guide that matches where your code is hosted: