Skip to content

Docker

This guide covers packaging a Haute pipeline as a Docker container - a self-contained image that can run anywhere. The container target builds the image and pushes it when a registry is configured; it deliberately does not provision a host, create an endpoint, or start the container. Your IT team (or a separate platform workflow) runs it from there.

What is Docker?

Docker is a tool that packages your application and everything it needs (Python, libraries, model files) into a single container - like a shipping container for software. Anyone with Docker installed can run it, regardless of what's on their machine. You don't need to understand Docker to use this target - Haute and CI handle everything.

You don't need Docker on your laptop

Docker runs on the CI runner, not your machine. You never need to install Docker, build images, or run containers yourself. You just merge to main and CI does the rest. Your IT team takes the built image and deploys it to their infrastructure.

If your organisation uses Databricks, the Databricks target is simpler and doesn't involve containers at all.

When should I choose this target?

Choose Docker if your company doesn't use Databricks and your IT team has asked you for a container image, or if they've said they'll handle the hosting and just need a package from you. This is also the right choice if your IT team uses Kubernetes, Docker Compose, or any other container platform you haven't heard of - you don't need to know what those are.

This target involves your IT team

As an analyst, your role is to configure haute.toml and merge to main. CI builds and pushes the Docker image automatically. Your IT team handles everything else - the registry, the hosting, the infrastructure. The sections below are split: Steps 1-5 are for you, and the "For your IT team" section at the bottom is reference material for IT.


How it works

  1. You edit your pipeline locally and preview with haute serve
  2. You merge to main - CI automatically builds a Docker image containing your pipeline and pushes it to a container registry when registry is configured
  3. Your IT team (or an automated platform) runs the image as a service, exposing the API

The generated CI workflow logs in to the registry with the credentials from Step 1, then builds and pushes; IT handles the infrastructure and must provide the endpoints before the smoke and impact steps can use them (Step 3).


Prerequisites

Before you start, you need:

  • Python 3.11+ installed on your machine
  • Haute installed (uv add haute - see Installing Haute)
  • A container registry - this is an online storage service for Docker images (like a shared drive for containers). Ask your IT team which registry your company uses - they'll give you the registry URL and credentials

If you haven't initialised your project yet, open your VS Code terminal and run:

haute init --target container --ci github

Your team may have already done this

If you cloned an existing project that already has a haute.toml file, skip this step - it's already initialised.


Step 1: Add registry credentials to CI

This step is usually done by IT

Your IT team will know the registry URL and credentials. Ask them to add these as CI secrets, or give them the .env.example file from your project.

CI needs credentials to push the Docker image to your registry. Add these as encrypted secrets in your CI provider:

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

How to add them depends on your CI provider - see GitHub Actions, GitLab, or Azure DevOps.

Before each staging and production deploy, the generated workflow logs in with these two secrets to the host of the registry you set in Step 2: ghcr.io for ghcr.io/myorg, and Docker Hub for a bare namespace such as myorg. With no registry set, the image stays on the CI machine and nothing logs in.


Step 2: Configure haute.toml

Here's what the container section of your haute.toml looks like:

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

[deploy]
target = "container"
model_name = "motor-pricing"

[deploy.container]
registry = ""
port = 8080
base_image = "python:3.11.9-slim"

[test_quotes]
dir = "tests/quotes"

What each setting means

Setting What it does Example
target Tells Haute to build a Docker container "container"
model_name Used as the Docker image name "motor-pricing"
registry Where to push the image. Leave empty for local-only. "ghcr.io/myorg" or ""
port The port the API server listens on inside the container (like an extension number on a phone system) 8080
base_image The base Docker image to build from. It must name an exact patch version (python:3.11.9-slim) or a digest (python@sha256:…); a floating tag such as python:3.11-slim is refused before the build. An official python image must run your project's Python (its .python-version), so the container runs the Python CI tested; haute init writes one that does "python:3.11.9-slim" for a 3.11 project

The registry value is the address your IT team gave you for where Docker images are stored. If you don't know it, ask them: "What's our container registry URL?" They'll give you something like ghcr.io/yourorg or myregistry.azurecr.io. Put that value in haute.toml.


Step 3: Give smoke and impact their endpoints

The smoke and impact steps call the running staging and production services, so they need their addresses. haute init writes both into haute.toml commented out; once IT runs the services, uncomment them and put in the real addresses:

[ci.staging]
endpoint_suffix = "-staging"
endpoint_url = "https://motor-pricing-staging.example.com"

[ci.production]
endpoint_url = "https://motor-pricing.example.com"

Until the staging address is set, the smoke step stops with "No staging endpoint URL configured". Until production is first released, the impact step reports a first deployment instead of a comparison: it reads that from your repository, where the production job tags each release deploy/.... After the first release, production must answer at its address; an unset address, an error status (404 included) or no answer fails the step.


Step 4: Deploy by merging to main

You don't run any deploy command. When you merge to main, CI automatically:

  1. Validates your pipeline and scores test quotes
  2. Generates an API app that wraps your pipeline (with two web addresses: /quote for scoring and /health for status checks)
  3. Generates a Dockerfile (a recipe that tells Docker how to build the container)
  4. Builds the Docker image
  5. Pushes the image to your registry when registry is configured (otherwise the image remains only on that CI runner)

Once the image is in the registry, your IT team (or a separate platform workflow) can pull and run it. A successful haute deploy for this target means image packaging completed; it is not evidence that a live endpoint exists.

What does success look like?

After the haute deploy job succeeds, you should see:

  1. In the CI logs - ✓ Deployed: motor-pricing v1, followed by Image: and the image tag, such as ghcr.io/yourorg/motor-pricing:a1b2c3d
  2. From your IT team - confirmation that the image is running and the endpoint URL to test
  3. In the CI workflow - smoke and impact jobs can pass only after your hosting process runs the staging service and you have set its address (Step 3); the impact report compares against production once its address is set too

The deploy job's success confirms only validation and image packaging/push. It does not make the later smoke and impact jobs green by itself, and the pipeline is live only after your hosting process has started the image and its health checks pass.


Step 5: Test the deployed API

Once your IT team has the container running, you can test it with Python:

import requests

# Replace with the actual URL your IT team gives you
url = "http://<your-endpoint>:8080"

# Score a test quote
response = requests.post(
    f"{url}/quote",
    json=[{"IDpol": 99001, "VehPower": 7, "DrivAge": 42, "Area": "C", "VehBrand": "B12"}],
)
print(response.json())

# Check the service is alive
health = requests.get(f"{url}/health")
print(health.json())  # {"status": "ok", "model": "motor-pricing", ...}

For your IT team

As an analyst, you can stop reading here - your job is done after Step 5 above. The sections below are reference material for whoever manages the container registry and hosting infrastructure.

What Haute generates (click to expand)

When Haute deploys, it builds the image from a directory of its own under the project's .haute_build/, containing:

File Purpose
app.py API application (built with FastAPI) that wraps the scoring pipeline
Dockerfile Instructions for building the Docker image
deploy_manifest.json Metadata about what was deployed (version, schemas, artifacts)
artifacts/ Copies of model files
utility/ Your project's utility package, when the pipeline imports it
requirements.lock, haute.lock Every Python package the image installs, pinned by version and hash, resolved for Linux (linux/amd64) and the image's Python when Haute deploys; the image installs exactly these and resolves nothing
dependency_lock.json What the lock was resolved for and from; the image checks its installed packages against it as it builds

These are generated fresh on every deploy, in a new directory each time, which is removed once the image is built and pushed. Run haute deploy --keep-build to keep it for inspection.

API contract (click to expand)

The generated container exposes two endpoints:

Endpoint Method Purpose
/quote POST Send quote data (JSON object or array), receive premium results in a stable envelope
/health GET Returns a JSON object with "status": "ok", the model name, the Haute version and the input and output schemas - used by infrastructure to check the service is alive

The /quote endpoint accepts a JSON object or array of quote objects and returns { "rows": [...], "row_count": n, "returned_rows": n, "truncated": false, "limit": 1000, "execution_metrics": {...} }, one row per quote in the order sent. A pipeline that answers one quote per request (see Requests with several quotes) refuses an array of several quotes with HTTP 422 naming the nodes that stop it; /health reports which kind the deployment is under request_rows. For larger results, rows is capped at limit, row_count reports the full result size, and truncated is true. Send Accept: application/x-ndjson to receive every row as newline-delimited JSON instead. A request body over the size limit gets HTTP 413. A failure while scoring gets HTTP 500 with "error_code": "deploy_internal_error" and a request_id; the container's log holds the detail under that id, and every response carries its id in the X-Request-ID header. No MLflow, no pandas - just JSON in, JSON out.

Registry options (click to expand)
Registry registry value in haute.toml Credentials needed
Docker Hub docker.io/yourname Docker Hub username + access token
GitHub Container Registry ghcr.io/yourorg GitHub username + personal access token
AWS ECR 123456789012.dkr.ecr.eu-west-1.amazonaws.com AWS credentials
Azure Container Registry myregistry.azurecr.io Azure service principal
Local only "" (empty) None needed
Handing off the image (click to expand)

The Docker image is pushed to the registry configured in haute.toml. On the machine that built it, you can also export it as a .tar file, using its full tag (which includes the registry when one is configured):

docker save ghcr.io/yourorg/motor-pricing:a1b2c3d > motor-pricing.tar

The image can run on any platform that supports Docker containers - Kubernetes, Docker Compose, AWS ECS, Azure Container Apps, or others. It requires:

  • POST /quote on the configured port (default 8080)
  • GET /health on the same port
  • No environment variables required at runtime, unless an Apply Optimisation node loads its result from an MLflow run or registered model; that container needs MLflow credentials

Troubleshooting

CI fails with "authentication required" on push

The registry refused the login or the push. Check that DOCKER_USERNAME and DOCKER_PASSWORD are set correctly in your CI provider, that the account may push to the registry in haute.toml, and that the Log in to the registry step ran before the deploy (a workflow generated by an older Haute has none: run haute ci update).

CI fails during image build

Check the CI logs for the build step. Common causes:

  • Missing Python dependencies - the image installs Haute's scoring runtime and the packages your model files need, not your pyproject.toml, so a package your pipeline code imports beyond those is not in the image
  • Model file not found - ensure all model files referenced in your pipeline are committed to the repository

Container crashes on startup (reported by IT)

Ask your IT team for the container logs. Common causes:

  • Missing dependencies inside the image
  • Model files not found - ensure they're committed and referenced correctly in your pipeline

API returns errors

If the container is running but /quote returns errors, the pipeline is likely failing at runtime. Check the container logs for Python tracebacks. The most common cause is a schema mismatch between the request JSON and what your pipeline expects.