Recipes¶
Practical patterns for common Sputnik use cases. These are starting points, not drop-in universal templates -- adapt paths, commands, framework-specific calls, and variable names to your project.
Note
Examples on this page intentionally use familiar tools like Docker, Composer, npm, and Laravel-style commands. Treat them as patterns, not as Sputnik-specific conventions.
Simple build task¶
See: Writing Tasks
#[Task(name: 'build', description: 'Build the project')]
final class BuildTask implements TaskInterface
{
public function __invoke(TaskContext $ctx): TaskResult
{
$ctx->exec(['composer', 'install', '--no-dev', '--optimize-autoloader']);
$ctx->shell('npm ci && npm run build');
return TaskResult::success();
}
}
Container task with Docker¶
See: Environments
Config:
Task:
#[Task(name: 'db:migrate', description: 'Run database migrations', environment: 'container')]
final class MigrateTask implements TaskInterface
{
public function __invoke(TaskContext $ctx): TaskResult
{
$ctx->exec(['php', 'artisan', 'migrate', '--force']);
return TaskResult::success('Migrations applied');
}
}
sputnik db:migrate
# On host: automatically wraps as "docker compose exec -T app php artisan migrate --force"
# In container: runs directly
Context-based .env generation¶
Config:
contexts:
dev:
description: Local development
variables:
constants:
appEnv: dev
debug: true
prod:
description: Production
variables:
constants:
appEnv: prod
debug: false
variables:
constants:
dbHost: localhost
dbName: myapp
templates:
env:
src: templates/.env.dist
dist: .env
overwrite: always
Template (templates/.env.dist):
Switch context and the .env is automatically re-rendered:
Runtime variable overrides¶
See: Variables
#[Task(name: 'deploy', description: 'Deploy the application')]
final class DeployTask implements TaskInterface
{
public function __invoke(TaskContext $ctx): TaskResult
{
$target = $ctx->get('deployTarget', 'staging');
$ctx->info("Deploying to {$target}");
$ctx->shell('rsync -avz ./dist/ {{ deployTarget }}:/var/www/');
return TaskResult::success("Deployed to {$target}");
}
}
Task with options and arguments¶
See: Writing Tasks
use Sputnik\Attribute\Argument;
use Sputnik\Attribute\Option;
#[Task(name: 'db:seed', description: 'Seed database tables', environment: 'container')]
final class SeedTask implements TaskInterface
{
#[Argument(name: 'table', description: 'Table to seed')]
private ?string $table;
#[Option(name: 'count', description: 'Number of rows', shortcut: 'c', type: 'int', default: 10)]
private int $count;
#[Option(name: 'truncate', description: 'Truncate before seeding', default: false)]
private bool $truncate;
public function __invoke(TaskContext $ctx): TaskResult
{
$table = $ctx->argument('table') ?? 'all';
$count = $ctx->option('count');
if ($ctx->option('truncate')) {
$ctx->exec(['php', 'artisan', 'db:truncate', $table]);
}
$ctx->exec(['php', 'artisan', 'db:seed', '--table=' . $table, '--count=' . $count]);
return TaskResult::success("Seeded {$table} with {$count} rows");
}
}
Listener on context switch¶
See: Event Listeners
use Sputnik\Attribute\AsListener;
use Sputnik\Event\ContextSwitchedEvent;
use Sputnik\Executor\ExecutorInterface;
#[AsListener(event: ContextSwitchedEvent::class, priority: -10, environment: 'container')]
final class ClearCacheOnContextSwitch
{
public function __construct(
private readonly ExecutorInterface $executor,
) {}
public function __invoke(ContextSwitchedEvent $event): void
{
if (!$event->hasChanged()) {
return;
}
$this->executor->execute('php artisan cache:clear');
}
}
Runs automatically after every context switch, after templates have been re-rendered (priority -10 runs after the built-in listeners at 100 and 0).
One-shot context override¶
See: CLI Reference
Run a single task in a different context without switching:
The persisted context is not changed. Templates are rendered with the override context for this run, then restored afterwards.
Host-only task¶
See: Environments
#[Task(name: 'docker:start', description: 'Start containers', environment: 'host')]
final class DockerStartTask implements TaskInterface
{
public function __invoke(TaskContext $ctx): TaskResult
{
$ctx->exec(['docker', 'compose', 'up', '-d']);
return TaskResult::success();
}
}
This task will fail with an error if executed inside a container, preventing accidental nested container operations.