Spuff Specification
Status of this document
This document specifies the Spuff project configuration file format used to define ephemeral cloud development environments. Distribution of this document is unlimited.
The canonical version of this specification can be found at docs/spec.md.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Version
This document specifies Spuff Configuration Format version 1.
Requirements and Optional Attributes
The Spuff configuration format aims to provide a developer-friendly syntax for defining reproducible development environments. Implementation of some attributes and features MAY vary across cloud providers and SHOULD be documented by the implementation.
The following terms are used to define attribute requirements:
Required: Attributes that MUST be present for the configuration to be valid
Optional: Attributes that MAY be omitted; implementations MUST use defined default values
Platform-dependent: Behavior or availability depends on the underlying cloud provider
The Spuff File
The configuration file MUST be named spuff.yaml or spuff.yml. The file MUST be valid YAML 1.2 encoded in UTF-8.
A Spuff configuration consists of:
version- Specification version (OPTIONAL)name- Project name (OPTIONAL)resources- VM resource configuration (OPTIONAL)bundles- Language toolchain definitions (OPTIONAL)packages- System packages (OPTIONAL)services- Docker Compose services (OPTIONAL)repositories- Additional repositories to clone (OPTIONAL)env- Environment variables (OPTIONAL)setup- Setup scripts (OPTIONAL)ports- SSH tunnel port mappings (OPTIONAL)hooks- Lifecycle hooks (OPTIONAL)
The following diagram shows how the configuration elements interact:
File Discovery
Spuff implementations MUST search for configuration files using the following algorithm:
Start from the current working directory
Look for
spuff.yamlorspuff.yml(in that order)If not found, move to the parent directory
Repeat until a configuration file is found or the filesystem root is reached
The directory containing the discovered configuration file is considered the project root.
Secrets File
A separate spuff.secrets.yaml file MAY be placed alongside the main configuration file. This file:
MUST contain only an
envsectionMUST NOT be committed to version control
Values MUST override corresponding values in the main configuration
SHOULD be added to
.gitignore
Version top-level element
Type: string Default: "1" Required: No
The version attribute defines the specification version. This attribute is OPTIONAL and defaults to "1".
Implementations MUST reject configurations with unsupported version numbers. Implementations SHOULD provide clear error messages when encountering unknown versions.
Name top-level element
Type: string Default: Directory name of project root Required: No Constraints: SHOULD be a valid identifier (alphanumeric, hyphens, underscores)
The name attribute defines a human-readable name for the project environment.
If omitted, implementations MUST use the name of the directory containing the configuration file.
The name is used for:
VM instance naming (with unique suffix)
State tracking and identification
Logging and status display
Resources top-level element
The resources element defines VM resource configuration. All sub-attributes are OPTIONAL.
Attribute Precedence
Configuration values follow this precedence order (highest to lowest):
CLI flags (
--size,--region)Project configuration (
spuff.yaml)Global configuration (
~/.spuff/config.yaml)Provider defaults
size
Type: string Default: Provider-dependent Required: No
Specifies the VM instance size. Valid values are provider-dependent.
DigitalOcean examples:
s-1vcpu-1gb
1
1 GB
25 GB
s-2vcpu-4gb
2
4 GB
80 GB
s-4vcpu-8gb
4
8 GB
160 GB
s-8vcpu-16gb
8
16 GB
320 GB
region
Type: string Default: Provider-dependent Required: No
Specifies the datacenter region. Valid values are provider-dependent.
DigitalOcean examples: nyc1, nyc3, sfo3, ams3, fra1, lon1, sgp1, blr1
Bundles top-level element
Type: array<string> Default: [] (empty array) Required: No
The bundles element defines pre-configured language toolchains to install. Each bundle includes the language runtime/compiler plus essential development tools (LSPs, linters, formatters, debuggers).
Valid Bundle Identifiers
Implementations MUST support the following bundle identifiers:
rust
rustup, cargo
rust-analyzer, clippy, rustfmt, mold, cargo-watch
go
go (1.23+)
gopls, delve, golangci-lint, air
python
python3.12, pip
uv, ruff, pyright, ipython
node
node (22 LTS), npm
pnpm, typescript, eslint, prettier
elixir
erlang/OTP, elixir, mix
elixir-ls, phoenix
java
openjdk (21), maven
gradle, jdtls
zig
zig (0.13+)
zls
cpp
gcc, clang, cmake
ninja, clangd, gdb, lldb
ruby
ruby, bundler
solargraph, rubocop
Implementations MUST return an error for unknown bundle identifiers.
Installation Behavior
Required tools: Installation failure MUST cause the bundle to be marked as failed
Optional tools: Installation failure SHOULD be logged but MUST NOT cause bundle failure
Bundles SHOULD be installed in parallel when possible
Installation progress MUST be trackable via the agent API
Packages top-level element
Type: array<string> Default: [] (empty array) Required: No
The packages element defines additional system packages to install via the system package manager (apt on Ubuntu/Debian).
Package names MUST be valid package identifiers for the target system. Implementations SHOULD NOT validate package names before provisioning (validation occurs at install time).
Installation Behavior
Packages MUST be installed after the base system is ready
Package installation failure SHOULD be logged with the specific package name
All packages are installed in a single transaction when possible
Services top-level element
The services element configures Docker Compose services. This element does NOT duplicate Docker Compose configuration; it references an existing compose file.
enabled
Type: boolean Default: true if compose file exists, false otherwise Required: No
Controls whether Docker Compose services should be started.
compose_file
Type: string Default: "docker-compose.yaml" Required: No
Path to the Docker Compose file, relative to the project root.
Implementations MUST support both docker-compose.yaml and docker-compose.yml filenames when using default discovery.
profiles
Type: array<string> Default: [] (empty array, all services without profile) Required: No
Docker Compose profiles to activate. Corresponds to docker compose --profile flag.
Repositories top-level element
Type: array<Repository> Default: [] (empty array) Required: No
The repositories element defines additional Git repositories to clone into the environment.
Repository Formats
Repositories can be specified in two formats:
Short Syntax
Short syntax assumes GitHub and expands to:
URL:
https://github.com/owner/repo.gitPath:
~/projects/repoBranch: HEAD (default branch)
Long Syntax
url
string
-
Yes
Git repository URL (HTTPS or SSH)
path
string
~/projects/<repo-name>
No
Clone destination path
branch
string
null (HEAD)
No
Branch, tag, or commit to checkout
SSH Agent Forwarding
Implementations MUST use SSH agent forwarding for cloning operations. This allows users' local SSH keys to authenticate with private repositories without exposing keys on the VM.
Env top-level element
Type: object<string, string> Default: {} (empty object) Required: No
The env element defines environment variables to be set on the VM.
Variable Resolution
Implementations MUST support variable references in values. References are resolved from the local environment (where spuff up is executed) before being sent to the VM.
$VAR
$HOME
Simple reference, empty string if not set
${VAR}
${USER}
Braced reference, empty string if not set
${VAR:-default}
${PORT:-8080}
With default, uses default if not set
Resolution Rules
Variable names MUST match the pattern
[a-zA-Z_][a-zA-Z0-9_]*Unset variables MUST resolve to empty string (without default) or the default value
Resolution MUST occur before configuration is sent to the VM
Literal
$can be escaped as$$
Secrets Merging
When spuff.secrets.yaml exists, its env values MUST be merged after the main configuration, overriding any duplicate keys.
Setup top-level element
Type: array<string> Default: [] (empty array) Required: No
The setup element defines shell commands to execute after bundles and packages are installed.
Execution Rules
Commands MUST be executed in order (sequential, not parallel)
Commands MUST be executed in the user's home directory by default
If a command returns a non-zero exit code, subsequent commands MUST be skipped
Exit codes and output MUST be logged to
/var/log/spuff/scripts/NNN.logCommands MUST be executed as the unprivileged user, not root
Logging
Each script MUST have its output captured in a numbered log file:
Ports top-level element
Type: array<integer> Default: [] (empty array) Required: No Constraints: Each value MUST be a valid port number (1-65535)
The ports element defines ports for automatic SSH tunneling when connecting via spuff ssh.
Tunnel Behavior
For each port N in the array:
Local
localhost:Nis forwarded to VMlocalhost:NTunnels are established when
spuff sshis invokedTunnels remain active for the duration of the SSH session
This allows local development tools (browsers, IDEs) to connect to services running on the remote VM.
Hooks top-level element
The hooks element defines lifecycle scripts for custom automation.
post_up
Type: string Default: null Required: No
Script executed after the environment is fully ready (all bundles, packages, services, repositories, and setup scripts complete).
pre_down
Type: string Default: null Required: No
Script executed before VM destruction. This allows for cleanup, backups, or graceful shutdown procedures.
Execution Rules
Hooks MUST be executed as shell scripts (bash)
Hook failures SHOULD be logged but MUST NOT prevent the operation from completing
Multi-line scripts SHOULD use YAML literal block syntax (
|)
Complete Configuration Example
Agent API Reference
The spuff-agent running on provisioned VMs exposes a REST API for managing project setup. All endpoints require authentication via X-Spuff-Token header when SPUFF_AGENT_TOKEN is set.
GET /project/config
Returns the current project configuration.
Response: 200 OK with JSON body containing the parsed configuration.
GET /project/status
Returns detailed setup progress.
Response: 200 OK with JSON body:
POST /project/setup
Triggers project setup. This endpoint is idempotent; calling it multiple times has no effect if setup is already in progress or complete.
Request Body: Project configuration JSON (optional, uses embedded config if omitted)
Response: 202 Accepted if setup started, 200 OK if already running/complete.
Setup Status Values
pending
Not yet started
in_progress
Currently executing
done
Successfully completed
failed
Execution failed (with error message)
skipped
Intentionally skipped
Validation Rules
Strict Validation
Implementations MUST validate:
YAML syntax is valid
File encoding is UTF-8
Bundle identifiers are in the supported list
Version string is supported
Lenient Validation
Implementations SHOULD NOT validate:
Package names (provider/repository dependent)
Region codes (provider dependent)
VM sizes (provider dependent)
Repository URLs (format flexibility)
Port numbers beyond basic range check
Invalid values in lenient categories will cause runtime errors during provisioning.
File Format Notes
YAML Version: 1.2 (via serde_yaml)
Encoding: UTF-8 (MUST)
Indentation: 2-space or 4-space (standard YAML)
Comments: Supported with
#Multi-line Strings: Use
|for literal blocks,>for folded blocksEmpty Sections: All sections are optional; omit unused sections
Changelog
Version 1 (Initial)
Initial specification release
Support for bundles: rust, go, python, node, elixir, java, zig, cpp, ruby
Environment variable resolution with defaults
Docker Compose integration
SSH port tunneling
Lifecycle hooks (post_up, pre_down)
Secrets management via spuff.secrets.yaml
Last updated
Was this helpful?