A PHP SDK for interacting with the Daytona API to manage development sandboxes.
- PHP 8.2 or higher
- Laravel 10.x, 11.x, or 12.x (optional, for Laravel integration)
composer require elliottlawson/daytona-php-sdk-
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=30The 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
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'
);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
]);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());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);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);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 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);// 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);// 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!";
}// 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);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.
// 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');// 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;
}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();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();
}
}
}// 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();// 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();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}
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.
echo $commit->hash . ' - ' . $commit->message . PHP_EOL;
echo 'Author: ' . $commit->author . PHP_EOL;
echo 'Date: ' . $commit->date . PHP_EOL;
}
### Using the Sandbox Object
For a more object-oriented approach, you can work with the `Sandbox` object:
```php
// 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();
# 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-coverageThe 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.
All SDK methods throw Exception on errors:
use ElliottLawson\Daytona\Exceptions\Exception;
try {
$sandbox = $client->createSandbox([...]);
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}MIT