spatie/scotty
Scotty is a beautiful SSH task runner for running scripted tasks on remote servers. Define tasks and macros in a simple Scotty.sh (bash + annotations), then run them with clear output. Fully compatible with Laravel Envoy as a drop-in replacement.
Install Scotty:
curl -L https://github.com/spatie/scotty/releases/latest/download/scotty -o scotty
chmod +x scotty
Place it in your project root or add to $PATH.
Create a Scotty.sh file:
touch Scotty.sh
Add a basic server definition:
#!/usr/bin/env scotty
# @servers remote=deployer@your-server.com
Verify SSH connection:
./scotty doctor
First task:
# @task on:remote
testConnection() {
echo "Hello from remote!"
}
Run it:
./scotty run testConnection
Automate a Laravel deployment:
# @servers production=deployer@prod-server.com
# @macro deploy pullCode runMigrations restartServices
# @task on:production
pullCode() {
cd /var/www/laravel-app
git pull origin main
}
# @task on:production
runMigrations() {
cd /var/www/laravel-app
php artisan migrate --force
}
# @task on:production
restartServices() {
sudo systemctl restart nginx
sudo systemctl restart php-fpm
}
Run with:
./scotty run deploy
Modular Deployments:
deploy, rollback, backup).# @macro fullDeploy deploy backup rollbackSafetyNet
Environment-Specific Configs:
# @servers staging=deployer@staging.example.com production=deployer@prod.example.com
# @option env=staging
Run with:
./scotty run deploy --env=production
Laravel-Specific Tasks:
# @task on:remote
optimizeApp() {
cd $APP_DIR
php artisan optimize:clear
php artisan config:cache
}
Local Development:
127.0.0.1 for local testing:
# @servers local=127.0.0.1
# @task on:local
testLocally() {
php artisan test
}
Version Control: Commit Scotty.sh to your repo for team consistency.
CI/CD: Trigger Scotty from GitHub Actions or GitLab CI:
- name: Deploy
run: ./scotty run deploy --branch=$CI_COMMIT_REF_NAME
SSH Config: Leverage ~/.ssh/config for complex setups (e.g., jump hosts):
# @servers prod=prod-server
(Ensure prod-server is defined in ~/.ssh/config.)
Blade Templates: For dynamic scripts, use Blade syntax (e.g., @if(env('APP_ENV') === 'local')).
Error Handling: Use set -e in tasks to exit on failure:
# @task on:remote
deploy() {
set -e
cd $APP_DIR
git pull origin $BRANCH || exit 1
}
SSH Key Issues:
~/.ssh/id_rsa.pub is added to authorized_keys on remote servers.scotty doctor --verbose.Path Repetition:
APP_DIR="/var/www/my-app"
Task Dependencies:
--continue to bypass:
./scotty run deploy --continue
|| exit 1 to critical tasks to enforce failure handling.Option Conflicts:
@option) are rejected. Always declare them:
# @option branch=main
Blade vs. Bash:
@option directives are ignored in Blade files. Use environment variables instead:
# In Blade:
@set('branch', env('BRANCH', 'main'))
Pretend Mode:
--pretend to dry-run, but verify SSH commands manually for complex setups../scotty run deploy --verbose
./scotty run pullCode
-vvv to SSH commands in tasks to debug connections:
# @task on:remote
debugSSH() {
ssh -vvv deployer@your-server.com "echo 'Connected!'"
}
Aliases: Add Scotty to your shell config for convenience:
alias scotty='./scotty'
Template Files:
Use scotty init to scaffold new files:
./scotty init --format=bash --server=deployer@new-server.com
Pause/Resume:
Press p during execution to pause and inspect the server state.
Summary Mode:
Use --summary for quick CI/CD feedback:
./scotty run deploy --summary
Macro Reuse:
Define reusable macros for common patterns (e.g., backup, restart):
# @macro backup
# @task on:remote
# backupDB() { ... }
# @task on:remote
# backupFiles() { ... }
Local Testing:
Use 127.0.0.1 for local tasks to avoid SSH overhead:
# @servers local=127.0.0.1
# @task on:local
testArtisan() {
php artisan --version
}
Environment Variables:
Pass secrets via environment variables (e.g., DB_PASSWORD):
# @task on:remote
deploy() {
export DB_PASSWORD="$DB_PASSWORD"
php artisan migrate
}
Run with:
DB_PASSWORD=secret ./scotty run deploy
Confirmations:
Add confirm to critical tasks to prevent accidents:
# @task on:remote confirm="Are you sure? (y/n)"
deleteData() {
rm -rf /tmp/old-data
}
How can I help you explore Laravel packages today?