Open-source project
dg/ftp-deployment avatar
dg/ftp-deployment

ftp-deployment: a PHP script that makes FTP deploys repeatable and atomic

A tool for automated deployment of web applications to an FTP server.

618 stars88 forksPHPNOASSERTION

At a glance

What is it?
dg/ftp-deployment automates uploads to an FTP server from a PHP script driven by a deployment.ini file, with gitignore style excludes, pre-upload hooks, and a rename based commit step so a half-finished upload never lands. It is aimed at the large number of teams still shipping over FTP.
Who is it for?
ftp-deployment fits teams whose hosting provider gives them FTP and nothing better, and who want a deploy to be a repeatable command rather than a drag and drop session. It does not fit teams with SSH access to the target, where a transfer over SSH removes the whole class of problems this script works around, and it does not fit a codebase with no PHP available on the build machine.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The target audience is everyone still stuck on FTP

The README is blunt about the problem. There is nothing worse than uploading web applications to an FTP server manually with a tool like Total Commander, and editing files directly on the server while trying to keep some kind of synchronization going is described as even worse. The argument for automating it is not elegance but cost: once the process is automated it takes a fraction of the time and it removes the risk of forgetting to upload a file. The author also concedes that many sophisticated deploying techniques exist today, and then says the tool is designed for the many people who are still using FTP. That framing matters. ftp-deployment is not trying to be a general deployment platform, it is trying to make a legacy transport safe enough to script.

A deployment is one PHP command and an ini file

The mechanism is deliberately small. You tell the script which local folder to upload and where, and that information lives in a `deployment.ini` text file. You associate the config with the `deployment` script and deployment becomes a single command:

bash
php deployment deployment.ini

Only the `remote` item is required, everything else in the config is optional. Multiple sections can exist in one file, so a single project can carry separate rules for an application and for data, and configuration can also be stored in a PHP file instead, which the README links as `deployment.sample.php`. The PHP variant matters more than it first appears, because it lets the config be generated by code rather than checked in as a static file, and it allows hooks to be closures that receive the server, logger, and deployer objects.

One remote entry supports four protocols and keeps secrets out of the file

The `remote` key accepts more than plain FTP. The config comment lists `ftp://`, `ftps://`, `sftp://`, `file://`, and `phpsec://`, noting that sftp requires the SSH2 extension and phpsec uses the phpseclib library. The credentials can sit in the URL or in separate `user` and `password` keys, and any value may reference an environment variable, which the config file describes as handy for secrets in CI/CD:

ini
[my site] ; Optional section (there may be more than one section).
; remote FTP server
remote = ftp://user:[email protected]/directory
; you can use ftps://, sftp://, file:// or phpsec:// protocols (sftp requires SSH2 extension; phpsec uses phpseclib library)
; any value may reference an environment variable, which is handy for secrets in CI/CD
; (quote it in .ini configs, because INI treats the parentheses as special otherwise):
password = "%env(FTP_PASSWORD)%"

The comment about quoting is a real INI quirk, since parentheses are otherwise treated as special. There is also a `hostKey` option for sftp and phpsec connections that accepts a full public key, a SHA256 fingerprint, or an MD5 fingerprint, and the config comments frame it as protection against man-in-the-middle attacks. For a script that holds production credentials, that check is the setting worth turning on first. FTP passive mode is a separate `passiveMode` switch.

The ignore list follows gitignore syntax, not a file extension list

Exclusions are the part of a deploy config that quietly causes outages, and ftp-deployment borrows the syntax developers already know. The `ignore` item uses the same format as `.gitignore`, and the README lists the cases that matter: `log` ignores all log files or directories in all subfolders, `/log` ignores the log entry in the root only, `app/log` ignores the one inside the app folder in the root, `data/*` ignores everything inside the data folder while still creating that folder on the FTP server, `!data/db/file.sdb` makes an exception to the previous rule, and a pattern like `project.pp[jx]` matches either extension. There is also an `include` item for an explicit whitelist, which by default includes everything. A related option, `hashCache`, caches local file hashes by path, size and mtime to speed up rescans of large trees, and it defaults to no.

Hooks run before, during, and after the upload

The config supports four kinds of scheduled work. The `before` array runs jobs before the upload starts, and the comment notes that local jobs are done even when there is no need for synchronization, which is how a build step gets a guarantee. `afterUpload` runs after the files are uploaded but before they are renamed. `after` runs once everything is done, with local jobs again running even if no synchronization happened. Each job has a prefix that says where it runs: `local:` for a shell command on the build machine, `remote:` for a command on the server, `upload:` to send a specific local file, and `http://` to call a URL. A `purge` array removes directories on the server after the upload. The `preprocess` option names file patterns to process before uploading, and `deploymentFile` names the file that stores hashes of all uploaded files, defaulting to `.deployment`. There is a `filePermissions` and `dirPermissions` pair for new files and directories, which matters on shared hosting where the FTP account and the web server do not share a group.

Files land as .deploytmp and are renamed, which is the closest thing to atomicity

The README describes syncing a large number of files as attempting to run in something like a transaction. Every file is uploaded with the extension `.deploytmp` and then quickly renamed. That is what stops a visitor from hitting `index.php` while it is still half written, and it is the single feature that a naive FTP upload script cannot reproduce. The rename step is also where the `afterUpload` hook sits, which is the natural place to switch a maintenance mode or warm a cache. Two related mechanisms guard concurrency. While a deployment runs, a `.htdeployment.running` marker exists on the server, and if that marker is still there when a new deployment starts, because another machine is running one or a previous run was interrupted, the tool refuses to start unless `--force` is passed. Separately, an interrupted run is recoverable: pressing Ctrl+C during a deployment opens an interactive prompt offering to skip the current operation and continue with the next, or to resume.

Test mode, then the licence and the release history

You can rehearse a deployment before touching the server. The config has a `test` item that defaults to no, and the README says it can also be enabled with the `-t` or `--test` option, in which case uploading and deleting files is skipped so you can verify your settings. The `allowDelete` option controls whether remote files may be deleted, and it defaults to yes, which is the setting most worth reviewing before the first run against a live site. On licensing, the repository badge reads New BSD, while GitHub classifies the licence file as a custom licence it cannot categorise, so read `license.md` yourself rather than relying on the badge. The repository also carries `composer.json`, `phpstan.neon` for static analysis, `dresscode.neon` for coding style checks, and a `tests` directory wired to a GitHub Actions workflow. Releases move at a steady but unhurried pace: v3.6.1 in March 2024, v3.6.2 in March 2025, and v3.7.0 in April 2026, with the last push on 2026-10-19.

Editorial conclusion

ftp-deployment fits teams whose hosting provider gives them FTP and nothing better, and who want a deploy to be a repeatable command rather than a drag and drop session. It does not fit teams with SSH access to the target, where a transfer over SSH removes the whole class of problems this script works around, and it does not fit a codebase with no PHP available on the build machine. Before adopting it, read `deployment.sample.ini` end to end and decide your exclude rules first, because the ignore list is where most deploy failures come from, and check the New BSD licence file yourself, since the repository is labelled with a custom licence that GitHub cannot classify.

Frequently asked questions

How do I run an ftp-deployment?

The script is a PHP file named `deployment` and it is invoked with a config file, for example `php deployment deployment.ini`. Only the `remote` item in that ini file is required, and the same config can hold several sections for different sites.

Can ftp-deployment use SFTP instead of FTP?

Yes. The `remote` key accepts `ftp://`, `ftps://`, `sftp://`, `file://`, and `phpsec://`, with sftp requiring the SSH2 extension and phpsec using the phpseclib library. A `hostKey` option verifies the SSH server host key for sftp and phpsec connections using a public key or a fingerprint.

How do I keep FTP passwords out of the config file?

Any value in the ini file may reference an environment variable, and the README gives `password = "%env(FTP_PASSWORD)%"` as the example for CI/CD secrets. The value must be quoted, because INI treats the parentheses as special otherwise.

How does ftp-deployment avoid leaving a half-uploaded site online?

Files are uploaded with a `.deploytmp` extension and then quickly renamed, so the live files are replaced in one step after the upload completes. A `.htdeployment.running` marker also exists on the server during a deployment, and the tool refuses to start a second run while that marker is present unless you pass `--force`.

Can I test a deployment without uploading anything?

Yes. The config has a `test` item, and the `-t` or `--test` option enables it, in which case uploading and deleting files is skipped so you can verify the settings first. You can also press Ctrl+C during a run to skip the current operation and continue with the next one.

What licence is dg/ftp-deployment under?

The repository badge reads New BSD, while GitHub classifies the licence as a custom one it cannot categorise, and the licence text lives in the `license.md` file at the root. Check that file directly for the terms that apply to your use.

Official sources

  1. dg/ftp-deployment on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dg-ftp-deployment.svg)](https://hysenlabs.com/projects/dg-ftp-deployment)

Was this useful?