Naming
The container, volume, network and image names, and the fixed paths, that a worker and every control plane have to pronounce the same way.
Why it exists
A worker is a container called mp-worker-<id>, with volumes named after it, on a network named
after it, running an image called mp-proj-<slug>:v<n>, 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
that writes it, the Docker client that looks it up, and the UI that shows it.
Until this module existed, none of them had a single definition. mp-worker-<id> was rebuilt by
hand in fourteen places across two apps — and a typo in one of them does not fail to compile, it
produces a container nobody can find at runtime.
These are formats, not preferences
Changing a value here renames things that already exist on real machines: a running worker's container and volumes keep the name they were created with. Treat every constant on this page as versioned protocol. The package's own tests pin the exact strings so that they are annoying to change — read a diff to this module as a migration, not a rename.
The mp- prefix is historical (the product was called something else) and is deliberately left
alone: it is baked into containers, volumes and images that are running right now.
The full catalogue of names and paths, as a format reference, is in Formats → Names and paths.
import {
workerContainerName,
workerNetworkName,
workerVolumeName,
workerVolumePrefix,
projectImageRef,
isProjectImageRef,
SHARED_NETWORK,
workerStatusPath,
} from "@spunto/build/naming"
const container = docker.getContainer(workerContainerName(workerId))
const ref = projectImageRef(projectId, version) // mp-proj-my-app:v7Worker resources
workerContainerName(workerId)
function workerContainerName(workerId: string): stringThe container that is the worker: mp-worker-<workerId>.
workerContainerName("abc123") // → "mp-worker-abc123"workerNetworkName(workerId)
function workerNetworkName(workerId: string): stringThe per-worker bridge network: mp-worker-<workerId>-net. It isolates each worker at L3 so no
worker can reach another worker's container IP directly, bypassing the proxy.
WORKER_VOLUME_KINDS / WorkerVolumeKind
const WORKER_VOLUME_KINDS = ["workspace", "home", "docker", "containerd"] as const
type WorkerVolumeKind = (typeof WORKER_VOLUME_KINDS)[number]The named volumes a worker may own. Spawning creates workspace, plus docker and containerd
when the project runs docker-in-docker. home is historical: nothing creates it any more, but
workers created before it was dropped still have one, so teardown has to keep looking for it.
workerVolumeName(workerId, kind)
function workerVolumeName(workerId: string, kind: WorkerVolumeKind): stringOne of a worker's named volumes: mp-worker-<workerId>-<kind>.
workerVolumeName("abc123", "workspace") // → "mp-worker-abc123-workspace"
workerVolumeName("abc123", "docker") // → "mp-worker-abc123-docker"workerVolumePrefix(workerId)
function workerVolumePrefix(workerId: string): stringThe prefix every volume of one worker shares: mp-worker-<workerId>- — which is also the container
name plus a dash. Teardown and disk accounting scan by prefix rather than by listing kinds, so
that a volume added later is still found by code written before it.
const prefix = workerVolumePrefix(workerId)
const volumes = (await docker.listVolumes()).Volumes.filter((v) => v.Name.startsWith(prefix))The trailing dash is load-bearing: it is what keeps mp-worker-abcdef-workspace from being
counted (and deleted) as a volume of worker abc.
SHARED_NETWORK
const SHARED_NETWORK = "mp-shared-net"The network every shared service joins under its slug, and every worker joins at spawn, so a
workspace reaches http://<slug>:<port> by DNS. Spunto Lite's "shared services".
Project images
projectImageRef(projectId, version)
function projectImageRef(projectId: string, version: number): stringThe image a project's workers boot from, keyed by the project's version. The slug is derived,
not stored: an image ref may only hold lowercase alphanumerics and separators, and a project id is
not guaranteed to. The id is lowercased, every other character becomes -, runs of dashes collapse
and leading/trailing dashes are trimmed.
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"The project version only moves when the project's own config does. A change to the image
recipe is tracked separately by IMAGE_RECIPE_VERSION in
./script.
isProjectImageRef(ref)
function isProjectImageRef(ref: string): booleanTrue for an image this platform built (/^mp-proj-[a-z0-9-]+:v\d+$/), as opposed to a base image
it pulled. Useful when pruning images.
isProjectImageRef("mp-proj-p:v9") // → true
isProjectImageRef("node:24") // → false
isProjectImageRef("mp-proj-x:latest") // → falsePaths inside a worker
WORKER_STATUS_FILE / workerStatusPath(home)
const WORKER_STATUS_FILE = ".mp-status.json"
function workerStatusPath(home: string): stringWhere a worker reports its own setup progress (a SetupStatus),
read back by the control plane. The constant is relative to the user's home, not absolute: the
remote user is a project setting, so the absolute path is only known once you know who the worker
runs as. A trailing slash on home is tolerated.
workerStatusPath("/home/vscode") // → "/home/vscode/.mp-status.json"
workerStatusPath("/root/") // → "/root/.mp-status.json"The JSON written there is described in Formats → Worker status.
EXTENSIONS_DIR
const EXTENSIONS_DIR = "/opt/mp-extensions"VS Code extensions are installed into the image, at a system path — not into a home, which is per worker and would make every worker re-download them.
FEATURE_ENTRYPOINTS_FILE
const FEATURE_ENTRYPOINTS_FILE = "/etc/mp-feature-entrypoints"A file, not a directory: a newline-separated, sort -u'd list of commands to run at container
start. This implements the devcontainer entrypoint contract: a feature declares one, the image
build appends it here, and every boot reads the file back and runs each line.
This constant shipped as FEATURE_ENTRYPOINTS_DIR in 0.2.0, which was wrong — it is read with
while read < file, and the wrong name invited the wrong shell. It was renamed before anything
depended on it.
FEATURE_ENTRYPOINTS_STAGING_FILE
const FEATURE_ENTRYPOINTS_STAGING_FILE = "/tmp/mp-feature-entrypoints"Where entrypoints accumulate during the image build, before being merged into
FEATURE_ENTRYPOINTS_FILE. Separate because each feature install runs in its own layer and appends
blindly; the merge is what deduplicates.
EXTENSION_FAILURES_FILE
const EXTENSION_FAILURES_FILE = "/etc/mp-extension-failures"Extensions the image build could not install, one id per line. Left in the image so a worker can
tell what is missing without anyone re-reading the build log. EXTENSION_FAILED_MARKER in
./steps is the same information on its way out.
# inside a worker
cat /etc/mp-extension-failures 2>/dev/null