PHPackages                             inja-online/ftp-deployer - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [DevOps &amp; Deployment](/categories/devops)
4. /
5. inja-online/ftp-deployer

ActiveLibrary[DevOps &amp; Deployment](/categories/devops)

inja-online/ftp-deployer
========================

Deploy Laravel applications to cPanel-style FTP-only hosts with a temporary HTTP runner.

v0.1(1mo ago)834AGPL-3.0-or-laterPHPPHP ^8.1|^8.2|^8.3|^8.4|^8.5CI passing

Since Jul 3Pushed 1mo agoCompare

[ Source](https://github.com/inja-online/ftp-deployer)[ Packagist](https://packagist.org/packages/inja-online/ftp-deployer)[ Docs](https://github.com/injaonline/ftp-deployer)[ RSS](/packages/inja-online-ftp-deployer/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (1)Dependencies (6)Versions (2)Used By (0)

Laravel FTP Deployer
====================

[](#laravel-ftp-deployer)

Deploy Laravel apps to FTP-only cPanel/shared hosts without SSH.

This package uploads ZIP artifacts over FTP by default, skips unchanged `vendor/` uploads with a remote manifest, then runs remote Laravel maintenance commands through a short-lived tokenized HTTP runner. Disable archive mode to fall back to recursive changed-file FTP uploads.

Use it when shared hosting gives you FTP/FTPS but no SSH, no remote Composer, and no safe way to run Laravel maintenance commands after upload.

What problem does this solve?
-----------------------------

[](#what-problem-does-this-solve)

After multiple privilege escalation vulnerabilities, such as CVE-2021-4034 “PwnKit” and CVE-2021-3156 “Baron Samedit”, showed non-root to root escalation was possible, many hosts restricted or disabled SSH access for deployment. OpenSSH hardening guidance and CIS Benchmarks pushed the same direction.

At the same time, many hosts still lacked built-in deployment tooling, especially shared-hosting panels like DirectAdmin with limited automation APIs.

This gap created the need for a minimal, secure deployment path that does not rely on full SSH access.

GitHub:

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 10, 11, 12, or 13
- PHP `ftp` extension
- PHP `zip` extension locally and on the remote host when archive mode is enabled
- FTP/FTPS access to your host
- Public HTTPS URL for the deployed app
- Local/CI build already completed for frontend assets

Install
-------

[](#install)

### From Packagist

[](#from-packagist)

```
composer require inja-online/ftp-deployer
php artisan vendor:publish --tag=ftp-deployer-config
```

### From a local directory (Local Install)

[](#from-a-local-directory-local-install)

Use this if you want to download the package code directly and put it in a local directory inside your Laravel application.

1. Download or clone this repository and place the files in a local folder within your Laravel project, e.g., `packages/ftp-deployer`.
2. Add a `path` repository pointing to your local folder in your Laravel application's `composer.json`:

```
{
  "repositories": [
    {
      "type": "path",
      "url": "packages/ftp-deployer",
      "options": {
        "symlink": true
      }
    }
  ]
}
```

*(Note: Set `"symlink": false` if you want Composer to mirror/copy the package files instead of creating a symlink, which is useful in environments like Docker or shared hosts where symlinks might not be supported or desired).*

3. Run the require command:

```
composer require inja-online/ftp-deployer:dev-main
php artisan vendor:publish --tag=ftp-deployer-config
```

### From custom GitHub repository

[](#from-custom-github-repository)

Use this when installing from a fork, private repo, or before Packagist release.

Add repository to your Laravel app `composer.json`:

```
{
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/inja-online/ftp-deployer"
    }
  ]
}
```

Then require package:

```
composer require inja-online/ftp-deployer:dev-main
php artisan vendor:publish --tag=ftp-deployer-config
```

For a fork/private repo, replace URL:

```
{
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/YOUR-USER/YOUR-REPO"
    }
  ]
}
```

Then install from chosen branch:

```
composer require inja-online/ftp-deployer:dev-main
```

Configure
---------

[](#configure)

Published config file:

```
config/ftp-deployer.php

```

Add deploy values to your local/CI `.env`:

```
FTP_DEPLOYER_PROFILE=production
FTP_DEPLOYER_HOST=ftp.example.com
FTP_DEPLOYER_USERNAME=ftp-user
FTP_DEPLOYER_PASSWORD=secret
FTP_DEPLOYER_PORT=21
FTP_DEPLOYER_SSL=false
FTP_DEPLOYER_PASSIVE=true
FTP_DEPLOYER_FTP_ROOT=/public_html
FTP_DEPLOYER_APP_URL=https://laravelapp.inja.online

# Archive deploys are enabled by default.
# This must be the PHP filesystem path matching FTP_DEPLOYER_FTP_ROOT.
FTP_DEPLOYER_FILESYSTEM_ROOT=/home/your-user/public_html/laravelapp.inja.online
FTP_DEPLOYER_ARCHIVE_ENABLED=true

# Default layout
FTP_DEPLOYER_MODE=simple
FTP_DEPLOYER_APP_ROOT=app
FTP_DEPLOYER_PUBLIC_ROOT=app/public
```

Default remote layout:

```
{ftp_root}/app/          Laravel root: artisan, vendor, .env, bootstrap, storage
{ftp_root}/app/public/   public root and temporary runner location
{ftp_root}/.ftp-deployer temporary manifests and archive uploads

```

If your domain root is `FTP_DEPLOYER_FTP_ROOT=/public_html/laravelapp.inja.online`, set `FTP_DEPLOYER_FILESYSTEM_ROOT` to the matching absolute PHP path, for example `/home//public_html/laravelapp.inja.online`.

Remote `.env` must already exist and contain `APP_KEY`. This package does not upload your local `.env`.

Deploy
------

[](#deploy)

Build your app locally or in CI first:

```
composer install --no-dev --prefer-dist --optimize-autoloader
npm ci
npm run build
```

For Bun/React/Vite projects, build with Bun instead:

```
bun install --frozen-lockfile
bun run build
```

No frontend? Skip Node/Bun entirely; if no `package.json` exists, frontend detection is skipped automatically.

Run deploy:

```
php artisan ftp-deploy production
```

For automation or AI agents, use structured JSON output:

```
php artisan ftp-deploy production --format=agent
```

Default remote commands:

```
migrate --force
optimize:clear
optimize
storage:link

```

Change them in `config/ftp-deployer.php` under `remote_commands`:

```
'remote_commands' => [
    'migrate --force',
    'app:setup:cache',
    'optimize:clear',
    'optimize',
    ['command' => 'storage:link', 'ignore_failures' => true],
],
```

For Bun/React builds, no-frontend apps, custom commands, queues, caches, and CI, see [Cookbook &amp; Examples](docs/src/pages/cookbook.md). For customization and overrides, see [Extending &amp; Overriding](docs/src/pages/extending.md).

Connection Check
----------------

[](#connection-check)

Before performing a deploy, you can verify your FTP connection settings (host, username, password, port, SSL, and passive mode) using:

```
php artisan ftp-deploy:check production
```

For automation or AI agents, use structured JSON output:

```
php artisan ftp-deploy:check production --format=agent
```

Migration to Versioned Mode
---------------------------

[](#migration-to-versioned-mode)

If you have an existing profile configured in `simple` mode and want to upgrade it to `versioned` mode, you can use the interactive migration command:

```
php artisan ftp-deploy:migrate production
```

Arguments &amp; Options:

- `profile` (optional): The profile key to migrate (defaults to `production`).
- `--write`: Automatically updates/appends the required environment variables to your local `.env` file (if writable).
- `--format=agent`: Emits a structured JSON payload instead of interactive prompts, making it ideal for automation.

### Remote Server Changes

[](#remote-server-changes)

Note that the migration command only updates your local configuration and **does not touch your remote `.env`**. To complete the transition:

1. Log into your FTP server.
2. Move your remote `.env` from `{ftp_root}/{app_root}/.env` to `{ftp_root}/{shared_root}/.env`.
3. Move your remote `storage/` directory to `{ftp_root}/{shared_root}/storage/`.
4. Ensure the remote `storage/` directory remains writable (`chmod -R 775` or equivalent).
5. Run `php artisan ftp-deploy ` to deploy the bootloader and link the new release layout.

Archive deploy mode
-------------------

[](#archive-deploy-mode)

Archive mode is enabled by default. It uploads an app ZIP every deploy and a vendor ZIP only when `composer.json` or `composer.lock` changes. Temporary ZIPs use random filenames under `.ftp-deployer/archives/`, are protected with deny `.htaccess` files, and are deleted after extraction. SQLite files under `database/` are excluded from app archives. ZIP passwords are not used in v1.

After extraction succeeds, stale files from the previous manifest are deleted, then the new manifest is saved before remote commands run. If extraction fails, the manifest and stale files are left unchanged. If your host times out during extraction or filesystem-root mapping is unavailable, set `FTP_DEPLOYER_ARCHIVE_ENABLED=false` to use recursive FTP uploads.

Simple mode extracts over live files and is best-effort. Use versioned mode for safer releases.

Versioned deploy mode
---------------------

[](#versioned-deploy-mode)

Simple mode uploads to stable app/public paths. Versioned mode uploads app code into release directories and updates a current-release pointer after success.

```
FTP_DEPLOYER_MODE=versioned
FTP_DEPLOYER_RELEASE_ROOT=../app/releases
FTP_DEPLOYER_SHARED_ROOT=../app/shared
FTP_DEPLOYER_CURRENT_PATH=../app/current
```

AI Agent Integration
--------------------

[](#ai-agent-integration)

This repository includes a pre-configured AI Agent Skill (`ftp-deployer`) to help autonomous coding assistants (such as Google Antigravity or Claude Code) run, configure, and troubleshoot deployments safely.

To install this skill to your agent globally:

```
npx skills add inja-online/ftp-deployer --skill ftp-deployer
```

For more details on integration, configuration custom roots, and sample agent prompts, see the [AI Agent Integration docs](docs/src/pages/agent-skill.md).

Documentation
-------------

[](#documentation)

- [Installation](docs/src/pages/installation.md)
- [Configuration](docs/src/pages/configuration.md)
- [Cookbook &amp; Examples](docs/src/pages/cookbook.md)
- [Extending &amp; Overriding](docs/src/pages/extending.md)
- [CLI Commands](docs/src/pages/commands.md)
- [Troubleshooting](docs/src/pages/troubleshooting.md)

Security
--------

[](#security)

- Runner filename and token are random per deploy.
- Runner accepts JSON and returns JSON.
- Runner is deleted after deploy success or failure.
- `.env` is excluded by default.
- Use HTTPS for `FTP_DEPLOYER_APP_URL`.

Manual release workflow
-----------------------

[](#manual-release-workflow)

Repository includes a manual GitHub Actions workflow for tagged package releases:

1. Open GitHub → Actions → `manual-release`.
2. Click **Run workflow**.
3. Enter version like `v1.2.3`.
4. Optionally mark as prerelease or add release notes.

The workflow validates Composer metadata, runs tests, PHPStan, lint dry-run, creates an annotated Git tag, then creates the GitHub release. Packagist can pick up the tag from GitHub.

Useful commands
---------------

[](#useful-commands)

```
composer test
composer stan
composer lint-test
```

License
-------

[](#license)

AGPL-3.0-or-later. See [LICENSE.md](LICENSE.md).

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance90

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

48d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/11869014?v=4)[Amin Sharifi](/maintainers/moaminsharifi)[@moaminsharifi](https://github.com/moaminsharifi)

---

Top Contributors

[![moaminsharifi](https://avatars.githubusercontent.com/u/11869014?v=4)](https://github.com/moaminsharifi "moaminsharifi (6 commits)")

---

Tags

laravellaravel-deploymentlaravel-frameworkftplaraveldeployercpanelFTPDeployer

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/inja-online-ftp-deployer/health.svg)

```
[![Health](https://phpackages.com/badges/inja-online-ftp-deployer/health.svg)](https://phpackages.com/packages/inja-online-ftp-deployer)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[spatie/laravel-prometheus

Export Laravel metrics to Prometheus

2861.8M11](/packages/spatie-laravel-prometheus)[api-platform/laravel

API Platform support for Laravel

58190.1k21](/packages/api-platform-laravel)[forjedio/inertia-table

Backend-driven dynamic tables for Laravel + Inertia.js

272.0k](/packages/forjedio-inertia-table)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
