elliottlawson / daytona-php-sdk
PHP SDK for Daytona API integration
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.5
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- laravel/pint: ^1.24
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- phpunit/phpunit: ^11.0|^12.0
- spatie/laravel-ray: ^1.40
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-06 19:43:33 UTC
README
A PHP SDK for interacting with the Daytona API to manage development sandboxes.
Requirements
- PHP 8.2 or higher
- Laravel 12.x or 13.x (optional, for Laravel integration)
Installation
composer require elliottlawson/daytona-php-sdk
Configuration
Laravel Applications
-
The service provider will be automatically registered via Laravel's package discovery.
-
Publish the configuration file:
php artisan vendor:publish --provider="ElliottLawson\Daytona\DaytonaServiceProvider" --tag="daytona-config"
- Add your Daytona credentials to your
.envfile:
DAYTONA_API_URL=https://api.daytona.io DAYTONA_API_KEY=your-api-key DAYTONA_ORGANIZATION_ID=your-org-id # Optional configuration DAYTONA_DEFAULT_SNAPSHOT=laravel-php84 DAYTONA_DEFAULT_MEMORY=2 DAYTONA_DEFAULT_DISK=2 DAYTONA_DEFAULT_CPU=1 DAYTONA_AUTO_STOP_INTERVAL=30
Configuration Options
The SDK requires three configuration values:
apiUrl(string): The Daytona API endpoint (defaults tohttps://api.daytona.io)apiKey(string, required): Your Daytona API authentication keyorganizationId(string, required): Your organization ID
Type-Safe Configuration
For better type safety and IDE support, you can use the Config class:
use ElliottLawson\Daytona\DTOs\Config; $config = new Config( apiUrl: 'https://api.daytona.io', apiKey: 'your-api-key', organizationId: 'your-org-id' );
Non-Laravel Applications
You can instantiate the client directly with configuration:
use ElliottLawson\Daytona\DaytonaClient; $client = new DaytonaClient([ 'api_url' => 'https://api.daytona.io', 'api_key' => 'your-api-key', 'organization_id' => 'your-org-id' // optional ]);
Usage
Using the Facade (Laravel)
The easiest way to use Daytona in Laravel is through the facade:
use Daytona; use ElliottLawson\Daytona\DTOs\SandboxCreateParameters; // Create a sandbox $sandbox = Daytona::createSandbox(new SandboxCreateParameters( language: 'php', snapshot: 'laravel-php84', )); // Execute commands $result = Daytona::executeCommand($sandbox->getId(), 'composer install'); // Work with files Daytona::writeFile($sandbox->getId(), '/workspace/test.txt', 'Hello World'); $content = Daytona::readFile($sandbox->getId(), '/workspace/test.txt'); // Delete sandbox Daytona::deleteSandbox($sandbox->getId());
Creating a Client
In Laravel (Recommended)
use ElliottLawson\Daytona\DaytonaClient; use ElliottLawson\Daytona\DTOs\Config; // Using dependency injection public function __construct(DaytonaClient $client) { $this->daytonaClient = $client; } // Using the service container $client = app(DaytonaClient::class); // Using without parameters (pulls from .env via config) $client = new DaytonaClient(); // Using the config from container $config = app(Config::class); $client = new DaytonaClient($config);
Manual Configuration
use ElliottLawson\Daytona\DaytonaClient; use ElliottLawson\Daytona\DTOs\Config; // Using typed configuration (recommended for type safety) $config = new Config( apiUrl: 'https://api.daytona.io', apiKey: 'your-api-key', organizationId: 'your-org-id' ); $client = new DaytonaClient($config);
Managing Sandboxes
Create a Sandbox
use ElliottLawson\Daytona\DTOs\SandboxCreateParameters; // Create with minimal parameters $sandbox = $client->createSandbox(new SandboxCreateParameters()); // Create with custom settings $sandbox = $client->createSandbox(new SandboxCreateParameters( language: 'php', snapshot: 'laravel-php84', envVars: ['APP_ENV' => 'development'], memory: 4, disk: 10, cpu: 2, autoStopInterval: 30, )); // The sandbox is automatically started and ready to use echo $sandbox->getId(); // Sandbox ID echo $sandbox->getState(); // 'started' echo $sandbox->getRunnerDomain(); // Access URL // Access sandbox data $data = $sandbox->getData(); // Returns SandboxResponse DTO $array = $sandbox->toArray(); // Returns array representation
Get Sandbox Information
// Get a Sandbox object by ID $sandbox = $client->getSandboxById($sandboxId); // Access sandbox properties echo $sandbox->getState(); // e.g., "started", "stopped" echo $sandbox->getCreatedAt(); echo $sandbox->getSnapshot(); echo $sandbox->getCpu(); echo $sandbox->getMemory(); // Refresh data from API $sandbox->refresh(); // Get raw SandboxResponse DTO if needed $response = $client->getSandbox($sandboxId);
List and Filter Sandboxes
use ElliottLawson\Daytona\DTOs\SandboxFilter; // List every sandbox $sandboxes = $client->listSandboxes(); foreach ($sandboxes as $sandbox) { echo $sandbox->getId() . ' - ' . $sandbox->getState() . PHP_EOL; } // Filter by labels using the array shorthand $sandboxes = $client->listSandboxes(['env' => 'dev']); // Or build a filter with the SandboxFilter DTO $filter = SandboxFilter::byLabels(['env' => 'dev']) ->withStates(['started']) ->withPublic(false); $sandboxes = $client->listSandboxes($filter);
For large result sets, listSandboxesPaginated() returns one page at a time using Daytona's cursor pagination:
// $filter, $cursor and $limit are all optional $filter = SandboxFilter::byLabels(['env' => 'dev'])->withStates(['started']); $cursor = null; $limit = 50; $response = $client->listSandboxesPaginated($filter, $cursor, $limit); foreach ($response->items as $sandbox) { echo $sandbox->getId() . PHP_EOL; } // Fetch the next page while one is available while ($response->hasMore()) { $response = $client->listSandboxesPaginated($filter, $response->nextCursor, $limit); foreach ($response->items as $sandbox) { echo $sandbox->getId() . PHP_EOL; } }
listSandboxesPaginated() returns a PaginatedSandboxesResponse:
$response->items—Sandbox[]in the current page$response->nextCursor— cursor for the next page, ornullwhen exhausted$response->hasMore()— whether another page is available$response->count()/$response->isEmpty()— page size helpers
SandboxFilter supports both the modern query params (states, isPublic, name, cursor, limit) and the legacy ones (state, user, public) for older Daytona versions. Filters can be built with named constructors (byLabels(), byId(), byName(), byState(), byStates(), byUser(), byPublic()) or chained with the with*() methods (e.g. withCursor(), withLimit(), withStates()).
Start/Stop Sandbox
// Using the Sandbox object (recommended) $sandbox->start(); $sandbox->stop(); $sandbox->delete(); // Wait for state transitions $sandbox->waitUntilStarted(timeout: 60); // Wait up to 60 seconds $sandbox->waitUntilStopped(timeout: 30); // Or using the client directly $client->startSandbox($sandboxId); $client->stopSandbox($sandboxId); $client->deleteSandbox($sandboxId);
Working with Files
Using the Sandbox Object (Recommended)
// Read files $content = $sandbox->readFile('/workspace/index.php'); echo $content; // Write files $sandbox->writeFile('/workspace/hello.txt', 'Hello, World!'); // List directory contents $listing = $sandbox->listDirectory('/workspace'); foreach ($listing->files as $file) { echo $file->name . ' - ' . $file->type . PHP_EOL; } // Delete files $sandbox->deleteFile('/workspace/temp.txt'); // Check file existence if ($sandbox->fileExists('/workspace/config.php')) { echo "File exists!"; }
Executing Commands
// Using the Sandbox object (recommended) $result = $sandbox->exec('ls -la'); // Check the result if ($result->isSuccessful()) { echo $result->output; } else { echo "Command failed with exit code: " . $result->exitCode; echo "Error: " . $result->error; } // Quick command execution $result = $sandbox->exec('composer install'); echo $result->output; // Note about exit codes: // - Exit code 0 means success // - Exit codes 1-255 indicate various error conditions // - Exit code -1 means the actual exit code couldn't be determined by Daytona if (!$result->hasKnownExitCode()) { echo "Warning: Exit code is unknown"; } // Execute with specific working directory $result = $sandbox->exec('ls -la', '/workspace'); // Execute with custom environment variables $env = ['NODE_ENV' => 'production', 'API_KEY' => 'secret123']; $result = $sandbox->exec('npm run build', null, $env); // Execute with timeout (in milliseconds) $timeout = 30000; // 30 seconds $result = $sandbox->exec('npm test', null, null, $timeout); // Execute with cwd, env and timeout $result = $sandbox->exec('composer install', '/app', $env, $timeout); // Or using the client directly $result = $client->executeCommand($sandboxId, 'npm test', '/workspace', $env, $timeout);
Session Management for Long-Running Commands
The SDK provides session support for managing long-running processes like development servers, build watchers, and background tasks.
// Create a session for long-running commands $session = $sandbox->createSession('dev-server'); // Start a development server asynchronously $serverCommand = $session->executeCommand( 'npm run dev', runAsync: true, cwd: '/workspace/app' ); // Get preview URL while server is running $preview = $sandbox->getPreviewLink(3000); echo "Access your app at: {$preview->url}\n"; // Stream server logs in real-time $serverCommand->streamLogs(function($chunk) { echo $chunk; flush(); }); // Or use the convenience method for quick async execution $buildCommand = $sandbox->execAsync('npm run build'); $status = $buildCommand->waitForCompletion(timeout: 300); if ($status->exitCode === 0) { echo "Build successful!\n"; } // Clean up when done $session->delete();
For more details on session management, see Session Management Documentation.
Git Operations
Using the Sandbox Object (Recommended)
// Clone a repository $sandbox->gitClone( 'https://github.com/laravel/laravel.git', 'main', // branch (optional) '/workspace', // destination path 'username', // for private repos (optional) 'password' // for private repos (optional) ); // Git status $status = $sandbox->gitStatus('/workspace'); echo "Branch: " . $status->branch . PHP_EOL; echo "Modified files: " . count($status->modified) . PHP_EOL; // Git workflow with fluent interface $sandbox->gitAdd('/workspace', ['file1.php', 'file2.php']) ->gitCommit('/workspace', 'Add new features', 'John Doe', 'john@example.com') ->gitPush('/workspace', 'username', 'password');
Using the Client Directly
// List branches $branches = $client->gitListBranches($sandboxId, '/workspace'); echo "Current branch: " . $branches->current . PHP_EOL; foreach ($branches->branches as $branch) { echo "- " . $branch . PHP_EOL; } // Git history $history = $client->gitHistory($sandboxId, '/workspace'); foreach ($history->commits as $commit) { echo $commit->hash . ' - ' . $commit->message . PHP_EOL; }
Complete Example
Using the Facade
use Daytona; use ElliottLawson\Daytona\DTOs\SandboxCreateParameters; // Create a sandbox $sandbox = Daytona::createSandbox(new SandboxCreateParameters( language: 'php', snapshot: 'laravel-php84', envVars: ['APP_ENV' => 'testing'], )); // Clone and setup a Laravel project $sandbox->gitClone('https://github.com/laravel/laravel.git') ->exec('composer install') ->exec('cp .env.example .env') ->exec('php artisan key:generate'); // Run tests $result = $sandbox->exec('php artisan test'); echo $result->output; // Clean up $sandbox->delete();
Using Dependency Injection
use ElliottLawson\Daytona\DaytonaClient; use ElliottLawson\Daytona\DTOs\SandboxCreateParameters; class DeploymentService { public function __construct( private DaytonaClient $daytona ) {} public function testDeployment(string $commitHash): bool { $sandbox = $this->daytona->createSandbox(new SandboxCreateParameters( language: 'php', snapshot: 'production-like', )); try { $sandbox->gitClone('https://github.com/mycompany/app.git') ->exec("git checkout {$commitHash}") ->exec('composer install --no-dev') ->exec('npm install && npm run build'); $result = $sandbox->exec('./vendor/bin/phpunit'); return $result->isSuccessful(); } finally { $sandbox->delete(); } } }
Advanced Usage
Working with Sandbox Data
// Get all sandbox data $data = $sandbox->getData(); // Returns SandboxResponse DTO $array = $sandbox->toArray(); // Returns array // Access specific properties echo $sandbox->getState(); // 'started', 'stopped', etc. echo $sandbox->getRunnerDomain(); // Sandbox URL echo $sandbox->getCpu(); // CPU cores echo $sandbox->getMemory(); // Memory in GB echo $sandbox->getDisk(); // Disk in GB echo $sandbox->getCreatedAt(); // Creation timestamp // Check sandbox state if ($sandbox->getState() === 'started') { // Sandbox is ready } // Refresh data from API $sandbox->refresh();
State Management
// Create sandbox without waiting $sandbox = $client->createSandbox( new SandboxCreateParameters(snapshot: 'php-8.3'), waitForStart: false ); // Manually wait for it to start $sandbox->waitUntilStarted(timeout: 120); // Wait up to 2 minutes // Stop and wait $sandbox->stop()->waitUntilStopped();
Preview URLs
Access services running in your sandbox through preview URLs:
// Get preview URL for a specific port $previewInfo = $sandbox->getPreviewLink(3000); echo "Preview URL: " . $previewInfo->url; echo "Access Token: " . $previewInfo->token; // For programmatic access (e.g., curl), use the authorization token: $response = Http::withHeaders([ 'x-daytona-preview-token' => $previewInfo->token ])->get($previewInfo->url); // Multiple ports example $ports = [3000, 8080, 5000]; foreach ($ports as $port) { $preview = $sandbox->getPreviewLink($port); echo "Port {$port}: {$preview->url}\n"; }
Preview URLs allow external access to services running in your sandbox:
- Public sandboxes: URLs are publicly accessible
- Private sandboxes: Require authentication token in
x-daytona-preview-tokenheader - URL format:
https://{port}-{sandboxId}.{runner-domain}
Error Handling
The SDK provides specific exception types for different error scenarios:
use ElliottLawson\Daytona\Exceptions\ConfigurationException; use ElliottLawson\Daytona\Exceptions\SandboxException; use ElliottLawson\Daytona\Exceptions\FileSystemException; use ElliottLawson\Daytona\Exceptions\CommandExecutionException; use ElliottLawson\Daytona\Exceptions\GitException; use ElliottLawson\Daytona\Exceptions\ApiException; try { $sandbox = $client->createSandbox(new SandboxCreateParameters()); $result = $sandbox->exec('npm test'); if (!$result->isSuccessful()) { // Handle command failure echo "Tests failed: " . $result->error; } } catch (ConfigurationException $e) { // Handle configuration errors (missing API key, etc.) echo "Configuration error: " . $e->getMessage(); } catch (SandboxException $e) { // Handle sandbox-specific errors echo "Sandbox error: " . $e->getMessage(); } catch (CommandExecutionException $e) { // Handle command execution errors echo "Command failed: " . $e->getMessage(); } catch (FileSystemException $e) { // Handle file operation errors echo "File operation failed: " . $e->getMessage(); } catch (GitException $e) { // Handle Git operation errors echo "Git operation failed: " . $e->getMessage(); } catch (ApiException $e) { // Handle API errors with status codes echo "API error ({$e->getStatusCode()}): " . $e->getMessage(); echo "Response: " . $e->getResponseBody(); }
Exception Types:
ConfigurationException- Missing API keys, invalid configurationSandboxException- Sandbox creation, state transitions, lifecycle errorsFileSystemException- File read/write/delete operationsCommandExecutionException- Command execution failuresGitException- Git operations (clone, commit, push, etc.)ApiException- HTTP API errors with status codes and responses
All exceptions extend the base ElliottLawson\Daytona\Exception class.
Using the Sandbox Object
For a more object-oriented approach, you can work with the Sandbox object:
// Get a sandbox instance $sandbox = new \ElliottLawson\Daytona\Sandbox($sandboxId, $client); // Execute commands $result = $sandbox->exec('npm install'); // File operations $content = $sandbox->readFile('/workspace/package.json'); $sandbox->writeFile('/workspace/test.js', 'console.log("Hello");'); // Directory operations $files = $sandbox->listDirectory('/workspace'); // Lifecycle management $sandbox->start(); $sandbox->stop(); $sandbox->delete();
Testing
# Run feature tests only (default) composer test # Run integration tests (requires Daytona server) composer test:integration # Run all tests composer test:all # Run tests with coverage composer test-coverage
Test Configuration
The test suite is split into two groups:
- Feature tests: Unit tests that don't require external services
- Integration tests: Tests that require a running Daytona server
By default, composer test or vendor/bin/pest only runs feature tests. This allows for quick testing during development without needing a Daytona server.
Error Handling
All SDK methods throw Exception on errors:
use ElliottLawson\Daytona\Exceptions\Exception; try { $sandbox = $client->createSandbox([...]); } catch (Exception $e) { echo "Error: " . $e->getMessage(); }
License
MIT