Cordium documentation · Latest

Environment and Tasks

The spec.runtime field controls the environment variables, lifecycle tasks and the container behavior of a Workspace.

Environment Variables

Environment variables are injected into the Workspace container and are therefore available to its lifecycle tasks, terminals, cordium exec commands and SSH sessions. Values can be static strings or resolved from the Secrets of the Workspace's Space, which are referred to by their full name:

spec: runtime: envVars: - key: NODE_ENV value: development - key: LOG_LEVEL value: debug - key: DATABASE_URL fromSecret: staging-database-url.payments.cordium - key: STRIPE_SECRET_KEY fromSecret: stripe-test-key.payments.cordium

If the same key is defined at several levels, the most specific level wins (i.e. Workspace > Template > Space > UserConfig). You can also set them via the CLI:

cordium run --template api-dev.payments.cordium \ -e LOG_LEVEL=debug \ --env-from-secret DATABASE_URL=staging-database-url.payments.cordium
note

A Secret-sourced environment variable is readable by every process running inside the Workspace, including AI agents. Whenever the credential protects a resource that can be exposed as an Octelium Service (e.g. an HTTP API, an LLM provider, a database or an SSH server), prefer secretless access, which keeps the credential entirely out of the Workspace (read more here).

Lifecycle Tasks

Tasks are shell commands that run at defined points of the Workspace lifecycle. Every task supports the following fields:

FieldDescription
nameA name for the task, used in the logs and in failure reports.
runThe script to execute via the Workspace user's login shell (e.g. bash, zsh or sh). Multi-line scripts are supported.
typeON_CREATE, POST_START or PRE_STOP. Required.
workingDirThe working directory (e.g. /workspace/repo). It is recommended to always set it explicitly.
isBackgroundStarts the task without waiting for it to complete (e.g. dev servers and daemons).
runAsRootRuns the task as root instead of the Workspace user.
onFailureON_FAILURE_ABORT fails the Workspace run if the task fails, while ON_FAILURE_CONTINUE (the default) logs the failure and continues the initialization.
envVarsTask-specific environment variables (key and value), merged with the Workspace's.

Task Types

ON_CREATE tasks run on fresh runs only, i.e. the first run of a persistent Workspace and every run of an ephemeral one. They do not run again on the subsequent starts of a persistent Workspace, nor in Workspaces that are restored from a Template pre-build or a WorkspaceSnapshot, since their effects are already part of the restored storage. Use them for one-time setup: installing dependencies, compiling, running database migrations and seeding data.

POST_START tasks run on every start of the Workspace, after the ON_CREATE tasks. Use them for starting background services and dev servers, and for any work that needs secretless access to Octelium Services (see below).

PRE_STOP tasks run when the Workspace is stopping, before its container is stopped, and they must complete within 10 minutes. Use them for graceful shutdowns, flushing buffers or uploading results.

Here is an example:

spec: runtime: tasks: - name: install-deps type: ON_CREATE workingDir: /workspace/repo run: npm ci onFailure: ON_FAILURE_ABORT - name: migrate type: POST_START workingDir: /workspace/repo run: npm run db:migrate onFailure: ON_FAILURE_ABORT envVars: - key: DATABASE_URL value: postgres://pg-staging/app - name: redis type: POST_START runAsRoot: true run: | apt-get update && apt-get install -y redis-server redis-server --daemonize yes - name: dev-server type: POST_START workingDir: /workspace/repo run: npm run dev -- --host 0.0.0.0 isBackground: true - name: graceful-shutdown type: PRE_STOP workingDir: /workspace/repo run: npm run shutdown

Task Order

Within each task type, the foreground tasks run sequentially in the following order: the Template's tasks, the Workspace's tasks, the repository configuration file's tasks, the UserConfig's tasks and finally the Space's tasks. Background tasks are started in the same order but are not waited for. The Workspace becomes RUNNING once all the ON_CREATE and foreground POST_START tasks complete.

note

Every Workspace runs octelium connect as the very first POST_START task in the background, which is what provides its secretless access to Octelium Services. Therefore, tasks that access Octelium Services (e.g. a migration against a database Service or an AI agent that uses an LLM Service) must be POST_START tasks. Since the connection is established asynchronously, such tasks should wait for the Service to become reachable first, for example:

timeout 120 sh -c 'until curl -s -o /dev/null http://my-api; do sleep 2; done'

Timeouts and Failures

All the foreground ON_CREATE and POST_START tasks of a run must complete within 60 minutes on fresh runs and within 20 minutes on the subsequent runs of a persistent Workspace. Longer jobs (e.g. long-running AI agent sessions) should run as background tasks.

When a task with onFailure: ON_FAILURE_ABORT fails, the Workspace run fails with a Task failure that reports the task's name and exit code, and the Workspace is stopped. You can inspect the task output via cordium logs and the web portal. Tasks without ON_FAILURE_ABORT only log their failures. Since scripts run via the Workspace user's login shell, which is sh in some images, write portable scripts or explicitly invoke bash -c when you need Bash-specific features.

Auto-Stop

Setting autoStop: true causes the Workspace to stop automatically as soon as all of its ON_CREATE and foreground POST_START tasks complete. Background tasks are not waited for. This is designed for CI/CD runs, batch jobs and unattended AI agent runs:

spec: isEphemeral: true repository: url: https://github.com/acme-corp/monorepo runtime: autoStop: true tasks: - name: build type: ON_CREATE workingDir: /workspace/repo run: make build onFailure: ON_FAILURE_ABORT - name: test type: ON_CREATE workingDir: /workspace/repo run: make test onFailure: ON_FAILURE_ABORT

You can wait for such a Workspace to finish via the SDKs' WaitUntilStopped/wait_until_stopped() helpers, which also report whether the run failed. For jobs that may exceed the foreground task timeout, run the job as a background task that stops its own Workspace once done via cordium stop, which stops the current Workspace when no name is given:

spec: runtime: tasks: - name: long-job type: POST_START isBackground: true workingDir: /workspace/repo run: | ./scripts/nightly-benchmarks.sh > /workspace/benchmarks.log 2>&1 cordium stop

Container Command and Entrypoint

By default, the Workspace container keeps the image's entrypoint and runs sleep infinity as its command, with a minimal init process as PID 1 that reaps zombie processes. You can override them as follows:

spec: runtime: entrypoint: /usr/bin/tini -- cmd: sleep infinity disableInit: true

Only set disableInit: true if the image already includes its own init system.

Filesystem and Capabilities

You can make the container's root filesystem read-only. /workspace, the home directory, /tmp, /var/tmp and mounted Volumes stay writable:

spec: runtime: filesystem: readOnly: true

You can also add and drop Linux capabilities. They are merged with the capabilities set at the Space and ClusterConfig levels:

spec: runtime: capabilities: add: - SYS_PTRACE drop: - NET_RAW

Or via the CLI:

cordium run --image python:3.13-slim --read-only --cap-drop NET_RAW --cap-add SYS_PTRACE
note

Capabilities only apply inside the Workspace's own user namespace, which is mapped to an unprivileged user on the host. In other words, even CAP_SYS_ADMIN inside a Workspace does not grant any privilege outside of it.

Nested Containers

Workspaces can run their own containers via rootless Podman, which is useful for running databases and other dependencies of integration tests, or for building and pushing container images. Install Podman (e.g. apt-get install -y podman) via the image or a task, and use it as root via sudo (read the complete example here):

sudo podman run -d --name postgres --net host \ -e POSTGRES_PASSWORD=password docker.io/library/postgres:18