Names and paths
Every container, volume, network, image and file name in ./naming, with its real value, and why changing one renames things that are already running.
A worker is a container called mp-worker-<id>, with volumes and a network named after it, running
an image called mp-proj-<slug>:v<n>, and writing its progress to a file at a fixed path. None of
that is an implementation detail. Each name is a contract between the shell script or Docker call
that creates it, the Docker client that looks it up later, and the UI that shows it.
Before this module, none of them had a single definition. mp-worker-<id> was rebuilt by hand in
fourteen places across two apps of Spunto Cloud, and Spunto Lite rebuilt the same names again on its
side. A typo in one of those places does not fail to compile. It produces a container that nobody
can find at runtime. Module reference: naming.
Docker objects
| Export | Kind | Value | Example |
|---|---|---|---|
workerContainerName(workerId) | function | mp-worker-${workerId} | mp-worker-abc123 |
workerNetworkName(workerId) | function | mp-worker-${workerId}-net | mp-worker-abc123-net |
workerVolumeName(workerId, kind) | function | mp-worker-${workerId}-${kind} | mp-worker-abc123-workspace |
workerVolumePrefix(workerId) | function | mp-worker-${workerId}- | mp-worker-abc123- |
WORKER_VOLUME_KINDS | constant | ["workspace", "home", "docker", "containerd"] | |
projectImageRef(projectId, version) | function | mp-proj-${slug}:v${version} | mp-proj-my-app:v7 |
isProjectImageRef(ref) | function | tests /^mp-proj-[a-z0-9-]+:v\d+$/ | true for mp-proj-p:v9 |
SHARED_NETWORK | constant | mp-shared-net |
Worker container, network and volumes
- The container is the worker.
- The network is a per-worker bridge. It isolates each worker at L3, so that no worker can reach another worker's container IP directly and bypass the proxy.
- The volumes. Spawning creates
workspace(the code), plusdockerandcontainerdwhen the project runs Docker-in-Docker.homeis historical: nothing creates it any more, but workers created before it was dropped still have one, so teardown has to keep looking for it. This is the "formats, not preferences" rule in action: a value that is no longer written still has to be read. - The prefix. Teardown and disk accounting both scan volumes by prefix rather than by listing
kinds, so a volume kind added later is still found by code written before it. The trailing
-is load-bearing. Without it,mp-worker-abcwould matchmp-worker-abcdef-workspace, and tearing down one worker would delete another's disk. The container name is also a strict prefix of its volumes (workerVolumePrefix(x) === workerContainerName(x) + "-"), which the agent's lookups rely on.
Project image
export function projectImageRef(projectId: string, version: number): string {
const slug = projectId
.toLowerCase()
.replace(/[^a-z0-9]/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "")
return `mp-proj-${slug}:v${version}`
}An image is keyed by the project's version. The slug is derived rather than stored, because an image reference may only hold lowercase alphanumerics and separators, and a project id is not guaranteed to:
projectImageRef("proj123", 4) // "mp-proj-proj123:v4"
projectImageRef("Proj_ID.42", 1) // "mp-proj-proj-id-42:v1"
projectImageRef("--weird--id--", 2) // "mp-proj-weird-id:v2"isProjectImageRef tells an image this platform built apart from a base image it pulled. It
is false for node:24, mp-proj-x and mp-proj-x:latest.
The image recipe version is not part of this name. Spunto Cloud records IMAGE_RECIPE_VERSION on
each build row and compares it at spawn. Spunto Lite looks an image up by asking Docker whether the tag
exists, so it appends the recipe to the tag itself, composing on top of this function: ${projectImageRef(id, v)}-r${IMAGE_RECIPE_VERSION}.
isProjectImageRef does not match those suffixed refs, so Lite does not use it to recognize them.
Folding the recipe into the package's name would rename every existing image, which is exactly the
kind of change this page is about.
Shared network
SHARED_NETWORK = "mp-shared-net" is the network that every shared service joins under its slug and
that every worker joins at spawn, so that a workspace reaches http://<slug>:<port> by DNS. These are
Spunto Lite's "shared services", the local counterpart of the Ship pillar.
Paths inside a worker or its image
| Export | Value | Where | What it is |
|---|---|---|---|
WORKER_STATUS_FILE | .mp-status.json | relative to the user's home | The setup status the worker writes |
workerStatusPath(home) | ${home}/.mp-status.json (trailing slashes on home stripped) | container | Absolute path of the above, e.g. /home/vscode/.mp-status.json |
EXTENSIONS_DIR | /opt/mp-extensions | image | Where the image build installs VS Code extensions (code-server --extensions-dir) |
FEATURE_ENTRYPOINTS_FILE | /etc/mp-feature-entrypoints | image | A file, not a directory. It is a newline-separated, sort -u'd list of commands to run at container start. |
FEATURE_ENTRYPOINTS_STAGING_FILE | /tmp/mp-feature-entrypoints | image build | Where entrypoints accumulate during the build, before being merged into the file above |
EXTENSION_FAILURES_FILE | /etc/mp-extension-failures | image | Extensions the build could not install, one id per line (see Extension failures) |
EXTENSIONS_DIRis in the image, at a system path, not in a home. A home is per worker, and putting extensions there would make every worker download them again. At start, the generated script copies them into the user's code-server extensions directory (cp -rn /opt/mp-extensions/. ~/.local/share/code-server/extensions/).- Feature entrypoints implement the devcontainer
entrypointcontract on Spunto's side. A feature declares one, its install appends it to the staging file (each feature install runs in its own layer and appends blindly), thefinalizeblock of the build appends the staging file toFEATURE_ENTRYPOINTS_FILEand deduplicates withsort -u, and every boot reads the file back and runs each line. Appending rather than overwriting keeps an entrypoint already baked into the base image alongside the new ones.
The README and docs/shared-packages.md still mention FEATURE_ENTRYPOINTS_DIR. That name was
_DIR in 0.2.0, which was wrong, and it was corrected to FEATURE_ENTRYPOINTS_FILE "before
anything depended on it". The export is FEATURE_ENTRYPOINTS_FILE, and there is no _DIR export.
Related names defined elsewhere
These are not in ./naming, but they are names that a worker and a control plane share in the same way:
| Export | Module | Value |
|---|---|---|
SKILLS_DIR | ./script | .spunto/skills (relative to the home). See Skills layout. |
TERMINAL_DIR | ./terminal | /tmp/spunto-term. See the terminal module. |
PROJECT_SPEC_KIND | ./spec | spunto/project. See Project spec. |
Why changing a value renames live things
Every value on this page is baked into something that already exists: a running container, a volume full of someone's code, an image on a node's disk, a file inside an image built last month. Docker does not rename them when the package changes. A live worker keeps the name it was created with.
So if workerContainerName changed, the next deploy of the control plane would look for
<new>-abc123, find nothing, and conclude that the worker had vanished. Teardown would leave its
volumes behind. Disk accounting would stop counting them. Nothing would fail to compile, and nothing
would throw.
These are formats, not preferences. Read any diff of naming/index.ts as a migration, not a
rename. The tests pin the exact strings for that reason. A change needs a plan for the objects that
already carry the old name: read both names, migrate the objects, or wait until none remain.
The mp- prefix is historical (the product used to be called something else), and it is deliberately
left alone for this reason. It is baked into containers, volumes and images that are running right now.
Worker setup status
SetupStatus and ~/.mp-status.json — what a worker's setup script writes inside the container, and what the control plane reads back.
Skills layout
What buildSkillsScript leaves on disk — clones under ~/.spunto/skills/<id>/, one symlink per skill in each discovery directory, a ledger, and a [skills] log prefix.