laravelcollective/remote
LaravelCollective Remote brings SSH and SFTP to Laravel. Connect to remote servers, run commands, upload and download files, and manage multiple named connections through a clean, familiar Laravel-style API, ideal for deployments, server maintenance, and remote automation.
Architecture fit. laravelcollective/remote is a thin convenience layer over phpseclib: a service provider, a facade, a config file of named remotes, and a fluent wrapper that yields a result object with exit code, stdout, and stderr, plus SFTP put/get/list. That shape fits one architectural niche well — a Laravel app that already owns credentials for a handful of known servers and needs scripted command execution or file movement (backup shipping, artifact distribution, config pushes, occasional ops tasks). It fits poorly anywhere else. There is no connection pooling, no async, no event stream, and no structured command model; it is a synchronous, blocking, per-call SSH client. From a TPM standpoint the important framing is that this package is not infrastructure — it is a library that lets application code act as an operator. Any design where deploys or remote mutations happen inside a web request is a design problem the package cannot solve for you.
Integration feasibility. Mechanically trivial: composer require, auto-discovery registers the provider, publish config, define remotes in .env-backed config, resolve from the container. No schema, no migrations, no front-end assets. The API surface is small enough to learn in a day and to wrap in an afternoon. The realistic integration cost is not the package — it is the surrounding discipline: key material storage, host key verification, timeout and retry policy, exit-code checking, and log redaction.
Technical risk. This is the dominant section. The last release, v6.2.0 (Sept 2020), is a compatibility-only bump declaring Laravel 8 support. That means the package's supported ceiling is Laravel 8, which has been past end-of-life since January 2023. Concretely: the Composer constraint will block upgrade to Laravel 9/10/11, so adopting this package creates a hard pin on an unsupported framework branch, and a security-patch gap on both sides of the dependency. Underneath, the pinned phpseclib generation is itself legacy; phpseclib 3 changed the API materially, so "upgrade phpseclib" is not a version bump but a rewrite of the wrapper. Secondary risks: nonzero exit codes are returned, not thrown, so a naive run() that ignores getExitCode() fails silently — a classic false-green in backup and deploy scripts; host key verification must be explicitly configured, and default acceptance of an unknown key is a MITM exposure on any path that crosses an untrusted network; command strings built by interpolation are a shell-injection vector; and credentials live in config/env and key files on the app server, with no integration to a secret manager.
Key questions. (1) Is the target application actually on Laravel 8, and is there a funded plan to leave it? If not, this is disqualified. (2) What is the use case — deploy, backup, or file sync — and does it belong in the application at all, or in CI/CD and a scheduler? (3) Are we willing to accept a permanent fork or in-house maintenance obligation, and who owns it? (4) What is the blast radius of a wrong command: read-only, or destructive on production? (5) Is host key verification configured, and where do keys live and rotate? (6) Would spatie/ssh, direct phpseclib 3, or a proper orchestration tool (Envoy, Ansible, a CI runner) meet the requirement without the framework pin?
Stack fit. Only a fit for Laravel ≤8 on PHP 7.3–8.0-ish, verified against the package's own require block before committing. If the estate is Laravel 10/11, stop: there is no supported path, and a fork to widen the framework constraint is an unbudgeted maintenance line item, not an integration task.
Migration path. Treat the package as a replaceable adapter, not a foundation. Define two narrow ports of your own — a command executor (run(host, argv[]): Result{exitCode, stdout, stderr}) and a file transfer port (put/get/exists/list) — implement them with this package, and bind them in AppServiceProvider. Every call site depends on the interface, never on the facade or package classes. That single decision converts a dead-end dependency into a swap: spatie/ssh, phpseclib 3 directly, or an out-of-process runner can replace the implementation without touching business code. Keep package result objects and exceptions out of controllers, jobs, events, and API responses — translate at the port boundary.
Compatibility. Pin ^6.2 explicitly rather than ^6.0, set config.platform.php to the production PHP version so CI resolves against reality, and inspect composer show --tree after install to see what phpseclib generation is actually pulled in. Run php artisan package:discover -v and confirm the provider registers. Watch for the classic Laravel production-only failure: env() called outside a config file returns null under config:cache.
Sequencing. (1) Use-case triage — decide whether the work belongs in-app or in CI; push deploys to CI wherever possible. (2) Written risk acceptance for the Laravel 8 pin and the ownership of the fork/exit plan. (3) Staging spike against a throwaway host: one command, one upload, one download, with exit codes asserted. (4) Build the port + adapter + fake, and backfill tests. (5) Hardening: host key verification, timeouts, retry policy, log redaction, key rotation runbook. (6) Only then enable a single production use case behind a feature flag, with a rollback that does not require a deploy.
Maintenance. Effectively zero upstream maintenance and a correspondingly high downstream burden. No releases since 2020, no Laravel 9+ support, no security response. Budget it as vendored code: someone must own the fork or the exit, review phpseclib CVEs manually, and re-test on every PHP upgrade. The opportunity score is attractive; the maintenance score is not, and for a dependency that touches production credentials, maintenance dominates.
Support. Community-only, via a low-traffic repository. No vendor, no SLA, no CVE feed. Internal support therefore means a runbook and at least two engineers who can read the wrapper and phpseclib docs. Expect questions like "why did the backup report success but nothing arrived" — answer: exit code was not checked.
Scaling. Every operation opens and tears down a connection; there is no pooling. Concurrency is bounded by the remote sshd (MaxSessions/MaxStartups) and by your queue worker count — N workers × M remotes is a connection storm against small VPS targets, and the failure looks like intermittent auth or timeout errors under load. Long SFTP transfers occupy an entire worker; size the queue and timeouts accordingly, and never run these on the request path. Throughput targets should be modest: this is orchestration glue, not a data-movement pipeline.
Failure modes. Ranked by likelihood and damage: (1) silent success — nonzero exit ignored, deploy/backup marked green; (2) framework pin blocking a security upgrade of Laravel itself; (3) unverified host keys permitting MITM; (4) shell injection via interpolated command strings; (5) worker exhaustion during large transfers or a hung remote; (6) credential/key sprawl and rotation debt across environments; (7) partial file writes with no atomic-rename or checksum step. Mitigations are cheap and should be non-negotiable: assert exit code on every call, enable host key verification, pass argv arrays and escape, set explicit timeouts, write-then-rename for uploads, alert on any nonzero exit, and log command intent without secrets.
Ramp-up. Developer ramp-up is a day; the API is small and fluent. Operational ramp-up is the real cost — SSH semantics, key custody, host verification, exit-code discipline, and an exit plan. Estimate one engineer-week to a hardened, wrapped, tested first use case, plus ongoing ownership. Recommendation: do not adopt for new work; if a legacy Laravel 8 app already depends on it, wrap it behind an interface, harden it, and schedule replacement as part of the framework upgrade rather than after it.
How can I help you explore Laravel packages today?