Drupal
Install times for drupal/recommended-project, the Drupal starter in viv's benchmark corpus.
| Composer | viv | |
|---|---|---|
| Cold | 1.97s | 0.69s |
| Warm | 2.10s | 0.31s |
viv 0.12.0, composer 2.10.2, riff 0.0.7, vivacity 0.6.0, flags --no-plugins --no-scripts, 3 runs each, from a local mirror.
source: bench/results/corpus.md, section 2026-09-15T06:54:06Z (2026-09-15)
Plugins
| Plugin | viv |
|---|---|
| drupal/core-composer-scaffold | Native (src/plugins/drupal_scaffold.rs) (#93) |
| drupal/core-project-message | Known inert — Composer prints a message viv does not |
| drupal/core-recipe-unpack | Known inert for install/update — only subscribes to POST_UPDATE_CMD/POST_CREATE_PROJECT_CMD (via composer require/create-project), never plain install |
| composer/installers | Native (src/plugins/installers.rs), pure path mapping |
source: docs/plugin-strategy.md
Local
source: Using viv as composer
make install-shim installs a composer binary next to viv. Plain
make install installs viv only, and refreshes the shim only if one is
already there, so it never replaces your real Composer. Put it on PATH
ahead of the real Composer, or symlink it as composer in CI: everyday
install, dump-autoload, normalize and create-project run through
viv; every other command falls through to your real Composer install.1
This is also the cheapest way to check whether a project migrates cleanly:
alias composer to the shim and run your existing scripts unedited.
In GitHub Actions, one step installs viv and puts the shim first on PATH, so an existing composer install step runs through viv unedited:
- uses: svandragt/vivace/action@v0
- run: composer install --no-dev
The action downloads the release tarball for the runner's OS and architecture, checks it against the release's SHA256SUMS, and installs nothing else. Pin a release with with: { version: v0.21.0 }; set shim: false to get viv on PATH without the composer shim. Cache viv's store with actions/cache on ~/.cache/vivace, keyed on composer.lock.
A command or flag the shim doesn't understand falls back to the real
Composer with a note on stderr naming what wasn't understood, so a migration
that quietly stopped using viv is visible instead of just slower. Set
VIV_SHIM_STRICT=1 to make that fallback a hard error instead, for a CI job
that wants a red build rather than a silent return to Composer.
You can also point a script straight at viv. The CI idiom
--prefer-dist --no-interaction --no-progress already describes what viv
does, so viv install and viv dump-autoload accept those flags and
ignore them instead of failing.
Dockerfile
source: In a Dockerfile
To build a vendor/ stage without a PHP runtime, use the published image
in place of composer:2:
FROM ghcr.io/svandragt/vivace:0 AS vendor
COPY composer.json composer.lock ./
RUN ["viv", "install", "--no-dev"]
FROM php:8.4-fpm
COPY --from=vendor /app/vendor /app/vendor
Two things differ from the composer:2 stage it replaces:
- Write
RUNin exec form, as above. The image has no shell, so the familiarRUN viv install --no-devdoes not work. - The image runs
vivby default and ships thecomposershim beside it, soRUN ["composer", "install", "--no-dev"]works too if you would rather not edit the command. - The image carries no real Composer to fall back to, so a command or flag
the shim doesn't understand hard-errors there instead of silently running
Composer, the way it would on a machine that still has Composer installed.
composer --versionstill works: it prints the shim's own version.
Tags are :0.21, :0.21.0 and :0. There is no :latest: a moving tag
that silently resolves to nothing breaks scripted installs, which is the
mistake that kept releases/latest returning 404 for ten releases.
CI
source: In CI
The GitHub Action
In GitHub Actions, one step installs viv and puts the shim first on PATH, so an existing composer install step runs through viv unedited:
- uses: svandragt/vivace/action@v0
- run: composer install --no-dev
The action downloads the release tarball for the runner's OS and architecture, checks it against the release's SHA256SUMS, and installs nothing else. Pin a release with with: { version: v0.21.0 }; set shim: false to get viv on PATH without the composer shim.
See Using viv as composer for what the shim does and does not map to viv, and VIV_SHIM_STRICT for turning a silent Composer fallback into a hard build failure.
Caching the store
Cache viv's store with actions/cache on ~/.cache/vivace, keyed on composer.lock, so a runner that already built the cache once skips the network on every later run.
A runner with no PHP
A runner image with no PHP installed can still run PHP tooling: viv php
install 8.4 downloads a self-contained PHP build into the store, with no
system package manager involved, and viv run phpunit, viv run phpcs and
similar then run that tool on the pinned build. See A PHP per
project for the full command set.
Compatibility sweep
| Mode | Result | Details |
|---|---|---|
| dev | identical | plugins: native; composer 6567ms |
| no-dev | identical | plugins: native; composer 2163ms |
source: compat/results/v0.21.0.md
-
The shim maps
install,dump-autoload,normalize,create-project,update,requireandremovewith their supported flags (update's partial-update package arguments and-w/-Wincluded) toviv; everything else,search, an unrecognised flag, it hands through to the real Composer binary unchanged. PointVIV_COMPOSER_PATHat the real binary if it isn't first onPATH. The shim only has something to hand through to if a real Composer is onPATHin the first place: if there isn't one, those commands fail rather than silently falling back to viv.composer --versionis the exception: it printsviv <version> (composer shim), then the real Composer's own version if one is onPATH, and exits0either way. ↩