Artifact and workload spec reference
Field-by-field reference for the spec files dr artifact create --spec-file and dr workload create --spec-file read, plus one end-to-end walkthrough that uses both.
How to read this document
- Two specs, two commands. The artifact spec says what runs: images, ports, environment variables. The workload spec says how much of it runs and where: replicas, CPU, memory, placement. See
dr artifactanddr workloadfor the commands themselves. - JSON is sent byte-for-byte; YAML is converted to JSON first. Quote any YAML value that must stay a string (for example
"0644"), or it arrives as a number. - Field-level validation is the server's. A rejected spec comes back as a
422naming the offending JSON path. The handful of checks the CLI makes before the request are listed under What the CLI checks before sending.
Artifact spec
Top-level fields
| Field | Required | Notes |
|---|---|---|
name |
yes | The artifact's display name. Names are not unique; what groups successive versions of one thing is the artifact repository, which the platform assigns. |
type |
no | service (the default) or agent. |
spec.containerGroups[] |
yes | At least one group, each with at least one container. |
type sits beside the spec, not inside it. The platform pops any type it finds under spec and re-derives the kind from the artifact's own type, so a type written one level down is discarded without a word: an agent silently becomes a service, and agent-only fields such as a2aEnabled are then rejected as unknown fields on the service variant. An artifact repository takes its kind from whichever artifact opened it, so a later version cannot change it.
name: my-agent
type: agent # beside spec, never under it
spec:
containerGroups:
- name: default
containers: [...]
Container groups and containers
spec:
containerGroups:
- name: default
containers:
- name: primary
primary: true
port: 8080
imageUri: nginx:latest
| Field | Required | Notes |
|---|---|---|
containerGroups[].name |
no, but name it | The workload spec addresses a group by this name. An unnamed group cannot be given a replica count or a resource allocation later. |
containers[].name |
no, but name it | Same reason: runtime overrides reference a container by name. |
containers[].primary |
one per spec | Marks the container that serves traffic. Its port is what the endpoint URL reaches. |
containers[].port |
for the primary | The port the container listens on. Must be 1024 or above. |
containers[].imageUri |
one of the two | An existing image. Nothing is built. |
containers[].imageBuildConfig |
one of the two | Build the image from your code. See below. |
containers[].environmentVars |
no | See Environment variables and secrets. |
containers[].readinessProbe |
no | {path, port}: when the platform should treat the container as ready to serve. |
The specs the CLI generates itself use default for the group and primary for the container. Following that convention keeps the workload half short.
Where the image comes from
Each container carries either imageUri or imageBuildConfig, never both.
Prebuilt image. Nothing to build, nothing to sync:
Built from your own Dockerfile. The build reads ./Dockerfile out of the code you pushed with dr artifact code sync:
Generated from an execution environment. The platform writes the Dockerfile for you from a base environment, and you supply the command that starts your app:
containers:
- name: primary
primary: true
port: 8080
imageBuildConfig:
dockerfile:
source: generated
executionEnvironmentId: 68b0c1d2e3f4a5b6c7d8e9f0
executionEnvironmentVersionId: 68b0c1d2e3f4a5b6c7d8e9f1
entrypoint: ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
The platform works out the runtime from the project's own files: pyproject.toml with uv.lock, or package.json with package-lock.json. A pyproject.toml without uv.lock is accepted: the deploy's sync generates the lock before the upload if uv is installed where the deploy runs, so commit it. A project with neither file pair, or a package.json without its lock, cannot be built this way, and dr workload config refuses the mode up front rather than letting the build fail after the code is synced; dr workload up refuses the same cases before it creates anything. Binding to a workload already built this way from a directory with none of the usual project files keeps its build: dr workload up pulls the workload's code into that directory first. A directory with project files of its own is uploaded as it is, so it has to be buildable. The environment also has to match the project: a Node project on a Python base image fails at build time, so dr workload config refuses the pair when the environment's language and the project's files disagree; an environment with no language label is accepted. executionEnvironmentId and executionEnvironmentVersionId are only valid when source is generated. No CLI command lists execution environments yet; take the ids from the DataRobot UI or from GET /api/v2/executionEnvironments/.
imageBuildConfig.codeRef names the catalog version the build compiles, and dr artifact code sync writes it for you:
imageBuildConfig:
codeRef:
datarobot:
catalogId: 68b0c1d2e3f4a5b6c7d8e9f0
catalogVersionId: 68b0c1d2e3f4a5b6c7d8e9f1
dockerfile:
source: provided
Write it by hand only to point a new artifact at a catalog version that already exists.
Environment variables and secrets
environmentVars is a list on the container. A plain setting carries its value directly:
environmentVars:
- name: LOG_LEVEL
value: info
- name: PORT
value: "8080" # quoted: YAML would otherwise send a number
A secret is stored as a credential and referenced by id, so the value never appears in the spec, in your repository, or in dr artifact get:
environmentVars:
- name: OPENAI_API_KEY
source: dr-credential
drCredentialId: 68b0c1d2e3f4a5b6c7d8e9f0
key: apiToken
key selects which field of the credential to use, not the variable's own name: one stored credential can bundle several fields, as an S3 credential bundles awsAccessKeyId and awsSecretAccessKey. A credential holding a single secret is stored as an API token, whose field is apiToken.
There is no dr credential command yet. Create one in the DataRobot UI, or with the API, using the endpoint and token dr auth export already puts in your shell:
curl -X POST "$DATAROBOT_ENDPOINT/credentials/" \
-H "Authorization: Bearer $DATAROBOT_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "my-app-openai", "credentialType": "api_token", "apiToken": "<the secret>"}'
The id in the response is what goes in drCredentialId. A name already in use comes back as a 409, and reusing that credential would deploy whatever value it already holds, so pick a fresh name rather than assuming it is yours.
A container reads its environment once, at startup. Rotating a secret (PATCH /api/v2/credentials/{id}/ with a new apiToken) therefore changes nothing for a workload that is already running until its containers restart: dr workload stop <id> then dr workload start <id>.
Workload spec
Top-level fields
| Field | Required | Notes |
|---|---|---|
name |
yes | The workload's display name. |
artifactId |
one of the two | An existing artifact, normally a locked one. |
artifact |
one of the two | An inline artifact definition, created and deployed in the same call. Takes the same fields as the artifact spec above. |
importance |
no | low (the default), moderate, high or critical. |
useCaseId |
no | Links the workload to a Use Case. On its own an organizational link; it is also what Enclave placement is governed by. See Placement. |
runtime |
no | Replicas, resources and placement. Without it the platform applies its own defaults. |
Runtime
name: my-app
artifactId: 68b0c1d2e3f4a5b6c7d8e9f0
runtime:
containerGroups:
- name: default # must match a group in the artifact; the platform assumes default when omitted
replicaCount: 1
containers:
- name: primary # must match a container in that group
resourceAllocation:
cpu: 1
memory: 512MB
runtime addresses the artifact's containers by name, so containerGroups[].name must name a group in the artifact and containers[].name a container inside it. A name that matches nothing has nothing to size, which is why the artifact half is worth naming.
| Field | Notes |
|---|---|
replicaCount |
Fixed scale. Mutually exclusive with autoscaling.enabled: true on the same group. |
autoscaling |
Dynamic scale. See below. |
resourceAllocation.cpu |
CPU cores. Fractional values are allowed (0.5). |
resourceAllocation.memory |
A byte count or a 1000-based unit: B, KB, MB, GB, TB. Binary units such as Gi are read as their decimal namesakes rather than converted, so write the decimal unit you actually mean. The CLI reads memory back the same way: 2GB for 2,000,000,000 bytes, and the exact byte count ("2147483648") when no decimal unit divides it. Sizes elsewhere in the CLI, such as code uploads and checkouts, are binary and labelled KiB/MiB/GiB. |
Fixed replicas or autoscaling, not both
Replica bounds live on autoscaling (minReplicaCount / maxReplicaCount); each policy needs only scalingMetric and target. Omit replicaCount when autoscaling is enabled. A full copy-paste spec is in workload-autoscaling.yaml.
runtime:
containerGroups:
- name: default
autoscaling:
enabled: true
minReplicaCount: 1
maxReplicaCount: 10
policies:
- scalingMetric: cpuAverageUtilization
target: 80
containers:
- name: primary
resourceAllocation:
cpu: 1
memory: 512MB
[!NOTE] The Workload API still accepts the legacy per-policy
minCount/maxCountfields on input and hoists them automatically, but responses always useminReplicaCount/maxReplicaCountonautoscaling. Prefer the new shape in new specs.
Placement
Enclave placement is opt-in, and it is governed by a Use Case: an administrator grants Enclaves to a Use Case, and a workload that names that Use Case in useCaseId can be placed on them. Without useCaseId a workload runs outside any Enclave. useCaseId on its own changes nothing about placement when the Use Case has no Enclaves; when it has some, a spec that names the Use Case must also set a policy, or the server refuses it with ENCLAVE_TARGETING_REQUIRED.
| Field | Notes |
|---|---|
useCaseId |
Top-level. The Use Case whose Enclaves the workload may run on. dr workload create --use-case-id <id> writes it. |
runtime.enclaveSelectionPolicy |
availability: DataRobot picks among the Enclaves granted to the Use Case. manual: run on the Enclaves listed in runtime.enclaves. |
runtime.enclaves |
A list of Enclave names, read with the manual policy. Rather than writing it by hand, pass dr workload create --enclave <name>, which sets the policy and a one-element list. |
name: my-app
artifactId: 68b0c1d2e3f4a5b6c7d8e9f0
useCaseId: 68b0aa11bb22cc33dd44ee55
runtime:
enclaveSelectionPolicy: availability
containerGroups:
- name: default
replicaCount: 1
containers:
- name: primary
resourceAllocation:
cpu: 1
memory: 512MB
The server refuses a spec that asks for placement without a Use Case (MISSING_USE_CASE), and one whose Use Case has Enclaves but sets no policy (ENCLAVE_TARGETING_REQUIRED). The create flags refuse to override a spec that already sets the fields they write. The flag-level story, including the breaking change to --enclave, is in dr workload create.
Deploy a service from source, end to end
This is the whole path for a project whose image DataRobot builds, from an empty artifact to a URL that answers. Every step is a command you can paste.
# 1. Register the draft artifact. spec.yaml is the artifact spec above,
# with imageBuildConfig.dockerfile.source: provided.
dr artifact create --spec-file spec.yaml # prints the new artifact id
# 2. Link this directory to it. Writes .datarobot/workload/ and a starter
# .drignore; commit the .drignore.
dr artifact code init <artifact-id>
# 3. Push the code the image is built from.
dr artifact code sync
# 4. Build the image and wait for it. On success the summary line ends with
# the image it produced; on failure it carries the tail of the build log.
dr artifact build create --wait # Build <id>: COMPLETED in 94s (image: …)
# 5. Confirm the build landed before locking: locking does not reliably
# check this for you.
dr artifact build list <artifact-id> # the newest build should be COMPLETED
# 6. Freeze the artifact. One-way: a locked artifact cannot be edited,
# unlocked or deleted.
dr artifact lock <artifact-id>
# 7. Deploy it. workload.yaml is the workload spec above, with
# artifactId set to the artifact you just locked.
dr workload create --spec-file workload.yaml # prints the new workload id
# 8. Startup is asynchronous. Poll until it says running.
dr workload status <workload-id>
# 9. Call it. The endpoint URL ends in a slash, so append without one.
curl "$(dr workload endpoint <workload-id>)health"
To change the code afterwards, the artifact you locked stays as it is: create a new artifact, sync, build and lock it, then deploy it with a new dr workload create. Locked artifacts are kept rather than deleted, so each version goes on naming exactly what it ran. These commands have no in-place replacement, so the new deploy is a new workload with a new id and a new endpoint URL, and anything calling the old URL has to be repointed at it.
What the CLI checks before sending
Everything else is the server's, and comes back as a 422 naming the JSON path.
dr artifact create --spec-file:
nameis present and not empty.spec.containerGroupshas at least one entry.- Every container group has at least one container.
dr workload create --spec-file:
nameis present and not empty.- Exactly one of
artifactIdorartifactis set. - Per container group,
autoscalingis an object rather than a boolean, andreplicaCountdoes not sit besideautoscaling.enabled: true.
Unknown fields are deliberately not rejected locally: the server's 422 names the offending path, which is more useful than a local complaint about a key the platform may have added since this CLI was built.
See also
dr artifact: register, sync, build and lock the artifact.dr workload: deploy and operate the workload.- workload-autoscaling.yaml: a complete autoscaling spec.
- Authentication: where
DATAROBOT_ENDPOINTandDATAROBOT_API_TOKENcome from.