PHPackages                             drops/drops - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. drops/drops

ActiveProject[Utility &amp; Helpers](/categories/utility)

drops/drops
===========

DROPS — Drupal Remote Operations and Pipeline System

v1.6.0(1w ago)012↓50%MITPHPPHP &gt;=8.2CI passing

Since May 3Pushed 1w agoCompare

[ Source](https://github.com/deburca/drops)[ Packagist](https://packagist.org/packages/drops/drops)[ RSS](/packages/drops-drops/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (7)Dependencies (9)Versions (15)Used By (0)

DROPS — Drupal Remote Operations and Pipeline System
====================================================

[](#drops--drupal-remote-operations-and-pipeline-system)

A PHP command-line tool for deploying Drupal websites between environments. Built on [Symfony Console](https://symfony.com/doc/current/components/console.html), distributed as a Composer package, and designed to align naturally with the Drupal developer ecosystem.

DROPS models a deployment as a two-phase operation:

1. **Export** — run on the source environment to produce a portable deployment package (`.tar.gz`)
2. **Import** — run on the target environment to apply the deployment package

The package can be transferred by any means (scp, SFTP, S3, shared NFS, etc.).

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

[](#requirements)

RequirementVersion / DetailsPHP8.2 or higherPHP extensions`json`, `phar`, `zlib`System tools`rsync` (file transfer steps), `gzip` (database dump compression)SSHSSH agent or key file on the machine running DROPS (for remote environments)Installation
------------

[](#installation)

### Option 1: Global Composer install (recommended for Drupal developers)

[](#option-1-global-composer-install-recommended-for-drupal-developers)

```
composer global require drops/drops
```

This adds `drops` to `~/.composer/vendor/bin/drops`. Ensure that directory is on your `$PATH`:

```
# Add to ~/.zshrc or ~/.bashrc:
export PATH="$HOME/.composer/vendor/bin:$PATH"
```

### Option 2: Clone and symlink

[](#option-2-clone-and-symlink)

```
git clone https://github.com/deburca/drops.git
cd drops
composer install
ln -s "$(pwd)/bin/drops" /usr/local/bin/drops
```

### Option 3: Project-local install

[](#option-3-project-local-install)

```
composer require drops/drops
./vendor/bin/drops --version
```

### Verify installation

[](#verify-installation)

```
drops --version
# DROPS — Drupal Remote Operations and Pipeline System 1.0.0
```

Configuration
-------------

[](#configuration)

All configuration lives in a single directory (default: `~/.drops/`, override with `--config-dir`). Configuration files are written in YAML.

### Directory layout

[](#directory-layout)

```
~/.drops/
├── environments/
│   ├── production.yml
│   ├── staging.yml
│   └── local-dev.yml
├── applications/
│   ├── acme-corp.yml
│   └── staff-intranet.yml
└── steps.php              # Optional: register custom steps

```

Create the directory structure:

```
mkdir -p ~/.drops/environments ~/.drops/applications
```

### Environment configuration

[](#environment-configuration)

Each environment describes *how* to reach a machine and *where* the Drupal site lives.

**SSH environment:**

```
# ~/.drops/environments/production.yml
id: production
label: "Production Server"

access:
  type: ssh
  host: prod.example.com
  port: 22
  user: deploy
  identity_file: ~/.ssh/id_ed25519   # Optional; omit to use SSH agent

paths:
  webroot: /var/www/drupal/web
  drush: /var/www/drupal/vendor/bin/drush
  php: /usr/bin/php8.2
  temp: /tmp/drops
  private_files: /var/private-files/drupal   # Optional; Drupal's file_private_path

env_vars:
  APP_ENV: production
```

**Local environment:**

```
# ~/.drops/environments/local-dev.yml
id: local-dev
label: "Local Development"

access:
  type: local

paths:
  webroot: /home/alice/projects/acme/web
  drush: /home/alice/projects/acme/vendor/bin/drush
```

**Multi-site environment:**

For Drupal multi-site installs where multiple sites share a single codebase, add a `uri` field. This tells DROPS which site to target — Drush commands will include `--uri`, and file operations will use `sites//` instead of `sites/default/`.

Create one environment config per site:

```
# ~/.drops/environments/production-site-a.yml
id: production-site-a
label: "Production — Site A"

access:
  type: ssh
  host: prod.example.com
  user: deploy

paths:
  webroot: /var/www/drupal/web
  drush: /var/www/drupal/vendor/bin/drush

uri: site-a.example.com
```

```
# ~/.drops/environments/production-site-b.yml
id: production-site-b
label: "Production — Site B"

access:
  type: ssh
  host: prod.example.com
  user: deploy

paths:
  webroot: /var/www/drupal/web
  drush: /var/www/drupal/vendor/bin/drush

uri: site-b.example.com
```

When `uri` is omitted, DROPS behaves as a standard single-site install (`sites/default/`).

**DDEV environment:**

For DDEV projects, set `access.exec` to route all commands through the container. Use container paths for `webroot` and `drush`:

```
# ~/.drops/environments/ddev-local.yml
id: ddev-local
label: "Local DDEV"

access:
  type: local
  exec: ddev exec -p my-project     # All commands run inside the DDEV web container

paths:
  webroot: /var/www/html/web         # Container path (DDEV mounts project to /var/www/html)
  drush: drush                       # Drush is on PATH inside the container
  temp: /tmp/drops                   # Host path — package staging stays on the host
```

**Lando environment:**

```
# ~/.drops/environments/lando-local.yml
id: lando-local
label: "Local Lando"

access:
  type: local
  exec: lando ssh -c                 # All commands run inside the Lando appserver

paths:
  webroot: /app/web                  # Container path (Lando mounts project to /app)
  drush: drush
  temp: /tmp/drops
```

The `access.exec` option wraps all executed commands (Drush, database, hooks) with the given prefix so they run inside the container. File operations (upload/download) stay on the host since the project filesystem is volume-mounted.

### Application configuration

[](#application-configuration)

Each application defines a Drupal site and which deployment steps it requires.

Steps not listed in the configuration are **enabled by default**. You only need to explicitly list steps you want to disable (`false`). This keeps application configs concise — most sites can omit the `steps` section entirely and get a full deployment pipeline.

**Full example (with selective step disabling):**

```
# ~/.drops/applications/acme-corp.yml
id: acme-corp
label: "ACME Corp Website"

steps:
  pre_hooks: false          # Only list steps you want to disable
  post_hooks: false

step_config:
  database_export:
    skip_data_tables:
      - cache
      - cache_*
      - watchdog
      - sessions

  config_export:
    sync_dir: ../config/sync

  config_import:
    sync_dir: ../config/sync

  files_export:
    directories:
      - files
    exclude:
      - "*.log"
      - ".DS_Store"

  files_import:
    directories:
      - files
    delete_removed: false

  pre_hooks:
    export_scripts:
      - hooks/pre-export.sh
    import_scripts:
      - hooks/pre-import.sh

  post_hooks:
    export_scripts:
      - hooks/post-export.sh
    import_scripts:
      - hooks/post-import.sh

import_options:
  create_rollback_package: true
  rollback_package_dir: /var/backups/drops/
```

**Multi-site application:**

For multi-site installs, you can set a `uri` on the application config. This takes precedence over the environment-level `uri`, allowing a single environment config to serve multiple applications that each target a different site:

```
# ~/.drops/applications/site-a.yml
id: site-a
label: "Site A"
uri: site-a.example.com      # Overrides environment-level URI
```

**Minimal example (database updates and cache rebuild only):**

```
# ~/.drops/applications/staff-intranet.yml
id: staff-intranet
label: "Staff Intranet"

steps:
  pre_hooks: false
  maintenance_on: false
  database_export: false
  files_export: false
  config_export: false
  config_import: false
  files_import: false
  database_import: false
  post_hooks: false
  # database_update, cache_rebuild, and maintenance_off remain enabled by default
```

Usage
-----

[](#usage)

### Verify connectivity

[](#verify-connectivity)

```
drops ping --env=production
```

### Validate configuration

[](#validate-configuration)

```
drops validate --all
drops validate --app=acme-corp --env=production
```

### Export a deployment package

[](#export-a-deployment-package)

```
drops export \
  --app=acme-corp \
  --env=production \
  --output=./packages/acme-$(date +%Y%m%d-%H%M%S).tar.gz
```

### Import a deployment package

[](#import-a-deployment-package)

```
drops import \
  --app=acme-corp \
  --env=staging \
  --package=./packages/acme-20250503-141500.tar.gz
```

### Dry run

[](#dry-run)

```
drops import \
  --app=acme-corp \
  --env=production \
  --package=./deploy.tar.gz \
  --dry-run
```

### Override steps at runtime

[](#override-steps-at-runtime)

```
# Run only specific steps
drops import --app=acme-corp --env=staging \
  --package=./deploy.tar.gz \
  --steps=database_update,cache_rebuild

# Skip specific steps
drops export --app=acme-corp --env=production \
  --output=./deploy.tar.gz \
  --skip-steps=files_export
```

### List resources

[](#list-resources)

```
drops list:environments
drops list:applications
```

Global options
--------------

[](#global-options)

```
--config-dir=PATH    Path to config directory (default: ~/.drops)
--dry-run            Print what would happen without executing
--continue-on-error  Continue running steps after a failure
--no-ansi            Disable terminal colour output
--help               Show help for a command
--version            Show DROPS version

```

Built-in deployment steps
-------------------------

[](#built-in-deployment-steps)

StepPhaseDescription`pre_hooks`BothRun user-defined scripts before all other steps`maintenance_on`ImportEnable Drupal maintenance mode`database_export`ExportDump the source database to the package`files_export`ExportArchive file assets from the source`config_export`ExportRun `drush config:export` and capture output`database_import`ImportRestore the database dump to the target`config_import`ImportRun `drush config:import` from the package`files_import`ImportRestore file assets from the package`database_update`ImportRun `drush updatedb` on the target`cache_rebuild`ImportRun `drush cache:rebuild` on the target`maintenance_off`ImportDisable Drupal maintenance mode`post_hooks`BothRun user-defined scripts after all other stepsCustom steps
------------

[](#custom-steps)

Custom steps extend DROPS with site-specific deployment logic. A custom step is a PHP class implementing `Drops\Step\StepInterface` with four methods:

MethodReturnsPurpose`getId()``string`Unique identifier used in application config`getLabel()``string`Human-readable name shown in progress output`getPhase()``Phase`When the step runs: `Phase::EXPORT`, `Phase::IMPORT`, or `Phase::BOTH``run(DeployContext $context)``StepResult`Execute the step; return success, failure, or skippedThe `DeployContext` passed to `run()` gives access to:

- `$context->environment` — execute commands on the target via `execute()`, `upload()`, `download()`
- `$context->appConfig` / `$context->envConfig` — full application and environment configuration
- `$context->drushCommand('...')` — build a Drush command with the correct path
- `$context->getStepConfig('step_id')` — read step-specific config from the application YAML
- `$context->output` — Symfony Console output for logging
- `$context->dryRun` — whether this is a dry run

### Example: Cache warming step

[](#example-cache-warming-step)

This step runs after import to warm Drupal's caches by requesting key URLs:

```
