Skip to content
Published on

Shippy vs. Deployer vs. Capistrano

Authors

Part one of this series deployed TYPO3 and Pimcore with Shippy; part two covered its backup tooling. Shippy's own tagline describes it as "inspired by Deployer and Capistrano — without the runtime," which is a precise enough summary that it's worth unpacking directly: what the three tools actually share, and what "without the runtime" means in practice.

The pattern all three use

Capistrano, Deployer and Shippy all deploy the same way: a releases/ directory holding timestamped, complete copies of the application; a current symlink pointing at whichever release is live; a shared/ directory for anything that must survive between releases (.env, uploads, logs). Deploying builds a new release directory, runs whatever setup commands the project needs inside it, and only then repoints current — an atomic filesystem operation, so no request ever sees a half-deployed release. Rolling back is the same symlink swap pointed at an older release instead.

Capistrano originated this pattern for Ruby/Rails deployment. Deployer adopted it for PHP. Shippy adopted it too, explicitly citing both as prior art. If you've used any one of these three, the deployment model transfers directly to either of the other two — the differences are in how each one is configured and run, not in what happens on the server.

Comparison

ShippyDeployerCapistrano
Runtime needed to run a deployNone — static Go binaryPHP + ComposerRuby + gems
Config formatYAML (.shippy.yaml)PHP recipe files (deploy.php)Ruby DSL (Capfile, deploy.rb)
InstallHomebrew, go install, Docker, prebuilt binaryComposer / PharRubyGems / Bundler
File selectionDeny-by-default allowlist (include:) as of v0.1.0Copies most files, exclude-list-basedCopies most files, exclude-list-based
Framework recipesTYPO3 defaults built into shippy initLaravel, Symfony, WordPress, Yii and others, officialNone official; PHP support via third-party plugins
Built-in backup commandYes — shippy backup + shippy gitlab:uploadNoNo
Server provisioningNo (explicitly out of scope)Yes — dep provisionNo
Zero-downtime mechanismAtomic symlink swapAtomic symlink swapAtomic symlink swap (origin of the pattern)
Primary ecosystemPHP, TYPO3-awarePHPRuby/Rails, extended to other languages via plugins

What "without the runtime" means

Deployer is itself a PHP application, installed via Composer. Running dep deploy requires a working PHP installation — with the right extensions — on whichever machine issues the command, local or CI runner. Capistrano is a Ruby gem; running cap production deploy requires Ruby and Bundler on that same machine. Neither requirement touches the target server, which only needs SSH.

That doesn't mean either one is hard to run in CI — any of the three can be wrapped in a Docker image and run as docker run <image> deploy production, Shippy included. The distinction isn't "can it be containerized," it's what has to be inside that image, and whether you already have it for another reason. Shippy's official image is the binary and nothing else — no interpreter underneath it. A hypothetical Deployer or Capistrano image needs a full PHP or Ruby runtime baked in first, on top of the tool itself: more to pull, more to keep patched, more surface area before a deploy ever runs.

In practice that cost is often zero: a PHP project's CI usually runs PHP anyway, for composer install and the test suite, so Deployer running in that same job costs nothing extra — the interpreter it needs is already there for unrelated reasons. Same for Capistrano on a Ruby/Rails project. Where the static binary actually wins is a deploy-only stage or runner that has no other reason to carry a PHP or Ruby toolchain: a shared/minimal CI runner used across projects in different languages, or a laptop you don't want another version-managed interpreter on just to ship one PHP app. None of this touches the target server either way — all three tools only ever need SSH there, regardless of what runs the deploy command itself.

Configuration: YAML vs. a scripting DSL

Deployer and Capistrano are both programmable — a recipe or Capfile is real PHP or Ruby, so conditionals, loops, custom task ordering and reusable abstractions are all directly available:

// Deployer: deploy.php
task('deploy:announce', function () {
    if (currentUser() === 'ci') {
        run('curl -X POST $SLACK_WEBHOOK -d "Deploying $branch"');
    }
});

Shippy's .shippy.yaml is declarative data, not a script — no conditionals, no loops, no custom task functions. What it offers instead is only/except host scoping on individual commands and {{ }}/${} template substitution from composer.json and environment variables, which covers per-environment variation without needing a general-purpose language to express it:

commands:
  - name: Database migrations
    run: ./vendor/bin/typo3 upgrade:run
    only: [production]

This is a real trade-off, not a strictly-better-or-worse one. A YAML file is easier to read, diff, and validate (shippy config validate checks syntax and required fields before anything touches a server) — and it's impossible to write a deploy script that does something conditionally clever and hard to reason about. A PHP or Ruby recipe can express deployment logic no YAML file can, at the cost of a config file that's also an executable program.

What ships built in

Deployer's dep provision is a genuine capability neither of the others has: server provisioning — firewall, PHP, database, HTTPS — from the same tool that later deploys to it. If provisioning fresh servers is a recurring task, that's a real point in Deployer's favor.

Shippy's shippy backup + shippy gitlab:upload has no equivalent in either Deployer or Capistrano — both would need a separate script (or a third-party recipe/plugin) to produce a database dump and shared-file archive and push it somewhere. Part two of this series covers wiring that into a scheduled pipeline.

Deployer has the deepest out-of-the-box framework coverage of the three — official recipes for Laravel, Symfony, WordPress and others handle framework-specific deploy steps Shippy leaves to a project's own commands: list, and Shippy leaves entirely to TYPO3 (shippy init writes TYPO3-aware defaults; nothing else is built in).

When to pick which

Shippy fits a PHP project — TYPO3 in particular — where the deploy mechanism should be a config file, not a program, and where not needing a PHP/Ruby toolchain on the CI runner or laptop actually matters (slow CI images, air-gapped/DMZ hosts with restricted egress, minimal container images).

Deployer fits when framework-specific deploy logic goes beyond what a static config can express, when official Laravel/Symfony/WordPress recipes cover most of the work already, or when dep provision's server setup is genuinely useful.

Capistrano fits an existing Ruby/Rails codebase, or a polyglot deployment that already standardized on it before PHP entered the picture — using it for a pure-PHP project today means opting into a Ruby dependency and community PHP plugins for functionality Deployer or Shippy provide natively.

All three produce the same result on the server: a current symlink, a handful of old releases to roll back to, and a deployment that either fully succeeds or never goes live at all.