Tutorial: Deploy a production-ready container¶
Deploy a containerized AI service with full governance: locked artifact, importance, sharing, and monitoring. Unlike a draft Workload, this Workload is long-lived and production-grade.
This tutorial deploys a FastAPI-based agent service that exposes:
- OpenAI-compatible
/chat/completions: connects to the DataRobot LLM Gateway—no separate LLM deployment required. - LangGraph
/agentendpoint: a ReAct agent with ArXiv search. /healthz,/readyz,/health: liveness, readiness, detailed status.
otkachnlp/fastapi-server-example is a publicly available third-party image (not published by DataRobot) used here for illustration.
Locked-artifact Workloads at a glance¶
| Property | Value |
|---|---|
| Lifetime | Indefinite. Persists until explicitly stopped or deleted. |
| Artifact mutability | Immutable once locked. |
importance |
Optional; defaults to low. Set explicitly for production (critical, high, moderate, or low). |
| Workloads per artifact | Unlimited. One locked artifact can back many Workloads. |
| Replace | Supported. Replace locked with locked only. |
Connect to DataRobot¶
To connect to DataRobot, you need the following:
- DataRobot API endpoint and token,
DATAROBOT_ENDPOINTandDATAROBOT_API_TOKEN. - A terminal with
curl. JSON request bodies and responses are handled in Python in this notebook so it runs without extra dependencies.
The connection details are set automatically inside this DataRobot Notebook. The next cell connects to DataRobot and exports DATAROBOT_ENDPOINT / DATAROBOT_API_TOKEN as shell environment variables so the curl cells that follow can use them.
import os
import datarobot as dr
client = dr.Client()
os.environ.setdefault("DATAROBOT_ENDPOINT", client.endpoint)
os.environ.setdefault("DATAROBOT_API_TOKEN", client.token)
print("Connected:", os.environ["DATAROBOT_ENDPOINT"])
Configure variables¶
Set the values that vary per user before running the rest of the notebook.
| Variable | Purpose |
|---|---|
MODEL |
Model name passed to the container's MODEL env var and sent in the chat-completions request. The container routes to the DataRobot LLM Gateway using DATAROBOT_ENDPOINT and DATAROBOT_API_TOKEN. |
RECIPIENT_USER_ID |
User, group, or organization ID to share the Workload with. Leave blank to skip sharing. |
os.environ["MODEL"] = "azure/gpt-5-nano-2025-08-07"
os.environ["RECIPIENT_USER_ID"] = "" # Leave blank to skip sharing
print("MODEL:", os.environ["MODEL"])
Create the Workload¶
Artifacts are always created as draft, so you create the Workload first with importance set, then lock the artifact. Locking an artifact flips its backing Workload into the locked (production) lifecycle: indefinite lifetime, immutable spec, eligible for locked-to-locked replace.
The artifact's spec defines container topology (image, port, entrypoint, env vars, probes); replica count, CPU/memory, and autoscaling are deployment-time concerns and live in runtime.containerGroups[]. The container authenticates against the DataRobot LLM Gateway using a per-Workload API token the platform resolves and injects automatically via {"source": "api-key"}—no value is set in the spec, so the token never appears in GET /artifacts/{id} responses. See Environment variable types.
readinessProbe.path gates running. This tutorial points it at /healthz, which returns 2xx as soon as the FastAPI process is up. The container also exposes a /readyz endpoint that exercises the LLM connection, but keep the readiness probe pointed at /healthz—gating running on an external dependency causes the Workload's status to flap whenever that dependency has issues.
import requests
headers = {
"Authorization": f"Bearer {os.environ['DATAROBOT_API_TOKEN']}",
"Content-Type": "application/json",
}
payload = {
"name": "agent-service",
"importance": "high",
"artifact": {
"name": "agent-service-artifact",
"type": "service",
"spec": {
"containerGroups": [{
"name": "default",
"containers": [{
"name": "agent",
"imageUri": "otkachnlp/fastapi-server-example:latest",
"port": 8080,
"primary": True,
"entrypoint": ["python", "server.py"],
"environmentVars": [
{"name": "MODEL", "value": os.environ["MODEL"]},
{"source": "api-key"},
],
"readinessProbe": {"path": "/healthz", "port": 8080},
}],
}],
},
},
"runtime": {
"containerGroups": [{
"name": "default",
"replicaCount": 1,
"containers": [{
"name": "agent",
"resourceAllocation": {"cpu": 1, "memory": "512MB"},
}],
}],
},
}
resp = requests.post(
f"{os.environ['DATAROBOT_ENDPOINT']}/workloads", headers=headers, json=payload
)
resp.raise_for_status()
result = resp.json()
print(result)
Save the Workload ID and artifact ID from the response so later cells can reference them:
workload_id = result["id"]
artifact_id = result["artifactId"]
os.environ["WORKLOAD_ID"] = workload_id
os.environ["ARTIFACT_ID"] = artifact_id
print("Workload ID:", workload_id)
print("Artifact ID:", artifact_id)
Lock the artifact¶
Transition the artifact from draft to locked. Because this Workload is the only one backing the draft artifact, the Workload's lifecycle transitions to locked alongside it.
Locking is one-way: locked artifacts cannot return to draft.
%%bash
set -euo pipefail
curl -X PATCH "${DATAROBOT_ENDPOINT}/artifacts/${ARTIFACT_ID}" \
-H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"status": "locked"}'
Wait for running¶
Poll the Workload's status until it reaches running. Expected happy-path progression: submitted → provisioning → launching → running. errored is a terminal state, not a transient blip to poll through—if you see it, the next cell fetches the events endpoint automatically so you can inspect the cause rather than continuing to wait.
import time
import requests
headers = {"Authorization": f"Bearer {os.environ['DATAROBOT_API_TOKEN']}"}
for i in range(120):
resp = requests.get(
f"{os.environ['DATAROBOT_ENDPOINT']}/workloads/{workload_id}", headers=headers
)
resp.raise_for_status()
status = resp.json()["status"]
print("status:", status, flush=True)
if status == "running":
break
if status == "errored":
events = requests.get(
f"{os.environ['DATAROBOT_ENDPOINT']}/workloads/{workload_id}/events", headers=headers
)
print("Workload errored; see the events endpoint for detail:", events.json(), flush=True)
raise RuntimeError("Workload errored")
time.sleep(5)
else:
raise TimeoutError("Timed out waiting for running")
Invoke the service¶
Read the invoke URL from the Workload, then call your application routes against it. The server also contains an agent with a tool call to Arxiv; test it the same way with a POST to f"{endpoint}/agent" and a {"query": "..."} body.
import requests
headers = {"Authorization": f"Bearer {os.environ['DATAROBOT_API_TOKEN']}"}
workload = requests.get(
f"{os.environ['DATAROBOT_ENDPOINT']}/workloads/{workload_id}", headers=headers
).json()
endpoint = workload["endpoint"]
response = requests.post(
f"{endpoint}/chat/completions",
headers={**headers, "Content-Type": "application/json"},
json={"model": os.environ["MODEL"], "messages": [{"role": "user", "content": "Hello!"}]},
)
print(response.status_code)
print(response.json())
Govern the Workload¶
Now that it's a production Workload, wire up importance and sharing.
PATCH /workloads/{id}acceptsname,description, andimportance. For runtime changes (replicas, resources) usePATCH /workloads/{id}/settings, which triggers a rolling replacement.
%%bash
set -euo pipefail
# Raise importance to critical
curl -X PATCH "${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}" \
-H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"importance": "critical"}'
# Share with another user, group, or organization (skips if RECIPIENT_USER_ID is blank)
if [ -n "$RECIPIENT_USER_ID" ]; then
curl -X PATCH "${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}/sharedRoles" \
-H "Authorization: Bearer ${DATAROBOT_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"operation": "updateRoles",
"roles": [{"id": "'"${RECIPIENT_USER_ID}"'", "role": "USER", "shareRecipientType": "user"}]
}'
else
echo "RECIPIENT_USER_ID is blank; skipping share."
fi
Observe¶
Locked Workloads expose the full monitoring surface with the organization's configured telemetry retention (see Monitoring concepts: Retention summary).
| Capability | Endpoint |
|---|---|
| Service health, latency, error rate | GET /workloads/{id} (computed fields on the Workload) |
| Lifecycle events (audit trail) | GET /workloads/{id}/events |
| Aggregate request statistics | GET /workloads/{id}/stats |
| Per-metric time series | GET /workloads/{id}/stats/{metric_name} |
| Per-replica status | GET /workloads/{id}/protons/{proton_id}/statusDetails |
%%bash
set -euo pipefail
curl -s "${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}/events" \
-H "Authorization: Bearer ${DATAROBOT_API_TOKEN}"
echo
curl -s "${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}/stats" \
-H "Authorization: Bearer ${DATAROBOT_API_TOKEN}"
Summary¶
In this tutorial you:
- Created a Workload with
importanceset, backed by a draft artifact, then locked the artifact to move it onto the production (indefinite-lifetime) path. - Deployed a FastAPI agent service that calls the DataRobot LLM Gateway using an automatically injected, per-Workload API token.
- Invoked both the OpenAI-compatible
/chat/completionsroute and the LangGraph/agentroute. - Raised
importanceand shared the Workload with another user, group, or organization. - Read events and aggregate statistics from the monitoring surface.