# Images and Devcontainers

> Cordium documentation. Canonical page: <https://octelium.com/docs/cordium/latest/workspaces/image>.

The `spec.image` field defines the container image that is used as the *Workspace*'s root filesystem. The image can be pulled from a container registry, built from an inline or a remote Dockerfile, built from a dedicated git repository, or built from the *Workspace*'s own repository, including via its [Development Container](https://containers.dev/) spec.

If `spec.image` is not set at any configuration level, Cordium uses the image defined by the repository (i.e. via its [configuration file](https://octelium.com/docs/cordium/latest/workspaces/overview.md#repository-configuration-files) or its devcontainer spec) if the *Workspace* has a repository, and otherwise its default Ubuntu-based image, which includes `git`, `curl`, `sudo`, `zsh`, `vim`, `jq`, `make` and other common tools.

> **Note:**
>
> The image is pulled or built only on fresh runs (i.e. the first run of a persistent *Workspace* and every run of an ephemeral one). The subsequent runs of a persistent *Workspace* reuse its existing container, including every package that was installed in it. To change the image of a persistent *Workspace*, create a new one. For images that take long to build, use [pre-builds](https://octelium.com/docs/cordium/latest/workspaces/templates.md#pre-builds) or a pre-built registry image.

## Registry

Pull a pre-built image from a container registry:

```yaml
spec:
  image:
    registry:
      url: ubuntu:26.04
```

Any OCI image reference works (e.g. `python:3.13-slim`, `ghcr.io/myorg/dev-base:1.4.2` or `registry.example.com/team/image@sha256:...`). For private registries, set the username and the full name of a *Secret* of the *Workspace*'s *Space* that holds the password or token:

```yaml
spec:
  image:
    registry:
      url: registry.example.com/dev/base:latest
      authentication:
        username: robot-user
        password:
          fromSecret: registry-token.payments.cordium
```

The *Secret* is resolved by the *Cluster* and is never stored in the spec or visible in API responses.

## Dockerfile

Build the image from an inline Dockerfile:

```yaml
spec:
  image:
    dockerfile:
      inline: |
        FROM node:24-bookworm
        RUN apt-get update && apt-get install -y --no-install-recommends \
              ripgrep jq postgresql-client \
            && rm -rf /var/lib/apt/lists/*
        RUN npm install -g @anthropic-ai/claude-code @openai/codex
```

Or from a Dockerfile downloaded from a URL:

```yaml
spec:
  image:
    dockerfile:
      url: https://raw.githubusercontent.com/myorg/dockerfiles/main/dev/Dockerfile
```

Since an inline or a downloaded Dockerfile has no build context, it cannot `COPY` or `ADD` local files. Use a [git repository](#git-repository) or the [*Workspace*'s repository](#from-the-workspace-repository) instead when you need a build context. You can also use the `--dockerfile` flag of `cordium run` and `cordium create workspace`, which reads a local Dockerfile and embeds it inline:

```bash
cordium run --dockerfile ./Dockerfile
```

> **Note:**
>
> Images are built inside the *Workspace*'s own pod with rootless Podman, and every build must complete within 10 minutes. Since persistent *Workspaces* keep their container across runs and pre-built *Templates* restore it from a snapshot, the build cost is only paid on fresh runs.

## Git Repository

Build the image from a dedicated git repository, which is separate from the *Workspace*'s own repository:

```yaml
spec:
  image:
    git:
      url: https://github.com/myorg/dev-images
      checkout: main
      dockerfile: images/backend/Dockerfile
      context: images/backend
```

The `dockerfile` and `context` paths are relative to the repository root. If `dockerfile` is omitted, Cordium looks for a devcontainer spec (i.e. `.devcontainer/devcontainer.json` or `.devcontainer.json`) in the repository instead.

## From the Workspace Repository

Build the image from a Dockerfile inside the *Workspace*'s primary repository (i.e. `spec.repository`):

```yaml
spec:
  repository:
    url: https://github.com/myorg/backend
  image:
    repository:
      dockerfile:
        path: docker/Dockerfile.dev
        context: .
```

Or from the repository's devcontainer spec:

```yaml
spec:
  repository:
    url: https://github.com/myorg/backend
  image:
    repository:
      devcontainer:
        dirPath: .devcontainer
```

Set `dirPath` to `.` to use a `.devcontainer.json` file at the repository root. If the image is set to `repository: {}` or is not set at all, Cordium first looks for an image in the repository's [configuration file](https://octelium.com/docs/cordium/latest/workspaces/overview.md#repository-configuration-files), then for `.devcontainer/devcontainer.json` or `.devcontainer.json`, and falls back to the default image.

## Devcontainers

Cordium implements the most commonly used parts of the [Development Container specification](https://containers.dev/). When a *Workspace*'s image is built from a devcontainer spec, Cordium:

- Pulls or builds the image defined by the spec's `image`, `build` (i.e. `dockerfile`, `context` and `args`), or `dockerComposeFile` and `service` properties, and honors the `overrideCommand` and `init` properties.
- Applies the `containerEnv` values as environment variables.
- Downloads and installs the devcontainer features from their OCI registries.
- Runs the `onCreateCommand`, `updateContentCommand` and `postCreateCommand` hooks as `ON_CREATE` tasks and the `postStartCommand` hook as a `POST_START` task, all in `/workspace/repo`.
- Installs the VS Code extensions listed in `customizations.vscode.extensions` when the image provides a `code`, `code-server` or `openvscode-server` binary (e.g. to pre-install the extensions of a browser-based VS Code).

No Cordium-specific configuration is needed. For example, the following spec is enough for a repository that already has a `.devcontainer/devcontainer.json`:

```yaml
spec:
  repository:
    url: https://github.com/myorg/devcontainer-project
```

> **Note:**
>
> You can read a complete devcontainer-based example [here](https://octelium.com/docs/cordium/latest/examples/dev/devcontainer.md).

### Devcontainer Features

You can also install [devcontainer features](https://containers.dev/features) into any *Workspace*, regardless of how its image is obtained, via `spec.runtime.devcontainers.features`. They are merged with the features declared by the repository's own devcontainer spec, if any. Here is an example:

```yaml
spec:
  image:
    registry:
      url: ubuntu:26.04
  runtime:
    devcontainers:
      features:
        - reference: ghcr.io/devcontainers/features/go:1
          options:
            - key: version
              value: "1.26"
        - reference: ghcr.io/devcontainers/features/node:1
          options:
            - key: version
              value: "24"
        - reference: ghcr.io/devcontainers/features/github-cli:1
```

Features are downloaded and installed on fresh runs only.
