Ivory is a cross-platform CLI tool that manages local PHP runtimes and runs Composer and PHP scripts in a consistent, project-aware way.
It lets you:
- Install and manage multiple PHP versions under
~/.ivory - Pick a default (global) PHP version and per-project version
- Generate a
ivory.jsonproject config - Run PHP directly via Ivory
- Proxy Composer commands through a known PHP version
- Define reusable “scripts” in
ivory.jsonand run them easily - Inspect your environment and check what Ivory sees (
ivory doctor)
-
Local PHP version management
- Installs PHP builds under
~/.ivory/versions/<platform>/<version> - Uses a JSON manifest (
~/.ivory/php-versions.json) to know where to download PHP from iv install 8.3etc.- If a command resolves to a non-system PHP version that isn't installed yet, Ivory will install it automatically before running your command.
- Installs PHP builds under
-
Project-aware PHP selection
- Per-project PHP version via
.ivory.php-version - Global default PHP version via
ivory default - Falls back to system
phpwhen appropriate
- Per-project PHP version via
-
Project configuration with
ivory.jsonphp.version, extra INI and CLI args- Named scripts that describe how to run your app/tools
- Framework presets (Generic, Laravel, Symfony) via
ivory init
-
Composer integration
-
Finds or downloads a
composer.phar -
Runs Composer using a specific PHP version
-
Can run Composer scripts through your configured PHP toolchain
-
Nice DX
- Uses
System.CommandLinefor a clean CLI - Uses
ivory doctorto show what Ivory sees (PHP, Composer, config, etc.) - Scaffolds a basic
public/index.phpthat tries to loadvendor/autoload.php - Optional per-project PHP home (
.ivory/php) to keep ini overrides local
- Uses
-
Downloads come from the latest release at https://github.com/rizwan3d/Ivory/releases/latest/download/<platform-archive>.
# Linux (x64)
bash <(curl -fsSL https://raw.githubusercontent.com/rizwan3d/Ivory/refs/heads/master/install/linux.sh)
# macOS (arm64 or x64)
bash <(curl -fsSL https://raw.githubusercontent.com/rizwan3d/Ivory/refs/heads/master/install/mac.sh)# Windows (PowerShell 5+; AMD64 or ARM64)
powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/rizwan3d/Ivory/refs/heads/master/install/windows.ps1 | iex"The installers place ivory under a user-scoped bin directory (~/.ivory/bin on Unix, %LOCALAPPDATA%\Ivory\bin on Windows) and add it to PATH.
# Linux/macOS
bash <(curl -fsSL https://raw.githubusercontent.com/rizwan3d/Ivory/refs/heads/master/install/uninstall.sh)# Windows (PowerShell 5+)
powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/rizwan3d/Ivory/refs/heads/master/install/uninstall.ps1 | iex"Ivory looks for a ivory.json starting from the current directory and walking up parent directories.
{
"php": {
"version": "8.3",
"ini": [
"memory_limit=512M"
],
"args": [
"-d", "date.timezone=UTC"
]
},
"scripts": {
"serve": {
"description": "Run PHP built-in server",
"phpFile": "-S",
"phpArgs": [
"localhost:8000",
"public/index.php"
],
"args": []
},
"console": {
"description": "Run framework console",
"phpFile": "bin/console",
"phpArgs": [],
"args": []
}
}
}-
phpversion(string, optional) - preferred PHP version for this project (e.g."8.3"or"latest").ini(array of string) - extra-dINI settings to pass to PHP.args(array of string) - extra arguments always passed to PHP.
-
scripts(object)-
keys = script names (e.g.
"serve","test","console"). -
values = IvoryScript:
description(string, optional) – human-readable description.phpFile(string, required) – the PHP entry file or command (e.g."public/index.php","bin/console","-S").phpArgs(array of string, optional) – arguments placed before thephpFileor used as raw PHP arguments.args(array of string, optional) – additional arguments appended afterphpFile/phpArgs.
-
Internally, these are represented by IvoryConfig and IvoryConfig.IvoryScript in Ivory.Domain.Config.
Create a starter ivory.json for the current project and scaffold public/index.php if it doesn’t exist.
iv init [--framework <Generic|Laravel|Symfony>] [--php <version>] [--force]--framework– choose a preset (Generic, Laravel, Symfony).--php– default PHP version to write intoivory.json.--force– overwrite an existingivory.json.
This uses IPhpVersionResolver, IvoryConfigSerializer and IPublicIndexScaffolder under the hood.
Install a PHP runtime for the current platform.
iv install <version>Examples:
iv install 8.3
iv install 8.2This calls IPhpInstaller.InstallAsync, which:
- Looks up the version in
~/.ivory/php-versions.json(PhpVersionsManifest). - Downloads the appropriate artifact.
- Verifies SHA256.
- Extracts it into
~/.ivory/versions/<platform>/<version>.
List installed PHP versions:
iv list
# or
iv lsUses IPhpInstaller.ListInstalledAsync() and prints out versions in order.
Run php through Ivory with a specific PHP version (or project/global default).
iv php [--php <version>] [--] [args...]Examples:
iv php -- -v
iv php --php 8.3 -- -v
iv php --php 8.3 -- -r "echo 'Hello from Ivory';"This calls IPhpRuntimeService.RunPhpAsync with null script path and just forwards your arguments.
Run a named script from ivory.json or a direct PHP file.
iv run <script-or-file> [--php <version>] [--] [extra-args...]Behavior:
-
Ivory loads the nearest
ivory.jsonviaIProjectConfigProvider. -
If
<script-or-file>matches a script name inscripts, it:- Builds the PHP command from
php,phpFile,phpArgs, andargs.
- Builds the PHP command from
-
Otherwise, it treats
<script-or-file>as a path to a PHP file relative to the current directory.
Examples:
iv run serve
iv run console -- migrate
iv run public/index.php --php 8.3List available scripts from ivory.json:
iv scriptsShows:
- Script name
- Description
- The effective PHP command line that would be executed.
Set the project-local PHP version by writing .ivory.php-version in the current directory:
iv use <version>Example:
iv use 8.3Ivory will prefer this version when running commands inside this project directory.
Set the default / global PHP version (e.g. stored in ~/.ivory/config.json or equivalent):
iv default <version>Example:
iv default 8.2Used when there is no .ivory.php-version and no php.version in ivory.json.
Run Composer through Ivory.
iv composer [--php <version>] [--] [args...]Examples:
iv composer install
iv composer update
iv composer run-script test
iv composer --php 8.3 -- installInternally, IComposerService:
- Locates
composer.jsonand project root. - Ensures a
composer.pharis available (downloading if needed). - Uses
IPhpRuntimeServiceto runphp composer.phar <args>with the chosen PHP version.
Create a per-project PHP home in .ivory/php/ with its own php.ini and conf.d/ directory for extension ini files.
iv isolateAfter running this once in a project, Ivory will automatically set PHPRC and PHP_INI_SCAN_DIR to use your local php.ini and conf.d/ when executing PHP/Composer through Ivory, keeping settings and extensions from bleeding across projects.
Generate CI templates wired to Ivory for GitHub Actions or GitLab CI.
iv scaffold:ci # GitHub by default
iv scaffold:ci --target gitlab
iv scaffold:ci --target both --forceCreates .github/workflows/ivory-ci.yml and/or .gitlab-ci.yml running dotnet build plus a Ivory doctor + PHP version check.
- Uses the resolved PHP version (project
.ivory.php-version→ivory.jsonphp.version→ global default); when resolution issystem, the workflow pins8.3so CI has a concrete PHP tag. - Pass
--forceto overwrite existing files.
Generate a Dockerfile and docker-compose.yml using the resolved PHP version.
iv scaffold:dockerProduces a simple Dockerfile (php:-cli) and docker-compose.yml exposing port 8000; extend to install needed PHP extensions.
- The PHP tag is taken from the resolved Ivory PHP version; if it would be
system, Ivory pins8.3for the Docker base image. - Use
--forceto overwrite existingDockerfile/docker-compose.yml.
Generate a shell completion script (bash, zsh, fish, PowerShell) that includes commands, ivory.json script names, and PHP versions/aliases from your manifest.
iv completion bash # bash
iv completion zsh # zsh
iv completion fish # fish
iv completion powershellTip: source <(ivory completion bash) or add to your shell init file.
Inspect environment and show what Ivory sees:
iv doctorTypical output includes:
- Current platform (
PlatformId) - Home directory and
~/.ivorylayout (versions, cache, manifest path) - Installed PHP versions
- Effective project / global PHP version
- Locations of
composer.pharand systemcomposer - Whether
ivory.jsonwas found and where - Whether per-project isolation is enabled for this directory (and paths to
.ivory/php/php.iniandconf.dif present)
This uses:
IEnvironmentProbeto find systemphp/composerIPhpInstallerto inspect installed versionsIComposerServiceto locate ComposerIProjectConfigProviderto findivory.json
Ivory stores its own data under:
~/.ivory/
php-versions.json # PhpVersionsManifest (download sources & checksums)
versions/ # Installed PHP runtimes, per platform/version
cache/php/ # Download cache for PHP archives
config.json # Global config (e.g. default PHP version)
Per-project data:
ivory.json– project configuration (PHP + scripts)..ivory.php-version– per-project PHP version pin.