Weave 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

Remote Laravel Package

illuminate/remote

Illuminate Remote brings SSH-based remote command execution to Laravel, offering a fluent API for connecting to servers, running commands, and handling output inside your application. It fits Laravel’s ecosystem for deployment, server automation, and remote task workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

First, check whether you actually need to install it. illuminate/remote is a read-only subtree split of a component that already ships inside laravel/framework (it arrived alongside Laravel 11/12). If you're on a current Laravel app, the classes are already autoloadable and the Remote facade already resolves — composer require illuminate/remote is usually a no-op at best and a replace conflict at worst, because laravel/framework declares "replace": {"illuminate/*": "self.version"}. Only reach for the standalone package in a non-Laravel app, a slimmed-down illuminate/*-only install, or a Laravel version where the component isn't bundled yet.

Standalone install path:

composer require illuminate/remote   # pulls phpseclib/phpseclib ^3.0

Then register Illuminate\Remote\RemoteServiceProvider and alias Illuminate\Support\Facades\Remote in your provider/bootstrap list.

Where to look first (in this repo):

  1. src/RemoteManager.php — the Manager subclass; connection($name) and __call forwarding to the default connection.
  2. src/Connection.php — the real API surface: run(), output callbacks, file transfer.
  3. src/ProcessOutput.php — what run() hands back (string casting, lines(), failure helpers).
  4. src/PendingInvocation.php — the fluent into(...)->run(...) shape.
  5. The framework's config/remote.php stub — that's the authoritative key list (host, username, port, password, private_key, passphrase, timeout). Copy it if the component doesn't publish one for you, and match key names exactly to your installed version.

Minimal first use case — an ops command you'd otherwise SSH in for:

// config/remote.php
return [
    'default' => env('REMOTE_CONNECTION', 'production'),
    'connections' => [
        'production' => [
            'host' => env('REMOTE_HOST'),
            'username' => env('REMOTE_USERNAME'),
            'port' => 22,
            'private_key' => env('REMOTE_PRIVATE_KEY'), // path to key
            'passphrase' => env('REMOTE_PASSPHRASE'),
            'timeout' => 60,
        ],
    ],
];
use Illuminate\Support\Facades\Remote;

$output = Remote::connection('production')
    ->run('cd /var/www/app && php artisan migrate --force && php artisan queue:restart');

echo (string) $output;

That's the whole value proposition: the deploy/ops steps you'd script over SSH, expressed as testable PHP you can wrap in an artisan command.

Implementation Patterns

Named connections as environments. Treat connection names as first-class (production, staging, bastion) and drive the default from an env var. Keep credentials out of config/remote.php values and reference env vars — the config file is committed, the key is not.

Streaming vs. capturing output. For short commands, cast ProcessOutput to string or iterate lines(). For anything verbose (log tails, build output, migrations across a big schema), pass a callback so you can write incrementally instead of buffering megabytes:

Remote::connection('production')
    ->into(fn ($chunk) => $this->line($chunk))
    ->run('tail -n 200 storage/logs/laravel.log');

Verify the exact callback argument shape in PendingInvocation/ProcessOutput for your version — line-oriented vs. chunk-oriented changes how you format it.

Compose commands, don't rely on session state. cd and exported env vars are not dependable across run() calls. Always chain: cd /var/www/app && ..., or use absolute paths (/usr/bin/php /var/www/app/artisan ...). Same for source/export — put it inline in the same command string.

File transfer for the boring-but-critical cases. put()/get() on the connection are the natural fit for: pushing a built .env, shipping a release tarball, pulling nightly backups or logs to a local analysis directory. For deploys, prefer git pull + composer install --no-dev on the host over tarring vendor/, but upload the .env and any uncommitted build artifacts.

Wrap it in a service, not in controllers. A small RemoteOps class with deploy(), tailLogs(), restartWorkers(), healthCheck() methods gives you: one place to log commands, one place to add timeouts, and one place to fake in tests. Expose it through narrow artisan commands (php artisan ops:deploy production) so humans and CI use the same code path and every run is auditable.

Fan out across hosts with a loop, not a library.

$results = collect(config('remote.connections'))
    ->keys()
    ->mapWithKeys(fn ($name) => [
        $name => Remote::connection($name)->run('uptime'),
    ]);

Connection calls are sequential and blocking. For real parallelism across many boxes, shell out to parallel SSH (pssh, ssh -o ... & wait) or use the framework's concurrency primitives — don't expect the remote component to pipeline for you.

Health checks and pre-flight gates. A cheap run('test -d /var/www/app/current && echo ok') before a deploy, or run('df -h / | tail -1') to refuse deploys on a full disk, is where this component earns its keep in CI.

Gotchas and Tips

This repo is read-only. Issues and PRs go to laravel/framework. Don't open anything here — and don't vendor-patch this package, because the next composer update of the framework will overwrite it. If you need behavior changes, wrap Connection rather than fork it.

Interactive commands hang forever. sudo without -n, apt without DEBIAN_FRONTEND=noninteractive, nano, nested ssh, anything waiting on a TTY or a passphrase prompt will sit until the timeout kills it and leaves you confused. Use sudo -n, -y, --no-interaction, and GIT_TERMINAL_PROMPT=0.

Non-login shell means a different PATH. Commands that work in your interactive session (php, composer, node) may not resolve. Use absolute paths or bash -lc '...' to load the profile.

Check exit codes, don't trust "no exception." phpseclib will happily return stdout from a command that exited 1. Inspect the ProcessOutput failure helper (throw() / throwIfFailed() — check the class in your version) or read the exit status explicitly before assuming a git pull succeeded. A deploy that "succeeds" with a failed composer install is the classic failure mode.

Timeouts are per-connection config, and the default is short. Migrations, npm run build, and queue restarts routinely exceed it. Set timeout per connection and raise it for release commands rather than globally — a tight default catches hung interactive prompts.

Never put secrets in the command string. They land in the remote host's process list, in your logs, and in any audit trail you build. Use a config file on the host, ~/.my.cnf, or stdin piping.

Host key verification is likely not enforced. phpseclib doesn't verify host keys unless you explicitly check them, so a compromised DNS/host goes unnoticed. For production credentials, verify out-of-band (pin the fingerprint in config and compare against getServerPublicHostKey()) or tunnel through a host you already trust.

Keep remote calls out of the HTTP request path. They're blocking, latency-sensitive, and failure-prone in exactly the way you don't want in a web request. Run them from console commands, scheduled jobs, or queued jobs with a worker timeout longer than the SSH timeout.

Testing: fake the boundary, not the SSH. Facades can be mocked (Remote::shouldReceive('connection')->andReturn(...)), but the cleaner move is to type-hint your own RemoteOps abstraction and bind a fake implementation in tests. Assert on the command strings your code would have run — that's the part worth testing.

Watch the phpseclib edge cases. Old OpenSSH servers may need algorithm negotiation tweaks; encrypted keys need passphrase set explicitly; keys in PuTTY/.ppk format won't load (convert to PEM/OpenSSH first). These surface as opaque connection failures, so log the underlying exception message, not just "connection failed."

Log every invocation centrally. One helper that records connection, command, duration, exit code, and the first/last N lines of output will save you more debugging time than any other investment in this component.

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.
vento/laravel-airbrake
wfeller/laravel-batch
maco-studios/mage-os-installer
busarm/armie
siberfx/laravel-logger
kfteam1991/dcat-admin
dashed/dashed-laposta
dashed/dashed-ecommerce-montaportal
dashed/dashed-ecommerce-etsy
neohq/string-blade-compiler
log1x/acf-composer
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager