Skip to content

Tutorial: Build a Workload from source

プレミアム機能

Workload APIはプレミアム機能です。 この機能を有効にする方法については、DataRobotの担当者または管理者にお問い合わせください。

This tutorial takes a small FastAPI project from a local directory to a running Workload endpoint. You do not write a Dockerfile, run docker build, or push to a registry—the platform builds the image from your source and deploys it.

You'll create a draft artifact that builds from source, upload your code, run a build, deploy the result, invoke it, make a change and rebuild, and finally lock the artifact for production.

Pick the tab that matches how you want to work: cURL calls the REST API directly, and the CLI wraps the same calls in dr commands.

前提条件

Before starting, confirm the following are in place; the build step later in this tutorial fails without them.

  • A Python project with pyproject.toml at its root. A uv.lock beside it is required for the build; if you use the CLI, dr artifact code sync generates one for you when it is missing. Otherwise run uv lock before uploading.
  • The ID and version ID of an execution environment to use as the base image.
  • A service, agent, or mcp artifact—code-to-workload builds don't apply to other artifact types. This tutorial omits type from the artifact spec, which defaults to service.

A terminal with curl and jq, and your endpoint and token exported:

export DATAROBOT_ENDPOINT="https://app.datarobot.com/api/v2"
export DATAROBOT_API_TOKEN="<your-api-token>" 

The DataRobot CLI (dr), authenticated, with Workload commands enabled:

export DATAROBOT_CLI_FEATURE_WORKLOAD=true
dr auth login 

The project used here is a FastAPI app that serves GET /version:

my-api/
├── app.py           # FastAPI application
├── pyproject.toml   # Dependencies
├── uv.lock          # Locked dependencies
└── views/
    ├── __init__.py
    └── version.py   # GET /version endpoint 

Create the sample project

Create the project this tutorial builds and deploys. Every command below runs from the directory that will hold my-api/—the same directory the rest of this tutorial runs from—so none of it requires changing directories.

mkdir -p my-api/views 
my-api/pyproject.toml
[project]
name = "my-api"
version = "0.1.0"
description = "FastAPI app built from source"
requires-python = ">=3.11"
dependencies = [
    "fastapi>=0.115",
    "uvicorn>=0.30",
] 
my-api/app.py
from fastapi import FastAPI

from views.version import router as version_router

app = FastAPI(title="my-api")
app.include_router(version_router) 

Add an empty my-api/views/__init__.py, then the route:

my-api/views/version.py
from fastapi import APIRouter

router = APIRouter()

VERSION = "0.1.0"


@router.get("/version")
def get_version() -> dict:
    return {"version": VERSION} 

Generate the lockfile the build requires:

uv lock --project my-api 

Create the artifact

Create a draft artifact whose container builds from source. There is no imageUri and no codeRef yet: the image does not exist, and the code has not been uploaded.

Save the spec:

artifact.json
{
  "name": "my-api-artifact",
  "description": "FastAPI app built from source",
  "spec": {
    "containerGroups": [{
      "name": "default",
      "containers": [{
        "name": "primary",
        "primary": true,
        "port": 8080,
        "imageBuildConfig": {
          "dockerfile": {
            "source": "generated",
            "executionEnvironmentId": "<execution-environment-id>",
            "executionEnvironmentVersionId": "<execution-environment-version-id>",
            "entrypoint": ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
          }
        },
        "readinessProbe": {
          "path": "/version",
          "port": 8080,
          "initialDelaySeconds": 10
        }
      }]
    }]
  }
} 
curl -s -X POST "${DATAROBOT_ENDPOINT}/artifacts" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d @artifact.json | tee /tmp/artifact.json

export ARTIFACT_ID=$(jq -r '.id' /tmp/artifact.json) 
dr artifact create --spec-file artifact.json --output-format json | tee /tmp/artifact.json
export ARTIFACT_ID=$(jq -r '.id' /tmp/artifact.json) 

Bringing your own Dockerfile instead

To build from a Dockerfile in your project rather than a generated one, replace the dockerfile block with {"source": "provided", "path": "./Dockerfile"}. Everything else in this tutorial is unchanged. See Choose how the Dockerfile is obtained.

Upload the code

Upload the code through the REST API directly, or with the CLI, which also updates the artifact's codeRef automatically.

Upload a zip of the project, then attach the resulting catalog version to the artifact as its codeRef:

cd my-api && zip -r ../source.zip . -x '.venv/*' && cd ..

curl -s -X POST "${DATAROBOT_ENDPOINT}/files/fromFile/" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" \
  -F "file=@source.zip" | tee /tmp/upload.json

export CATALOG_ID=$(jq -r '.catalogId' /tmp/upload.json)
export CATALOG_VERSION_ID=$(jq -r '.catalogVersionId' /tmp/upload.json)
export STATUS_ID=$(jq -r '.statusId' /tmp/upload.json) 

The upload finishes asynchronously. Poll its status by id until it's done—unlike the build and Workload status endpoints used later in this tutorial, this one signals completion with a 303 redirect rather than a status field in the response body:

until [ "$(curl -s -o /dev/null -w '%{http_code}' \
  "${DATAROBOT_ENDPOINT}/status/${STATUS_ID}/" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}")" = "303" ]; do
  sleep 2
done 

Then patch the artifact:

curl -s -X PATCH "${DATAROBOT_ENDPOINT}/artifacts/${ARTIFACT_ID}" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "spec": {
      "containerGroups": [{
        "name": "default",
        "containers": [{
          "name": "primary",
          "primary": true,
          "port": 8080,
          "imageBuildConfig": {
            "codeRef": {
              "datarobot": {
                "catalogId": "'${CATALOG_ID}'",
                "catalogVersionId": "'${CATALOG_VERSION_ID}'"
              }
            },
            "dockerfile": {
              "source": "generated",
              "executionEnvironmentId": "<execution-environment-id>",
              "executionEnvironmentVersionId": "<execution-environment-version-id>",
              "entrypoint": ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
            }
          }
        }]
      }]
    }
  }' 

Link the project directory to the artifact once, then sync. The CLI uploads the directory and sets the artifact's codeRef for you:

cd my-api

dr artifact code init ${ARTIFACT_ID}
dr artifact code sync 

Preview what would be uploaded first with dr artifact code sync --dry-run.

Build the image

Trigger the build the same way you triggered the upload, through the REST API or the CLI.

curl -s -X POST "${DATAROBOT_ENDPOINT}/artifacts/${ARTIFACT_ID}/builds" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" | tee /tmp/build.json

export BUILD_ID=$(jq -r '.buildIds[0]' /tmp/build.json) 

The trigger returns 202 immediately. Poll until the build fails, is cancelled, or completes with its image applied to the artifact:

until curl -s "${DATAROBOT_ENDPOINT}/artifacts/${ARTIFACT_ID}/builds/${BUILD_ID}" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" \
  | jq -e '(.status == "COMPLETED" and .imageApplied) or (.status | test("FAILED|CANCELLED"))' >/dev/null; do
  sleep 10
done

curl -s "${DATAROBOT_ENDPOINT}/artifacts/${ARTIFACT_ID}/builds/${BUILD_ID}" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" | jq '{id, status, imageApplied}' 
dr artifact build create ${ARTIFACT_ID} --wait 

--wait polls until the build reaches a terminal status and prints the tail of the log if it fails. It stops at COMPLETED, which is before the artifact is updated to use the image, so confirm that below before deploying.

Wait for COMPLETED and for imageApplied to be true. BUILT means the image exists but the build has not finished, and COMPLETED means the image exists but the artifact is updated to use it a moment later. See Build statuses.

If the build reports FAILED, read the log:

curl -s "${DATAROBOT_ENDPOINT}/otel/artifact/${ARTIFACT_ID}/logs/?searchKeys=external_build_id&searchValues=${BUILD_ID}" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" | jq -r '.data[] | "\(.timestamp) \(.level) \(.message)"' 
dr artifact build logs ${ARTIFACT_ID} ${BUILD_ID} 

Diagnose a failed build maps the common failures to fixes.

Confirm the image is attached

Check that the build populated the container's imageUri.

curl -s "${DATAROBOT_ENDPOINT}/artifacts/${ARTIFACT_ID}" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" \
  | jq '.spec.containerGroups[0].containers[0] | {imageUri, build}' 

imageUri is now populated, and its tag is the build ID.

Deploy the Workload

Deploy by artifactId. An artifact that builds from source cannot be supplied inline.

curl -s -X POST "${DATAROBOT_ENDPOINT}/workloads" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-api",
    "artifactId": "'${ARTIFACT_ID}'",
    "runtime": {
      "containerGroups": [{
        "name": "default",
        "replicaCount": 1,
        "containers": [{
          "name": "primary",
          "resourceAllocation": {"cpu": 1, "memory": "512MB"}
        }]
      }]
    }
  }' | tee /tmp/workload.json

export WORKLOAD_ID=$(jq -r '.id' /tmp/workload.json) 
workload.yaml
name: my-api
artifactId: <artifact-id>
runtime:
  containerGroups:
    - name: default
      replicaCount: 1
      containers:
        - name: primary
          resourceAllocation:
            cpu: 1
            memory: "512MB" 
dr workload create --spec-file workload.yaml --output-format json | tee /tmp/workload.json
export WORKLOAD_ID=$(jq -r '.id' /tmp/workload.json) 

Wait for the Workload to reach running:

until [ "$(curl -s "${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" | jq -r '.status')" = "running" ]; do
  sleep 10
done 

Workload statuses are lowercase

Build statuses are uppercase (COMPLETED); Workload and artifact statuses are lowercase (running, draft). Match the exact casing in scripts.

Invoke the Workload

Confirm the deployed container serves live traffic by invoking its endpoint directly.

ENDPOINT=$(curl -s "${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" | jq -r '.endpoint')

curl -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" "${ENDPOINT}/version" 

Change the code and rebuild

Edit the app, then sync and rebuild. There is no need to cancel the previous build—triggering a new one supersedes it.

Re-upload the source and patch the codeRef as in Upload the code, then:

curl -s -X POST "${DATAROBOT_ENDPOINT}/artifacts/${ARTIFACT_ID}/builds" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" 
dr artifact code sync
dr artifact build create ${ARTIFACT_ID} --wait 

While the new build runs, the Workload keeps serving the previous image. To roll the new image out to the running Workload, see Replace and roll out.

Changed your dependencies?

Re-run uv lock before syncing. Builds install the lockfile as-is, so an edited pyproject.toml with a stale uv.lock builds the old dependency set without any error.

Lock for production

Locking makes the artifact immutable so it can back production Workloads. It requires a completed build—the platform verifies that imageUri came from one.

curl -s -X PATCH "${DATAROBOT_ENDPOINT}/artifacts/${ARTIFACT_ID}" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"status": "locked"}' 
dr artifact lock ${ARTIFACT_ID} 

Locking is irreversible, and a locked artifact accepts no further builds. To keep iterating, create a new draft artifact. See Artifact lifecycle.

削除

Stop and delete the Workload, then delete the artifact if you did not lock it in the previous step.

curl -s -X POST "${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}/stop" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}"

curl -s -X DELETE "${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}"

# Only if you did not lock it: locked artifacts cannot be deleted
curl -s -X DELETE "${DATAROBOT_ENDPOINT}/artifacts/${ARTIFACT_ID}" \
  -H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" 

If you used the CLI, remove the local link with rm -rf .datarobot/workload/.

次のステップ

This tutorial covered one full pass through the build-to-deploy flow; the following resources go deeper on configuration, build management, and post-deployment operations.

Goal Go here
Understand codeRef, base images, and Dockerfile options Build an image from source code
Manage builds in detail Trigger and manage builds
Roll a new image out to a running Workload Replace and roll out
Instrument the service with OpenTelemetry ワークロードをOpenTelemetryで計装する