Skip to content

GitHub Actions

This guide explains the GitHub Actions starter workflows generated by haute init. They run validation and deployment commands. As generated, anyone with write access to the repository can start the production deploy; to make a reviewer approve it, see Who can start production. Databricks is the only end-to-end service-update adapter; generic container builds/pushes only, and Azure Container Apps, AWS ECS, and GCP Cloud Run build and push the image and leave the service update to you.

New to Git, pull requests, or CI/CD?

This page assumes you understand basic Git concepts like branches, commits, and pull requests. If these are unfamiliar, read Before You Start first - it explains everything in plain English.

Not sure if this is the right page?

If you access your project on github.com, this is the right page. If it's on gitlab.com (or a company GitLab server), go to GitLab CI/CD. If it's on dev.azure.com, go to Azure DevOps. If you're not sure, ask your IT team or tech lead: "Where is our code repository hosted?" They'll tell you GitHub, GitLab, or Azure DevOps.

What is CI/CD?

CI (Continuous Integration) means your code is automatically tested every time you propose a change. CD (Continuous Deployment) means the workflow can run a deployment step after those tests pass. A live endpoint is created or updated only when the selected target and hosting process support it. See full explanation.

Read this before enabling the starter workflows

The files are examples that call haute deploy, haute smoke, and haute impact in order. Haute does not create staging infrastructure, require an approval, or guarantee an impact report. On Databricks, haute deploy returns once the endpoint serves the new version, and fails with Databricks' reason when it cannot. A failed deploy prevents dependent smoke and impact jobs. For platform-container targets, the deploy job succeeds once the image is pushed and prints its tag for your manual platform update; their generated workflow has no smoke or impact job, because nothing runs the new image until you update the service.

On container targets, the deploy jobs log in to the registry in haute.toml before they push, and the smoke and impact steps need the service addresses in haute.toml. The impact step compares a portfolio sample in the repository when you name one, and your test quotes until you do.

What is GitHub Actions?

GitHub Actions is GitHub's built-in automation tool. It runs scripts (called workflows) in response to events in your repository - like opening a pull request or merging to the main branch. It's free for public repositories and has a generous free tier for private ones.

Who does what on this page?

This page has two parts. As a pricing analyst, you only need to do Step 2 (commit the workflow files) and Step 3 (test it). Step 1 (adding secrets) is usually done by your IT team or tech lead - they have the credentials and know the GitHub UI. If you're setting this up yourself, Step 1 walks you through it, but it's perfectly fine to hand it to IT and say: "Can you add these secrets to our GitHub repository?", with the names haute init printed (Step 1 lists them by target).


What Haute generates

When you run haute init --ci github, Haute creates three workflow files in .github/workflows/, and a setup action their jobs share, .github/actions/setup/action.yml:

File When it runs What it does
ci.yml On every pull request Lints, type-checks, runs tests, validates your pipeline
deploy-staging.yml On a push to main or manual dispatch Runs validation, then deploy/smoke/impact commands in dependency order
deploy-production.yml When you click "Run workflow" manually Runs haute deploy for production, for anyone with write access unless an environment's reviewers must approve it

This gives you a complete release pipeline:

Pull request → CI checks → Merge → target-dependent staging deploy → smoke/impact only if deploy succeeds → [your GitHub approval process] → manually triggered production deploy

Workbench projects

A workbench project hosts no API: what it releases is the underwriter app. Its haute init writes three workflow files instead:

File When it runs What it does
ci.yml On every pull request Lints, type-checks and runs tests, then builds the app's release, which checks your pipeline and schema and scores your test quotes
release.yml On a push to main or manual dispatch Runs the same checks, then, on main only, builds the app's image once, pushes it to the registry [deploy.container] names, rolls the staging app out to it, and compares staging with production on saved quotes, keeping impact_report.md as the impact-report artifact
release-production.yml When you click "Run workflow" manually Rolls the production app out to the release staging runs, for the commit you give (main's head when blank)

Each rollout stops the old release before the new one starts, checks the app, and puts the previous release back when the new one fails (The underwriter app). Read the impact report before you run release-production.yml. One rollout of an environment runs at a time. The jobs read your host's secrets, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_CLIENT_ID and AZURE_CLIENT_SECRET, which haute ci secrets sets from .env; the service principal needs the AcrPush role on the registry.


How it works, step by step

1. You make a change and open a pull request

You edit your pipeline, update a model, or change a configuration. You push your changes to a branch and open a pull request (PR) on GitHub - which is a way of asking your teammate to review your changes before they go live.

2. CI runs automatically

The ci.yml workflow runs immediately:

  • Lint & format - checks your code style with ruff
  • Type check - runs mypy to catch type errors
  • Tests - runs pytest
  • Pipeline validation - runs haute lint, which refuses what the editor would refuse to save, and haute deploy --dry-run, which checks the test quotes pass

If any of these fail, the PR gets a red ❌ and you can see what went wrong.

3. You merge to main

Once CI passes and your teammate has reviewed the PR, you merge it - which applies your changes to the main version of the project. This triggers the deploy-staging.yml workflow (named Deploy) when the merge changes the pipeline, its configuration, data, models or test quotes.

4. Staging deployment (automatic)

The deploy workflow:

  1. Validates again, by running the same checks as ci.yml
  2. Deploys to staging - runs haute deploy --endpoint-suffix "-staging". On Databricks it waits until the staging endpoint serves the new version, and fails with Databricks' build log if the model cannot be served; the generic container target does not host an endpoint; platform-container targets push the image and leave the service update to you, so their workflow ends there.
  3. Smoke tests staging - only after a successful deploy, runs haute smoke --endpoint-suffix "-staging" against an existing live endpoint; on the generic container target it runs haute smoke, which calls the staging address in haute.toml
  4. Runs impact analysis - only after smoke succeeds, runs haute impact --endpoint-suffix "-staging" against existing staging and production endpoints; on the generic container target, haute impact at the addresses in haute.toml

When the impact command runs, its report is uploaded as the impact-report artifact and added to the run's summary page; it is not produced when an earlier stage fails.

5. You review the impact report

The impact report shows you exactly what changed, for each output column:

  Output: technical_price
  ────────────────────────────────────────────────────────────
  Staging mean:          548.57     Production mean: 536.12
  Rows changed:           8,420 / 10,000 (84.2%)
  Mean change:            +2.3%
  Median change:          +1.9%
  Premium impact:         +2.3%
  Max increase:          +18.7%
  Max decrease:           -4.2%

  Distribution:  P5=-0.8%  P25=+0.6%  P50=+1.9%  P75=+3.4%  P95=+7.1%

Segment tables follow, one for each text column of the portfolio sample with 2 to 50 values, listing the segments whose prices move most. Without a portfolio sample ([safety] impact_dataset), the report compares your test quotes and says so. The report is advisory: it never fails the job, so reading it is your decision point.

This is where you decide: does this change make sense? Is the impact expected?

6. You promote to production (manual)

If you're happy with the impact report, go to the Actions tab in GitHub, find the Deploy → Production workflow, and click Run workflow.

You can optionally paste the git SHA from the staging deploy to make sure you're deploying exactly what was tested - not something that was merged since.

The production workflow runs haute deploy and then tags the release. Its ability to update a live endpoint depends on the selected target. Anyone with write access to the repository can run it, whatever the branch protection: to make a reviewer approve each run, name an environment in [ci.production] environment and give it required reviewers (Who can start production).


Setup

Step 1: Add your credentials as GitHub Secrets

This step is usually done by IT

If you're not comfortable navigating GitHub's settings, ask your IT team or tech lead to do this. Give them the secret names haute init printed; the tables below list them by target.

Your CI/CD workflows need credentials stored securely in GitHub.

The quickest way: let Haute set them. Copy .env.example to .env and fill in the values, and sign in to the GitHub CLI once with gh auth login. Then either:

  • Push from the editor. The Push that first publishes your project to a new, empty GitHub repository also stores the secrets your deploy target needs, before your main branch arrives, so the first deploy has them. If that Push couldn't set them, a message says why. Whenever a later Push finds the repository missing some, the Git panel shows how many beside a Set from .env button.
  • Run haute ci secrets in your project.

Either way Haute reads the secrets from .env, checks that Databricks accepts them, and stores them as encrypted repository secrets, never printing their values. The editor only adds secrets the repository doesn't have yet; to replace one, for example after rotating a token, run haute ci secrets. haute ci secrets --check lists which the repository has.

Or add them by hand:

  1. Go to your repository page on GitHub (e.g. github.com/yourcompany/motor-pricing)
  2. Click the ⚙️ Settings tab (near the top of the page, next to "Insights")
  3. In the left sidebar, click Secrets and variables → Actions
  4. Click the green New repository secret button
  5. For each credential below, enter the Name and Secret value, then click Add secret:

For Databricks:

Secret name Value
DATABRICKS_MLFLOW_HOST The workspace URL for MLflow and the model registry
DATABRICKS_MLFLOW_TOKEN A personal access token scoped for MLflow and the model registry
DATABRICKS_RATING_HOST Your workspace URL (e.g. https://adb-xxx.12.azuredatabricks.net)
DATABRICKS_RATING_TOKEN Your personal access token (starts with dapi) scoped for Model Serving

For Docker/Container targets:

Secret name Value
DOCKER_USERNAME Your registry username
DOCKER_PASSWORD Your registry password or access token

For AWS ECS:

Secret name Value
AWS_ACCESS_KEY_ID Your AWS access key
AWS_SECRET_ACCESS_KEY Your AWS secret key
AWS_DEFAULT_REGION e.g. eu-west-1

ECR passwords last 12 hours, so the deploy jobs fetch a fresh one with these keys each run (see AWS ECS).

For Azure Container Apps:

Secret name Value
AZURE_SUBSCRIPTION_ID Your Azure subscription ID
AZURE_TENANT_ID Your Azure tenant ID
AZURE_CLIENT_ID Your service principal app ID
AZURE_CLIENT_SECRET Your service principal password

The deploy jobs log in to ACR as the service principal (see Azure Container Apps).

For GCP Cloud Run:

Secret name Value
GCP_PROJECT_ID The Google Cloud project holding the registry and the service
GCP_SERVICE_ACCOUNT_KEY The JSON key of the service account CI deploys as, on one line

The deploy jobs log in to Artifact Registry with the service account's key.

How to find these values

.env.example has a line for each, with a comment saying what it holds, beside variables CI does not read (on Databricks, the data-access pair DATABRICKS_HOST and DATABRICKS_TOKEN). Your target's setup page explains where each value comes from.

Step 2: Commit the workflow files

The workflow files were created by haute init in .github/workflows/. Make sure they're committed to your repository:

git add .github/ .python-version pyproject.toml uv.lock
git commit -m "Add CI/CD workflows"
git push

Every job installs with uv sync --frozen, which needs the project's uv.lock: commit it with pyproject.toml. uv add creates it; run uv sync once if it is missing. The jobs run the Python that .python-version names, so commit that file too.

What do these commands mean?

  • git add - tells Git to include these files in your next save
  • git commit -m "..." - saves a checkpoint with a description
  • git push - uploads the changes to the shared copy (GitHub)

See Git workflow for a full explanation.

Step 3: Test it

  1. Create a branch, make a small change, and open a pull request
  2. Watch the Actions tab - the CI workflow should start automatically
  3. If it passes, merge the PR
  4. Watch the deploy workflow run - it should deploy to staging and generate an impact report

The generated workflows in detail

You don't need to read or understand the YAML below

The workflow files were generated by haute init and work out of the box. The sections below are reference only - for troubleshooting or if your IT team wants to understand what’s happening.

ci.yml - Pull request checks
name: CI

on:
  pull_request:
    branches: [main]

jobs:
  lint:
    # Runs ruff check and ruff format --check
  typecheck:
    # Runs mypy
  test:
    # Runs pytest
  pipeline-validate:
    # Runs haute lint and haute deploy --dry-run

This runs on every pull request targeting main, and again when the deploy workflow calls it after a merge. All four jobs run in parallel for speed.

deploy-staging.yml - Merge to main
name: Deploy

on:
  push:
    branches: [main]
    paths:
      - "rating/**"
      - "tests/quotes/**"
      - "haute.toml"
      - "pyproject.toml"
      - "uv.lock"
  workflow_dispatch:

jobs:
  validate:     # runs the checks in ci.yml
  deploy-staging:  # haute deploy --endpoint-suffix "-staging"
  smoke-test:      # haute smoke --endpoint-suffix "-staging" (container: haute smoke)
  impact-analysis: # haute impact --endpoint-suffix "-staging" (container: haute impact)

A push to main triggers it only when relevant files change (the rating/ pipeline package, config, data, models, test quotes, and the dependency files pyproject.toml and uv.lock; a workbench's files are in the pipeline package, so a change to them deploys too); you can also run it by hand from the Actions tab. Jobs run in sequence - if staging fails, smoke test doesn’t run.

deploy-production.yml - Manual production deploy
name: Deploy → Production

on:
  workflow_dispatch:
    inputs:
      sha:
        description: Git SHA that was staged & impact-analysed
        required: false

jobs:
  deploy-production:
    # Runs under [ci.production] environment, when haute.toml names one
    # Verifies SHA matches (if provided)
    # Runs haute deploy (no suffix = production)
    # Tags the release in Git

This is triggered manually from the GitHub Actions UI. The optional SHA input lets you pin the exact commit that was tested.


Working solo?

If you're the only person on the team, the process is the same but simpler:

  1. min_approvers in haute.toml is not enforced by Haute; to make a reviewer approve production, see Who can start production
  2. The generated workflow attempts CI → staging → smoke test → impact analysis → manual production deploy; later jobs require a ready endpoint and do not run after a failed deploy
  3. An impact report is generated only when the impact command runs successfully

Troubleshooting

Workflow doesn't trigger

  • On PR: Make sure the PR targets the main branch (not another branch)
  • On merge: Make sure the deploy-staging.yml paths filter matches your changed files. If you only changed a markdown file, it won't trigger (by design)
  • Check the Actions tab: Go to your repo → Actions → look for the workflow run
  • Nothing has ever run in a new repository: if Run workflow works but no push or pull request has ever started a run, the repository's first push created more than one branch. GitHub never runs workflows from pushes or pull requests in such a repository, and later pushes do not change that. Haute 0.6.0 and earlier pushed like this the first time you pushed a project to an empty repository; later versions publish main on its own first. Create a new, empty repository and push the project to it. If you create a repository's first branch yourself, push main on its own first (git push origin main)

"Credentials not found" or "Authentication failed"

Your GitHub Secrets are missing or misnamed. On Databricks, haute deploy names every missing one before it does anything else, and haute ci secrets --check lists which the repository has. Double-check:

  1. Go to Settings → Secrets and variables → Actions
  2. Verify the secret names match exactly (they're case-sensitive)
  3. Verify the values are correct (no trailing spaces or newlines)

Deploy workflow succeeds but endpoint isn't updated

Check the deploy step logs in the Actions run. For Azure Container Apps, AWS ECS and GCP Cloud Run this is expected: the log ends with Image pushed: and The <target> service was not updated, and you point the service at that image yourself (see your target's documentation).

Impact analysis shows unexpected results

The impact analysis compares staging vs production. Before production's first release it reports a first deployment instead: on Databricks when the production endpoint does not exist, on the container target when the repository has no deploy/... release tag, which the production workflow pushes. After that, a production endpoint that cannot be reached fails the job, as does a missing or unready staging endpoint. Make sure staging is ready before the impact job runs.

"HEAD (...) does not match staged SHA (...)"

This error in the production workflow ("Merge may have occurred since staging.") means someone merged another PR between your staging deploy and your production deploy. The production workflow is protecting you from deploying untested code. Run the staging deploy again (push to main or re-run the deploy workflow) and then try production again.

The tag step fails

On Databricks, the production workflow's Tag release step names the tag after the latest registered model version, which it reads with haute status, using the same secrets as the deploy step. If the version cannot be read, the step fails with the reason instead of tagging a made-up version.

A workflow generated by an older Haute gave this step no credentials, so it tagged deploy/vunknown and the next production run failed on that existing tag. Run haute ci update to regenerate the workflows, and delete the deploy/vunknown tag.

Keeping the workflow files up to date

After upgrading Haute, run haute ci update. It overwrites changes you made to the generated files by hand: see Keeping the generated files up to date for what it rewrites and removes.