Sandbox image
The layered Docker image, what Kortix injects, constraints, examples, and snapshot rebuilds.
The sandbox base image. For how a session uses it at runtime, see Session runtime.
Every session boots inside a Docker container, built in two parts: your Dockerfile defines the environment (languages, tools, system packages), and the Kortix runtime layer is auto-injected on top so the dashboard can connect.
┌─────────────────────────────────────────┐
│ Kortix runtime layer (auto-injected) │ ← opencode + kortix-agent + ENTRYPOINT
├─────────────────────────────────────────┤
│ Your Dockerfile │ ← .kortix/Dockerfile
└─────────────────────────────────────────┘Referenced from the manifest as a named template under sandbox.templates (v2 kortix.yaml) or [[sandbox.templates]] (legacy v1 kortix.toml):
# kortix.yaml (v2)
sandbox:
templates:
- slug: dev # any slug except the reserved "default"
name: Dev box # optional label
dockerfile: .kortix/Dockerfile # OR image: python:3.12-slim
cpu: 2 # optional — vCPU cores
memory: 4 # optional — GiB
disk: 20 # optional — GiB
# Make a template the project-wide default so every session (UI, triggers,
# channels) boots it without passing a slug. Omit → the platform default.
default: dev# kortix.toml (legacy v1) — same fields, TOML array of tables
[[sandbox.templates]]
slug = "dev" # any slug except the reserved "default"
name = "Dev box" # optional label
dockerfile = ".kortix/Dockerfile" # OR image = "python:3.12-slim"
cpu = 2 # optional — vCPU cores
memory = 4 # optional — GiB
disk = 20 # optional — GiB
[sandbox]
default = "dev"Each entry sets exactly one of dockerfile or image. Paths must be repo-relative — absolute paths and .. traversal are rejected. A singular sandbox block with image/build keys on it directly (rather than under templates) is legacy and rejected; image definitions live under sandbox.templates, and sandbox itself otherwise only carries default. Field details: the manifest reference.
What the Kortix layer injects
On top of your Dockerfile's final stage, the snapshot builder appends:
USER root(the layer needs to install things).apt-get installa system package floor:ca-certificates curl git gzip nodejs npm unzip tmux iproute2 iputils-arping build-essential ffmpeg fonts-dejavu fonts-liberation fonts-noto fonts-noto-cjk latexmk libreoffice pandoc pkg-config poppler-utils qpdf tesseract-ocr python3 python3-dev python3-pip python3-venv texlive-bibtex-extra texlive-fonts-recommended texlive-latex-base texlive-latex-extra texlive-latex-recommended— CLI/networking basics, a build toolchain, LibreOffice/LaTeX/Pandoc document conversion, OCR, and a Python 3 toolchain.- A pinned Python package floor via
pip install --break-system-packages, verified importable at build time: web/parsing (beautifulsoup4,lxml,markdownify,markitdown[pptx],requests), data (numpy,pandas,scipy,scikit-learn), PDF/office (pdf2docx,pdf2image,pdfplumber,pymupdf,pypdf,pypdfium2,python-docx,python-pptx,openpyxl,reportlab), imaging/browser (pillow,pytesseract,playwright), and plotting (matplotlib,plotly,seaborn). This backs the starter's document/data/PDF/presentation skills and is present even if your Dockerfile never touches Python. npm install -g opencode-ai@<pinned-version>, plus thebunruntime andagent-browser(for the headless-browser tool).- A baked OpenCode tool-dependency cache under
/opt/kortix/(so the boot-timebun installin your config dir is a no-op, not a network round-trip). COPYthekortix-agentdaemon, thekortixCLI, and thekortix-entrypointscript to/usr/local/bin/, plus theslack-cliandexecutor-sdkpackage trees under/opt/kortix/.ENV KORTIX_WORKSPACE=/workspace,WORKDIR /workspace,EXPOSE 8000.ENTRYPOINT ["/usr/local/bin/kortix-entrypoint"].
Everything you installed stays on PATH; the layer relocates and removes nothing. The exact package lists above are pinned to a runtime layer version and can change between platform releases — they're the floor guaranteed on every sandbox, not a ceiling on what you can add.
Constraints
These rules keep a sandbox connectable. They aren't enforced statically — your build succeeds even if you violate them — but the session won't behave correctly.
| Rule | Why |
|---|---|
Don't set ENTRYPOINT. | Kortix overrides it. Your CMD is also ignored. |
Don't claim port 8000. | Reserved for the daemon's reverse proxy. Run your dev servers on other ports. |
FROM a Debian/Ubuntu-family base. | The layer assumes apt-get. Alpine, Fedora, Arch will fail at layer build. |
Don't RUN apt-get clean without rm -rf /var/lib/apt/lists/*. | The Kortix layer re-runs apt-get update; a broken lists cache breaks it. |
| Don't bake credentials into the image. | Declare the name in env: (or legacy [env]) and set the value in the dashboard's Environment variables page — it's injected at session start. |
Everything else is fair game: RUN curl … | sh for toolchains, COPY seed data, set ENV for non-secret config, install any apt/npm packages.
Examples
The starter ships no Dockerfile — sandbox.templates is a commented-out example in kortix.yaml, so a fresh project boots the platform's default image (bare Ubuntu plus the Kortix runtime layer above):
# syntax=docker/dockerfile:1.7
# Kortix platform default sandbox base.
FROM ubuntu:24.04
WORKDIR /workspaceAdd your own .kortix/Dockerfile and declare it under sandbox.templates once you need packages beyond the runtime floor. A minimal custom base might look like this:
# syntax=docker/dockerfile:1.7
FROM ubuntu:24.04
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl git build-essential \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspaceA heavier example with Python + Bun:
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl git python3.12 python3.12-venv unzip \
&& rm -rf /var/lib/apt/lists/* \
&& curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:${PATH}"
WORKDIR /workspaceHardware spec
cpu, memory, and disk on a sandbox.templates entry size the box your sessions run on. All three are optional; an omitted field takes the runtime provider's default. cpu is vCPU cores, memory and disk are GiB. GPUs are not supported in this version — a gpu key is rejected by the manifest validator.
# kortix.yaml (v2)
sandbox:
templates:
- slug: big
image: ubuntu:24.04
cpu: 4
memory: 8
disk: 50The spec is baked into the snapshot, not set per-session: the provider builds each sandbox from your project's snapshot and inherits its size from there. That has one practical consequence — changing the spec rebuilds the snapshot (it's part of the content hash, below) and the new size applies on the next session, exactly like a Dockerfile edit. Values are rounded to whole numbers; a non-positive value falls back to the default, and anything above the platform ceiling (cpu 32, memory 128 GiB, disk 500 GiB) is clamped down. See the manifest → sandbox.templates for the full field table and aliases.
Snapshot rebuilds
Snapshots are content-addressed: the platform hashes your Dockerfile's own bytes + the hardware spec + a platform runtime fingerprint (opencode version + kortix-agent binary). Unchanged hash reuses the snapshot; otherwise a new one builds. Rebuilds happen on any push that changes the Dockerfile's text, or any change to the sandbox spec — pure code commits, and edits to files your Dockerfile COPYs in without touching the Dockerfile itself, reuse the snapshot for free (the hash isn't computed over those copied files today).
Builds run on the sandbox provider (on the default Daytona provider, via Image.fromDockerfile). The first session on a new content hash triggers an inline build (a few minutes, shown as "preparing image"); later sessions on the same content hit the cache.
Editing the Dockerfile inside a session takes effect on the next session; current ones keep their booted snapshot. The edit reaches main only once a change request merges it.