Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Scotty Laravel Package

spatie/scotty

Scotty is a beautiful SSH task runner for executing scripted tasks on remote servers. Define tasks in a Scotty.sh file (bash with annotations), run them with clear output, and use it as a drop-in, Envoy-compatible alternative for deploys and ops.

View on GitHub
Deep Wiki
Context7

title: The Scotty.sh format weight: 3

The Scotty.sh format is plain bash with annotation comments. Every line is real bash, so your editor highlights it correctly and all your existing shell tooling works.

Servers

At the top of your file, define which servers you want to connect to:

# [@servers](https://github.com/servers) local=127.0.0.1 remote=deployer@your-server.com

You can define as many as you need:

# [@servers](https://github.com/servers) local=127.0.0.1 web-1=deployer@1.1.1.1 web-2=deployer@2.2.2.2

Tasks

A task is just a bash function with a # [@task](https://github.com/task) annotation above it. The on: parameter tells Scotty which server to run it on:

# [@task](https://github.com/task) on:remote
deploy() {
    cd /var/www/my-app
    git pull origin main
    php artisan migrate --force
}

That's the core concept. Everything else builds on this.

Running on multiple servers

You can target multiple servers by separating their names with commas:

# [@task](https://github.com/task) on:web-1,web-2
deploy() {
    cd /var/www/my-app
    git pull origin main
}

By default, the task runs on each server one after the other.

Parallel execution

If you want to run on all servers at the same time, add parallel:

# [@task](https://github.com/task) on:web-1,web-2 parallel
restartWorkers() {
    sudo supervisorctl restart all
}

This is handy for things like restarting workers across a cluster, where you don't need to wait for one to finish before starting the next.

Confirmation

For dangerous tasks (like deploying to production), you can require confirmation:

# [@task](https://github.com/task) on:remote confirm="Are you sure you want to deploy to production?"
deploy() {
    cd /var/www/my-app
    git pull origin main
}

Scotty will ask before running the task. If you say no, it stops.

Macros

A macro groups multiple tasks together so you can run them with a single command:

# [@macro](https://github.com/macro) deploy pullCode runComposer clearCache restartWorkers

If the list gets long, you can use the multi-line format:

# [@macro](https://github.com/macro) deploy
#   pullCode
#   runComposer
#   generateAssets
#   updateSymlinks
#   clearCache
#   restartWorkers
# [@endmacro](https://github.com/endmacro)

Run it with scotty run deploy. The tasks execute in the order you listed them. If any task fails, execution stops immediately.

Variables

You can define variables at the top of your file, right after the server and macro lines:

BRANCH="main"
REPOSITORY="your/repo"
APP_DIR="/var/www/my-app"
RELEASES_DIR="$APP_DIR/releases"
NEW_RELEASE_NAME=$(date +%Y%m%d-%H%M%S)

These are plain bash variables, so computed values like $(date) work naturally. All variables are available in all tasks.

You can also pass variables from the command line:

scotty run deploy --branch=develop

The key gets uppercased and dashes become underscores, so --branch=develop sets $BRANCH to develop.

Helper functions

Any function without a # [@task](https://github.com/task) annotation is treated as a helper. Helpers are available in all tasks:

log() {
    echo -e "\033[32m$1\033[0m"
}

# [@task](https://github.com/task) on:remote
deploy() {
    log "Deploying..."
    cd /var/www/my-app
    git pull origin main
}

Hooks

You can run code at different points during execution. This is useful for things like sending Slack notifications:

# [@before](https://github.com/before)
beforeEachTask() {
    echo "Starting task..."
}

# [@after](https://github.com/after)
afterEachTask() {
    echo "Task done."
}

# [@success](https://github.com/success)
onSuccess() {
    curl -X POST https://hooks.slack.com/... \
        -d '{"text": "Deploy succeeded!"}'
}

# [@error](https://github.com/error)
onError() {
    curl -X POST https://hooks.slack.com/... \
        -d '{"text": "Deploy failed!"}'
}

# [@finished](https://github.com/finished)
onFinished() {
    echo "Deploy process complete."
}

[@before](https://github.com/before) and [@after](https://github.com/after) run around each task. [@success](https://github.com/success) and [@error](https://github.com/error) run once at the end depending on whether everything passed. [@finished](https://github.com/finished) always runs, regardless of the outcome.

Complete example

Here's a full deploy script using all the concepts above:

#!/usr/bin/env scotty

# [@servers](https://github.com/servers) local=127.0.0.1 remote=deployer@your-server.com
# [@macro](https://github.com/macro) deploy
#   startDeployment
#   cloneRepository
#   runComposer
#   blessNewRelease
#   cleanOldReleases
# [@endmacro](https://github.com/endmacro)

BRANCH="main"
REPOSITORY="your/repo"
APP_DIR="/var/www/my-app"
RELEASES_DIR="$APP_DIR/releases"
CURRENT_DIR="$APP_DIR/current"
NEW_RELEASE_NAME=$(date +%Y%m%d-%H%M%S)
NEW_RELEASE_DIR="$RELEASES_DIR/$NEW_RELEASE_NAME"

# [@task](https://github.com/task) on:local
startDeployment() {
    git checkout $BRANCH
    git pull origin $BRANCH
}

# [@task](https://github.com/task) on:remote
cloneRepository() {
    cd $RELEASES_DIR
    git clone --depth 1 git@github.com:$REPOSITORY --branch $BRANCH $NEW_RELEASE_NAME
}

# [@task](https://github.com/task) on:remote
runComposer() {
    cd $NEW_RELEASE_DIR
    ln -nfs $APP_DIR/.env .env
    composer install --prefer-dist --no-dev -o
}

# [@task](https://github.com/task) on:remote
blessNewRelease() {
    ln -nfs $NEW_RELEASE_DIR $CURRENT_DIR
    sudo service php8.4-fpm restart
}

# [@task](https://github.com/task) on:remote
cleanOldReleases() {
    cd $RELEASES_DIR
    ls -dt $RELEASES_DIR/* | tail -n +4 | xargs rm -rf
}

Migrating from Envoy

If you're coming from Laravel Envoy, here's a quick reference. For the full Blade format documentation, see the Envoy compatibility page.

Blade format Scotty.sh format
[@servers](https://github.com/servers)(['remote' => '1.1.1.1']) # [@servers](https://github.com/servers) remote=1.1.1.1
[@task](https://github.com/task)('deploy', ['on' => 'remote']) # [@task](https://github.com/task) on:remote
[@endtask](https://github.com/endtask) } (end of function)
[@story](https://github.com/story)('deploy') ... [@endstory](https://github.com/endstory) # [@macro](https://github.com/macro) deploy task1 task2
{{ $variable }} $VARIABLE
[@setup](https://github.com/setup) PHP block Shell $(command) substitution
[@if](https://github.com/if)($condition) Bash if [ condition ]
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport