# Build an image from source code

> Build an image from source code - Point an artifact at your source code with codeRef, choose a
> provided or generated Dockerfile, and let the platform build the container image.

This Markdown file sits beside the HTML page at the same path (with a `.md` suffix). It summarizes the topic and lists links for tools and LLM context.

Companion generated at `2026-09-30T19:40:40.961152+00:00` (UTC).

## Primary page

- [Build an image from source code](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md): Full documentation for this topic (Markdown sidecar).

## Sections on this page

- [Source-to-image flow](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#source-to-image-flow): In-page section heading.
- [Select the source code withcodeRef](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#select-the-source-code-with-coderef): In-page section heading.
- [Location withinimageBuildConfig](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#location-within-imagebuildconfig): In-page section heading.
- [codeReffields](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#coderef-fields): In-page section heading.
- [Upload source and set thecodeRef](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#upload-source-and-set-the-coderef): In-page section heading.
- [Update the source on an existing artifact](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#update-source-on-an-existing-artifact): In-page section heading.
- [WhatcodeRefdoes not support](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#what-coderef-does-not-support): In-page section heading.
- [Choose how the Dockerfile is obtained](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#choose-dockerfile-source): In-page section heading.
- [Option 1: Use a provided Dockerfile](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#provided-dockerfile): In-page section heading.
- [Option 2: Use a generated Dockerfile](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#generated-dockerfile): In-page section heading.
- [Supported project types for generated Dockerfiles](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#supported-project-types): In-page section heading.
- [Select a base image](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#select-a-base-image): In-page section heading.
- [Known limitations of custom base images](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#base-image-limitations): In-page section heading.
- [Generated image layout](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#generated-image-layout): In-page section heading.
- [Build inputs the API does not expose](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#unsupported-build-inputs): In-page section heading.
- [Next steps](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#next-steps): In-page section heading.

## Related documentation

- [Workload API](https://docs.datarobot.com/en/docs/workload-api/index.html.md): Linked from this page.
- [Build artifacts](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/index.html.md): Linked from this page.
- [Build artifacts from source code](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/index.html.md): Linked from this page.
- [Trigger and manage builds](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-operations.html.md): Linked from this page.
- [Tutorial: Build a Workload from source](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/tutorial-build-from-code.html.md): Linked from this page.
- [Manage artifacts with the CLI](https://docs.datarobot.com/en/docs/workload-api/workload-interfaces/workload-cli/artifact-cli.html.md#dr-artifact-code-sync): Linked from this page.
- [container requirements](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/artifacts-concepts.html.md#container-requirements): Linked from this page.
- [execution environment](https://docs.datarobot.com/en/docs/workbench/nxt-registry/nxt-environment-workshop/index.html.md): Linked from this page.
- [Build logs](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-logs.html.md): Linked from this page.

## Documentation content

> [!NOTE] Premium feature
> The Workload API is a premium feature. Contact your DataRobot representative or administrator for information on enabling the feature.

Most artifacts reference a container image you already built and pushed ( `imageUri`). The alternative is to hand DataRobot your source code and let the platform build the image for you—no local `docker build`, no registry push, no credentials to manage. This is often called code to Workload.

Set `imageBuildConfig` on a container instead of `imageUri`, point it at your uploaded source with a `codeRef`, and trigger a build. When the build completes, the platform writes `imageUri` back onto the container for you.

For triggering builds and tracking their status, see [Trigger and manage builds](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-operations.html.md). For a worked example, see [Tutorial: Build a Workload from source](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/tutorial-build-from-code.html.md).

## Source-to-image flow

The following diagram traces a build from your uploaded source to a running Workload.

```
flowchart LR
    S["<b>Source directory</b><br/><i>your project</i>"] --> C["<b>Catalog version</b><br/><i>uploaded snapshot</i>"]
    C --> R["<b>codeRef</b><br/><i>pointer on the artifact</i>"]
    R --> B["<b>Build</b><br/><i>image built and pushed</i>"]
    B --> I["<b>imageUri</b><br/><i>written to the container</i>"]
    I --> W["<b>Workload</b><br/><i>running endpoint</i>"]
```

Each step is separately addressable: re-upload source without rebuilding, rebuild without redeploying, or deploy the same built image to more than one Workload.

This process requires a draft artifact of type `service`, `agent`, or `mcp`. Builds run only against `draft` artifacts of those types; locked artifacts are immutable, and other artifact types cannot use `imageBuildConfig` —a `nim` artifact, for example, always references a pre-built NVIDIA container image and is never eligible for a source build.

## Select the source code with codeRef

A `codeRef` identifies a specific version of your source code that has already been uploaded to DataRobot. It is a pointer into the DataRobot catalog, produced when you upload a snapshot of your project directory.

> [!NOTE] AcodeRefis not a Git reference
> Despite the name, `codeRef` does not point at a repository. There is no URL, branch, tag, or commit to select—see [WhatcodeRefdoes not support](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#what-coderef-does-not-support). The unit of selection is an uploaded catalog version, and you choose which version by choosing its ID.

### Location within imageBuildConfig

`codeRef` is a field on the container, nested under `imageBuildConfig`:

```
spec.containerGroups[].containers[].imageBuildConfig.codeRef
```

### codeRef fields

A `codeRef` has the following shape:

```
"codeRef": {
  "type": "datarobot",
  "provider": "datarobot",
  "datarobot": {
    "catalogId": "68f0c1a2b3d4e5f607182930",
    "catalogVersionId": "68f0c1a2b3d4e5f607182931"
  }
}
```

The following table describes each field.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| type | "datarobot" | No | Source kind. datarobot is the only accepted value; it defaults to datarobot when omitted. |
| provider | "datarobot" | No | Source provider. datarobot is the only accepted value; it defaults to datarobot when omitted. |
| datarobot | object | Yes | The catalog coordinates. |
| datarobot.catalogId | string | Yes | The catalog item holding your source code. Must be a 24-character hexadecimal ID. |
| datarobot.catalogVersionId | string | Yes | The specific version of that catalog item to build. Must be a 24-character hexadecimal ID. |

Because `catalogId` and `catalogVersionId` are used to construct DataRobot API paths, both are validated strictly. Surrounding whitespace is trimmed, and the following errors are returned for malformed values:

| Error | Cause |
| --- | --- |
| catalog_id and catalog_version_id must be non-empty when code_ref is set | One of the two IDs is missing or blank. |
| catalog_id and catalog_version_id must be 24-character hexadecimal identifiers | One of the two IDs is not a 24-character hex string. |

Both IDs are also checked for existence when you create or update the artifact: the platform reads the catalog version as you, so a nonexistent ID—or one you cannot access—is rejected at that point rather than at build time.

### Upload source and set the codeRef

Upload source and register the resulting `codeRef` through either interface: the CLI for a project directory already linked via `dr artifact code init`, or the REST API when scripting the upload directly.

**CLI (recommended):**
The CLI handles the upload and the `codeRef` update together. Link the directory once, then sync whenever you change the code:

```
# Link this project directory to an existing draft artifact
dr artifact code init ${ARTIFACT_ID}

# Upload the current contents and update the artifact's codeRef
dr artifact code sync
```

`sync` creates a new catalog version on every run and repoints the artifact's `codeRef` at it, so the artifact always references the snapshot you last pushed. Preview what would be uploaded with `dr artifact code sync --dry-run`. See [Manage artifacts with the CLI](https://docs.datarobot.com/en/docs/workload-api/workload-interfaces/workload-cli/artifact-cli.html.md#dr-artifact-code-sync).

> [!TIP] Exclude files from the upload
> `dr artifact code init` drops a starter `.drignore` at the project root, in gitignore syntax, listing what `sync` leaves out. Edit it and commit it. An uploaded `.venv/` or `node_modules/` bloats the catalog version and can interfere with runtime detection for generated Dockerfiles.
> 
> Projects linked by an older CLI have a `.wapiignore` instead. It is still honored when no `.drignore` sits beside it, but the name is deprecated—rename it when convenient. If both exist, only `.drignore` applies.

**REST:**
Upload the source, wait for the upload to finish, then patch the artifact.

```
# 1. Upload a zip of your project
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 response is `202` with `catalogId`, `catalogVersionId`, and a `statusId`. The upload finishes asynchronously; poll its status by id until it redirects—this endpoint signals completion with a `303`, not a status field in a JSON 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 attach the result to 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": [{
        "containers": [{
          "name": "primary",
          "primary": true,
          "port": 8080,
          "imageBuildConfig": {
            "codeRef": {
              "datarobot": {
                "catalogId": "'${CATALOG_ID}'",
                "catalogVersionId": "'${CATALOG_VERSION_ID}'"
              }
            }
          }
        }]
      }]
    }
  }'
```


### Update the source on an existing artifact

Point the `codeRef` at a newer `catalogVersionId` and trigger a new build. Two behaviors are worth knowing:

- The previousimageUriis retained. Changing the codeRef does not clear the image the artifact already has, so the last successfully built image stays deployable until a new build completes. It is stale, not gone.
- The build pointer is cleared. The build block on the container is dropped when the codeRef changes, because the recorded build no longer describes the current source.

> [!WARNING] Resend the whole container, not justcodeRef
> A `PATCH` that names a container replaces that container's full definition—it does not merge in only the fields you send. Patching `imageBuildConfig.codeRef` alone drops any sibling fields the request omits, silently resetting `dockerfile` to its `provided` / `./Dockerfile` default and clearing fields like `readinessProbe`. Always resend the container's complete current definition, with only `codeRef` changed.

> [!NOTE] imageUriis managed by the platform
> On a container that uses `imageBuildConfig`, you cannot set `imageUri` yourself. Sending a different value returns `422`: `Container '<name>': imageUri cannot be set on a container with imageBuildConfig; it is populated by the platform when the image build completes.` Echoing back the value the platform already stored—as happens in a read-modify-write round trip—is accepted.

### What codeRef does not support

The following capabilities are common in build systems but are not supported by `codeRef`; each has a documented workaround.

| Not supported | What to do instead |
| --- | --- |
| Git repository URLs, branches, tags, or commits | Upload a snapshot of your working tree. Use your own CI to sync a checkout if you need Git as the source of truth. |
| Credentials for private repositories | Not applicable—access is governed by your DataRobot API token and your permissions on the catalog item. |
| Container registry references as a build source | Use imageUri to reference a pre-built image directly. |
| Selecting a subdirectory of the upload as the build context | The build context is always the root of the catalog version. To build from a Dockerfile in a subdirectory, set dockerfile.path—but the context stays at the root. |
| More than one container building from source | Only one container per artifact may define imageBuildConfig. Supply imageUri for sidecars. |

## Choose how the Dockerfile is obtained

`imageBuildConfig.dockerfile` is a discriminated union on the `source` field. Pick `provided` when you want full control of the image, and `generated` when you would rather not write a Dockerfile at all.

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| codeRef | object or null | null | Source code to build. Optional at create time; required before you build or lock. |
| dockerfile | ProvidedDockerfile or GeneratedDockerfile | {"source": "provided", "path": "./Dockerfile"} | How the Dockerfile is obtained. |

### Option 1: Use a provided Dockerfile

Set `dockerfile.source` to `provided` to build from a Dockerfile that's already part of your uploaded source:

```
"imageBuildConfig": {
  "dockerfile": {
    "source": "provided",
    "path": "./Dockerfile"
  }
}
```

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| source | "provided" | "provided" | Selects this variant. |
| path | string | "./Dockerfile" | Path to the Dockerfile, relative to the root of the uploaded source. Maximum 2048 characters. |

The path is normalized before lookup, so `./docker/Dockerfile.prod` and `docker/Dockerfile.prod` are equivalent. Matching is on the full relative path, not the filename, so a Dockerfile in a subdirectory is found only if `path` names that subdirectory.

> [!NOTE] The build context is always the upload root
> Setting `path` to `services/api/Dockerfile` selects that Dockerfile, but the build context remains the root of the catalog version. Write `COPY` instructions relative to the root, for example `COPY services/api/ /app/`.

If the file is not present in the catalog version when the build runs, the trigger fails with `422`:

```
Dockerfile '<path>' not found in catalog <catalogId> version <catalogVersionId>.
Either upload a Dockerfile or use a generated dockerfile configuration.
```

Your image must still meet the platform's [container requirements](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/artifacts-concepts.html.md#container-requirements) — `linux/amd64`, non-root, a port between 1024 and 65535, an HTTP server, a readiness endpoint, and graceful SIGTERM handling.

### Option 2: Use a generated Dockerfile

Set `dockerfile.source` to `generated` to have the platform write the Dockerfile for you:

```
"imageBuildConfig": {
  "dockerfile": {
    "source": "generated",
    "executionEnvironmentId": "67ab469cecdca772287de644",
    "executionEnvironmentVersionId": "6a216fca4244d434c38a2bd5",
    "entrypoint": ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
  }
}
```

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| source | "generated" | Yes | Selects this variant. |
| executionEnvironmentId | string | Yes | Execution environment used to resolve the base image. 24-character hexadecimal ID. |
| executionEnvironmentVersionId | string | Yes | Execution environment version that pins the exact base image tag. 24-character hexadecimal ID. |
| entrypoint | array of strings | Yes | The command baked into the generated image as CMD. At least one element. |

The platform inspects your uploaded source, detects the project type, and writes a Dockerfile that installs your locked dependencies on top of the base image from the execution environment version.

> [!NOTE] entrypointhere is a build-time setting
> `imageBuildConfig.dockerfile.entrypoint` becomes the `CMD` of the built image. It is unrelated to `container.entrypoint`, which overrides the command at runtime.

## Supported project types for generated Dockerfiles

Detection is by filename at the root of the uploaded source. A lockfile is mandatory in both cases—builds install exactly what the lockfile pins, so that a build is reproducible.

| Language | Package manager | Required files at the upload root | Install command |
| --- | --- | --- | --- |
| Python | uv | pyproject.toml and uv.lock | uv sync --frozen --no-dev --no-install-project |
| Node.js | npm | package.json and package-lock.json | npm ci --omit=dev |

Detection checks for the Python files first. If a project's root has both a Python and a Node config—for example, a full-stack repo with a `pyproject.toml` / `uv.lock` backend and a `package.json` / `package-lock.json` frontend—the Python path always wins and the Node config is ignored, with no warning. Use a [provided Dockerfile](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#provided-dockerfile) if you need to build the Node side of a project like this.

Not supported for generated Dockerfiles: `requirements.txt` and pip, Poetry, Yarn, pnpm, and non-Python/Node languages. Use a [provided Dockerfile](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#provided-dockerfile) for anything outside the table.

> [!TIP] The CLI generates a missinguv.lockfor you
> For Python projects, `dr artifact code sync` runs your local `uv lock` when `pyproject.toml` has no lockfile beside it, and uploads the result. Your uv configuration, private indexes, and credentials apply. Commit the generated file. An existing `uv.lock` is never modified.

> [!TIP] ConflictingUV_FROZENorUV_LOCKEDbase-image settings are cleared automatically
> Some DataRobot-published execution environments (for example, GenAI Agents images) set `UV_FROZEN=1` by default. Because `uv` rejects `--frozen` and `--locked` together, the generated Dockerfile unsets both `UV_FROZEN` and `UV_LOCKED` before running `uv sync --frozen`, so the build succeeds regardless of what the base image sets.

> [!WARNING] Node builds skip root lifecycle scripts
> The generated Node Dockerfile removes `preinstall`, `install`, `postinstall`, `prepare`, and `prepublishOnly` from the root `package.json` before installing dependencies, because the full source tree isn't present yet at that point in the build. A project that relies on one of these to build output, generate code, or run a tool like `husky install` needs to produce that output before uploading, or use a [provided Dockerfile](https://docs.datarobot.com/en/docs/workload-api/build-artifacts/build-from-code/build-from-code.html.md#provided-dockerfile) that runs the step explicitly.

If detection fails, the build trigger returns `422` with a message that names the fix:

| Message | Fix |
| --- | --- |
| Found pyproject.toml but no uv.lock. Run uv lock and re-sync your code (newer dr CLI versions generate it automatically during dr artifact code sync). | Run uv lock, then re-sync. The CLI generates the lockfile for you during dr artifact code sync—this error means the upload reached the platform without one. Check that .drignore does not exclude uv.lock. |
| Found package.json but no package-lock.json. Run npm install to generate the lockfile and re-sync your code. | Run npm install, then re-sync. |
| Found package.json with a yarn/pnpm lockfile, but only npm (package-lock.json) is currently supported. | Generate a package-lock.json, or switch to a provided Dockerfile. |
| Unable to detect project runtime. Ensure the project contains a recognized config file (for example, pyproject.toml + uv.lock, or package.json + package-lock.json). | Confirm the config and lockfile are at the root of the upload, not in a subdirectory. |

## Select a base image

For generated Dockerfiles, the base image comes from the [execution environment](https://docs.datarobot.com/en/docs/workbench/nxt-registry/nxt-environment-workshop/index.html.md) version you name—the same mechanism used for custom models, jobs, applications, and notebooks. Any execution environment version works—DataRobot-published environments and your own custom environments alike—as long as the version resolves to an image.

The execution environment version must have a `sourceDockerImageUri` or an `imageId`. If it has neither, the build trigger fails with `422`:

```
Execution environment version has neither sourceDockerImageUri nor imageId.
Cannot determine the base image for the generated Dockerfile.
```

### Known limitations of custom base images

The generated Dockerfile installs dependencies and nothing else—there is no `apt-get` step and no OS-level branching. A minimal base image can therefore build a package set that a DataRobot-published environment handles without trouble.

| Symptom | Cause | Fix |
| --- | --- | --- |
| A dependency fails to compile during install | The package ships only a source distribution and needs a C/C++ toolchain, which slim base images omit. | Use a DataRobot-published execution environment, or a base image that includes build tools. |
| The container starts but crashes on import with a missing .so | A wheel needs an OS shared library the base image lacks (for example, libgomp1 for onnxruntime). | Use a base image that provides the library, or switch to a provided Dockerfile that installs it. |
| Many dependencies fail to install on an Alpine base | Most Python wheels on PyPI target glibc, not musl. | Use a glibc-based image, such as a Debian-derived one. |

Builds also fetch a fallback `uv` binary from a public container registry, which requires outbound network access from the build environment.

## Generated image layout

Knowing the layout helps when you write `entrypoint` and debug a container that starts but does not serve:

| Property | Value |
| --- | --- |
| Working directory | /app |
| Source location | The whole upload is copied to /app |
| Python dependencies | Virtual environment at /app/.venv, with /app/.venv/bin first on PATH |
| Node dependencies | /app/node_modules, with /app/node_modules/.bin first on PATH |
| User | Inherited from the base image, typically a non-root user |
| Dev dependencies | Excluded (--no-dev / --omit=dev) |
| Command | CMD set to your entrypoint; any base image entrypoint is cleared |

Dependencies are installed from the lockfile as-is. Editing `pyproject.toml` without re-running `uv lock` does not fail the build—it silently installs the old lock. Re-run `uv lock` and re-sync whenever you change dependencies.

## Build inputs the API does not expose

The following inputs are common in a Docker build but are either unsupported or fully managed by the platform, with no user-facing control.

| Not available | Notes |
| --- | --- |
| Docker build arguments | There is no buildArgs field. Bake values into the Dockerfile or supply them at runtime. |
| Build-time environment variables | environmentVars on the container applies at runtime only; it is not visible to the build. |
| Build CPU, memory, or disk sizing | Build resources are managed by the platform. |
| Build cache controls | Layer caching is managed by the platform and cannot be turned off or primed per build. |
| Custom build timeouts | Not configurable. |

## Next steps

Configuring `codeRef` and the Dockerfile source is only the first step; the following resources cover triggering the build, diagnosing it, and the artifact lifecycle around it.

| Goal | Go here |
| --- | --- |
| Trigger a build and track it to completion | Trigger and manage builds |
| Read the output of a build, or work out why one failed | Build logs |
| Follow the whole flow end to end | Tutorial: Build a Workload from source |
| Understand artifacts, lifecycle, and repositories | Artifact concepts |
