SpuntoSpunto/@spunto/build

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:v7

Worker resources

workerContainerName(workerId)

function workerContainerName(workerId: string): string

The container that is the worker: mp-worker-<workerId>.

workerContainerName("abc123") // → "mp-worker-abc123"

workerNetworkName(workerId)

function workerNetworkName(workerId: string): string

The 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): string

One 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): string

The 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): string

The 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): boolean

True 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")            // → false

Paths inside a worker

WORKER_STATUS_FILE / workerStatusPath(home)

const WORKER_STATUS_FILE = ".mp-status.json"
function workerStatusPath(home: string): string

Where 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
Edit on GitHub

On this page