Working with Sprites
After you’ve made it through the Quickstart, you’ve got a working Sprite and a sense of how to use it. This page covers what comes next: how Sprites behave over time, how to run persistent processes, how networking works, and how to shape the environment to fit your stack. Use it as a reference as you start building more with Sprites.
Lifecycle
Section titled “Lifecycle”Automatic Hibernation
Section titled “Automatic Hibernation”Sprites automatically hibernate when inactive, with no compute charges while idle. When you execute a command or make a request to a Sprite’s URL, it automatically wakes up with all your data intact.
Timeouts
Section titled “Timeouts”Sprites currently hibernate after 30 seconds of inactivity. This timeout is not configurable yet.
Idle Detection
Section titled “Idle Detection”A Sprite is considered active if any of the following are true:
- It has an active command running (via
exec) - Its stdin is being written to
- It has an open TCP connection over its URL
- A detachable session is running
Configuration
Section titled “Configuration”Resource Allocation
Section titled “Resource Allocation”Sprites currently run on machines with a fixed resource cap: 8 vCPUs, 8192 MB RAM, and 100 GB storage. These limits are not configurable yet. The SDK accepts config fields, but the API ignores them.
Keep in mind, Sprites are not billed based on those limits. You are only charged for the resources your Sprite actually uses. The fixed config defines the current maximum resources each Sprite can consume, not a flat rate.
Working Directory
Section titled “Working Directory”Set the working directory for command execution:
sprite exec -dir /home/sprite/project npm testconst result = await sprite.exec('npm test', { cwd: '/home/sprite/project',});cmd := sprite.Command("npm", "test")cmd.Dir = "/home/sprite/project"output, err := cmd.Output(){output, 0} = Sprites.cmd(sprite, "npm", ["test"], dir: "/home/sprite/project")Environment Variables
Section titled “Environment Variables”sprite exec -env MY_SECRET=hello bash -c 'echo $MY_SECRET'const result = await sprite.execFile('bash', ['-lc', 'echo $MY_SECRET'], { env: { MY_SECRET: 'hello' },});console.log(result.stdout); // hellocmd := sprite.Command("bash", "-c", "echo $MY_SECRET")cmd.Env = []string{"MY_SECRET=hello"}output, _ := cmd.Output()fmt.Println(string(output)) // hello{output, 0} = Sprites.cmd(sprite, "bash", ["-c", "echo $MY_SECRET"], env: [{"MY_SECRET", "hello"}])IO.puts(output) # helloEnvironment
Section titled “Environment”Pre-installed Tools
Section titled “Pre-installed Tools”The default Sprite environment includes:
- Languages: Node.js, Python, Go, Ruby, Rust, Elixir/Erlang, Java, Bun, Deno
- AI Tools: Claude CLI, Gemini CLI, OpenAI Codex, Cursor
- Utilities: Git, curl, wget, vim, and common development tools
Custom Setup
Section titled “Custom Setup”Run setup commands after creating a Sprite:
const sprite = await client.createSprite('my-sprite');
// Install custom dependenciesawait sprite.exec('pip install pandas numpy matplotlib');await sprite.exec('npm install -g typescript');
// Clone a repositoryawait sprite.exec('git clone https://github.com/your/repo.git /home/sprite/project');sprite, _ := client.CreateSprite(ctx, "my-sprite", nil)
// Install custom dependenciessprite.Command("pip", "install", "pandas", "numpy", "matplotlib").Run()sprite.Command("npm", "install", "-g", "typescript").Run()
// Clone a repositorysprite.Command("git", "clone", "https://github.com/your/repo.git", "/home/sprite/project").Run(){:ok, sprite} = Sprites.create(client, "my-sprite")
# Install custom dependenciesSprites.cmd(sprite, "pip", ["install", "pandas", "numpy", "matplotlib"])Sprites.cmd(sprite, "npm", ["install", "-g", "typescript"])
# Clone a repositorySprites.cmd(sprite, "git", ["clone", "https://github.com/your/repo.git", "/home/sprite/project"])Interactive Sessions
Section titled “Interactive Sessions”TTY Mode
Section titled “TTY Mode”For interactive applications, enable TTY mode:
# Open interactive shell (TTY enabled by default)sprite console
# Or with execsprite exec -tty bashconst cmd = sprite.spawn('bash', [], { tty: true, rows: 24, cols: 80,});
// Write to stdincmd.stdin.write('echo hello\n');
// Read from stdoutcmd.stdout.on('data', (data) => { process.stdout.write(data);});
// Resize terminalcmd.resize(30, 100);cmd := sprite.Command("bash")cmd.SetTTY(true)cmd.SetTTYSize(24, 80)
cmd.Stdin = os.Stdincmd.Stdout = os.Stdoutcmd.Stderr = os.Stderr
cmd.Start()
// Resize latercmd.Resize(30, 100)
cmd.Wait(){:ok, cmd} = Sprites.spawn(sprite, "bash", [], tty: true, tty_rows: 24, tty_cols: 80)
# Write to stdinSprites.write(cmd, "echo hello\n")
# Receive outputreceive do {:stdout, ^cmd, data} -> IO.write(data)end
# Resize terminalSprites.resize(cmd, 30, 100)Detachable Sessions
Section titled “Detachable Sessions”Create sessions that persist even after disconnecting:
# Create a detachable sessionsprite exec -detachable "npm run dev"
# List active sessionssprite exec
# Attach to a sessionsprite exec -id <session-id>// Create a detachable sessionconst sessionCmd = sprite.createSession('npm', ['run', 'dev']);let sessionId;
sessionCmd.on('message', (msg) => { if (msg && msg.type === 'session_info') { sessionId = msg.session_id; }});
// ... later, attach to itconst cmd = sprite.attachSession(sessionId);cmd.stdout.pipe(process.stdout);// Create a detachable sessioncmd := sprite.CreateDetachableSession("npm", "run", "dev")cmd.Start()
// List sessions to get the session IDsessions, _ := client.ListSessions(ctx, "my-sprite")sessionID := sessions[0].ID
// ... later, attach to itcmd = sprite.AttachSessionContext(ctx, sessionID)cmd.Stdout = os.Stdoutcmd.Run()# Create a detachable session{:ok, cmd} = Sprites.spawn(sprite, "npm", ["run", "dev"], detachable: true)
# Get the session ID from the messagesession_id = receive do {:session_info, ^cmd, %{session_id: id}} -> idend
# ... later, attach to it{:ok, cmd} = Sprites.attach_session(sprite, session_id)
receive do {:stdout, ^cmd, data} -> IO.write(data)endListing Sessions
Section titled “Listing Sessions”sprite execconst sessions = await sprite.listSessions();for (const session of sessions) { console.log(`${session.id}: ${session.command}`);}sessions, _ := client.ListSessions(ctx, "my-sprite")for _, session := range sessions { fmt.Printf("%s: %s\n", session.ID, session.Command)}{:ok, sessions} = Sprites.list_sessions(sprite)for session <- sessions do IO.puts("#{session.id}: #{session.command}")endManaging Sprites
Section titled “Managing Sprites”Referencing by Name
Section titled “Referencing by Name”# Set active sprite for current directorysprite use my-sprite
# Commands now use this spritesprite exec echo "hello"// Get sprite by name (doesn't verify it exists)const sprite = client.sprite('my-sprite');
// Get sprite and verify it existsconst sprite = await client.getSprite('my-sprite');// Get sprite by name (doesn't verify it exists)sprite := client.Sprite("my-sprite")
// Get sprite and verify it existssprite, err := client.GetSprite(ctx, "my-sprite")# Get sprite by name (doesn't verify it exists)sprite = Sprites.sprite(client, "my-sprite")
# Get sprite and verify it exists{:ok, sprite} = Sprites.get_sprite(client, "my-sprite")Listing Sprites
Section titled “Listing Sprites”# List all spritessprite list
# List with prefix filtersprite list --prefix "dev-"// List all spritesconst sprites = await client.listAllSprites();
// List with prefix filterconst devSprites = await client.listAllSprites('dev-');
// Paginated listingconst page = await client.listSprites({ maxResults: 50 });console.log(page.sprites);if (page.hasMore) { const nextPage = await client.listSprites({ continuationToken: page.nextContinuationToken, });}// List all spritessprites, _ := client.ListAllSprites(ctx, "")
// List with prefix filterdevSprites, _ := client.ListAllSprites(ctx, "dev-")
// Paginated listingpage, _ := client.ListSprites(ctx, &sprites.ListOptions{MaxResults: 50})fmt.Println(page.Sprites)if page.HasMore { nextPage, _ := client.ListSprites(ctx, &sprites.ListOptions{ ContinuationToken: page.NextContinuationToken, })}# List all sprites{:ok, sprites} = Sprites.list(client)
# List with prefix filter{:ok, dev_sprites} = Sprites.list(client, prefix: "dev-")
# Iterate through spritesfor sprite <- sprites do IO.puts(sprite.name)endHTTP Access
Section titled “HTTP Access”Every Sprite has a unique URL for HTTP access:
# Get sprite URLsprite url
# Make URL public (no auth required)sprite url update --auth public
# Make URL require sprite auth (default)sprite url update --auth default// Get sprite info including URLconst info = await client.getSprite('my-sprite');console.log(info.url);// Get sprite info including URLinfo, _ := client.GetSprite(ctx, "my-sprite")fmt.Println(info.URL)# Get sprite info including URL{:ok, info} = Sprites.get_sprite(client, "my-sprite")IO.puts(info.url)
# Update URL settingsSprites.update_url_settings(sprite, public: true)Updating URL settings is available via the CLI, Go SDK, Elixir SDK, or REST API (the JS SDK does not expose a helper yet).
Port Forwarding
Section titled “Port Forwarding”Forward local ports to your Sprite:
# Forward local port 3000 to sprite port 3000sprite proxy 3000
# Forward multiple portssprite proxy 3000 8080 5432// Forward single portsession, _ := client.ProxyPort(ctx, "my-sprite", 3000, 3000)defer session.Close()// localhost:3000 now forwards to sprite:3000
// Forward multiple portssessions, _ := client.ProxyPorts(ctx, "my-sprite", []sprites.PortMapping{ {LocalPort: 3000, RemotePort: 3000}, {LocalPort: 8080, RemotePort: 80},})# Forward single port{:ok, session} = Sprites.proxy_port(sprite, 3000, 3000)# localhost:3000 now forwards to sprite:3000
# Forward multiple portsmappings = [ %Sprites.Proxy.PortMapping{local_port: 3000, remote_port: 3000}, %Sprites.Proxy.PortMapping{local_port: 8080, remote_port: 80}]{:ok, sessions} = Sprites.proxy_ports(sprite, mappings)
# Stop proxy when doneSprites.Proxy.Session.stop(session)Port Notifications
Section titled “Port Notifications”Get notified when ports open in your Sprite:
const cmd = sprite.spawn('npm', ['run', 'dev']);
cmd.on('message', (msg) => { if (msg.type === 'port_opened') { console.log(`Port ${msg.port} opened on ${msg.address} by PID ${msg.pid}`); // Auto-forward or notify user }});cmd := sprite.Command("npm", "run", "dev")cmd.TextMessageHandler = func(data []byte) { var notification sprites.PortNotificationMessage json.Unmarshal(data, ¬ification)
if notification.Type == "port_opened" { fmt.Printf("Port %d opened on %s by PID %d\n", notification.Port, notification.Address, notification.PID) }}cmd.Run(){:ok, cmd} = Sprites.spawn(sprite, "npm", ["run", "dev"])
# Handle port notifications in receive loopreceive do {:port_opened, ^cmd, %{port: port, address: addr, pid: pid}} -> IO.puts("Port #{port} opened on #{addr} by PID #{pid}")
{:stdout, ^cmd, data} -> IO.write(data)endCheckpoints
Section titled “Checkpoints”Save and restore Sprite state:
# Create a checkpointsprite checkpoint create
# List checkpointssprite checkpoint list
# Restore from checkpointsprite restore <checkpoint-id># Create a checkpoint{:ok, checkpoint} = Sprites.create_checkpoint(sprite, comment: "before deploy")
# List checkpoints{:ok, checkpoints} = Sprites.list_checkpoints(sprite)
# Restore from checkpoint{:ok, _} = Sprites.restore_checkpoint(sprite, checkpoint.id)See Checkpoints and Restore for more details.
Error Handling
Section titled “Error Handling”import { ExecError } from '@fly/sprites';
try { await sprite.execFile('bash', ['-lc', 'exit 1']);} catch (error) { if (error instanceof ExecError) { console.log('Exit code:', error.exitCode); console.log('Stdout:', error.stdout); console.log('Stderr:', error.stderr); }}cmd := sprite.Command("bash", "-lc", "exit 1")err := cmd.Run()
if err != nil { if exitErr, ok := err.(*sprites.ExitError); ok { fmt.Printf("Exit code: %d\n", exitErr.ExitCode()) }}case Sprites.cmd(sprite, "bash", ["-lc", "exit 1"]) do {_output, 0} -> IO.puts("Success")
{output, exit_code} -> IO.puts("Exit code: #{exit_code}") IO.puts("Output: #{output}")endCleanup
Section titled “Cleanup”Always clean up Sprites when you’re done:
sprite destroy my-spriteawait sprite.delete();// orawait client.deleteSprite('my-sprite');err := client.DeleteSprite(ctx, "my-sprite"):ok = Sprites.destroy(sprite)# or:ok = Sprites.destroy(client, "my-sprite")Optional: Mount Sprite Filesystem Locally
Section titled “Optional: Mount Sprite Filesystem Locally”Add this helper function to your .zshrc or .bashrc to mount your Sprite’s home directory locally:
# Add to ~/.zshrcsc() { local sprite_name="${1:-$(sprite use)}" local mount_point="/tmp/sprite-${sprite_name}" mkdir -p "$mount_point" sshfs -o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 \ "sprite@${sprite_name}.sprites.dev:" "$mount_point" cd "$mount_point"}Then use it with:
sc my-sprite # Mounts and cd's to the sprite's filesystem