Download artifacts easily
DLoad simplifies downloading and managing binary artifacts for your projects. Perfect for development environments that require specific tools like RoadRunner, Temporal, or custom binaries.
DLoad solves a common problem in PHP projects: how to distribute and install necessary binary tools and assets alongside your PHP code. With DLoad, you can:
- Automatically download required tools during project initialization
- Ensure all team members use the same versions of tools
- Simplify onboarding by automating environment setup
- Manage cross-platform compatibility without manual configuration
- Keep binaries and assets separate from your version control
- Installation
- Quick Start
- Command Line Usage
- Configuration Guide
- Building Custom RoadRunner
- Custom Software Registry
- Use Cases
- API Rate Limits
- Failure Reporting
- Gitlab CI configuration
- Contributing
composer require internal/dload -W-
Install DLoad via Composer:
composer require internal/dload -W
Alternatively, you can download the latest release from GitHub releases.
-
Create your configuration file interactively:
./vendor/bin/dload init
This command will guide you through selecting software packages and create a
dload.xmlconfiguration file. You can also create it manually:<?xml version="1.0"?> <dload xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/php-internal/dload/refs/heads/1.x/dload.xsd" > <actions> <download software="rr" version="^2025.1.0"/> <download software="temporal" version="^1.3"/> </actions> </dload>
-
Download configured software:
./vendor/bin/dload get
-
Integrate with Composer (optional):
{ "scripts": { "post-update-cmd": "dload get --no-interaction -v || \"echo can't dload binaries\"" } }
# Create configuration file interactively
./vendor/bin/dload init
# Create configuration in specific location
./vendor/bin/dload init --config=./custom-dload.xml
# Create minimal configuration without prompts
./vendor/bin/dload init --no-interaction
# Overwrite existing configuration without confirmation
./vendor/bin/dload init --overwrite# Download from configuration file
./vendor/bin/dload get
# Download specific packages
./vendor/bin/dload get rr temporal
# Download with specific versions and minimum stability
./vendor/bin/dload get rr:2025.* dolt:1.44.1 mago:1.*@alpha
# Download with options
./vendor/bin/dload get rr --stability=beta --force| Option | Description | Default |
|---|---|---|
--path |
Directory to store binaries | Current directory |
--arch |
Target architecture (amd64, arm64) | System architecture |
--os |
Target OS (linux, darwin, windows) | Current OS |
--stability |
Release stability (stable, beta) | stable |
--config |
Path to configuration file | ./dload.xml |
--force, -f |
Force download even if binary exists | false |
--refresh, -r |
Check repositories for new releases even if the version registry is still fresh | false |
# List available software packages
./vendor/bin/dload software
# Show downloaded software
./vendor/bin/dload show
# Show specific software details
./vendor/bin/dload show rr
# Show all software (downloaded and available)
./vendor/bin/dload show --all# Build custom software using configuration file
./vendor/bin/dload build
# Build with specific configuration file
./vendor/bin/dload build --config=./custom-dload.xml| Option | Description | Default |
|---|---|---|
--config |
Path to configuration file | ./dload.xml |
The build command executes build actions defined in your configuration file, such as creating custom RoadRunner binaries with specific plugins.
For detailed information about building custom RoadRunner, see the Building Custom RoadRunner section.
The easiest way to create a configuration file is using the interactive init command:
./vendor/bin/dload initThis will:
- Guide you through selecting software packages
- Show available software with descriptions and repositories
- Generate a properly formatted
dload.xmlfile with schema validation - Handle existing configuration files gracefully
Create dload.xml in your project root:
<?xml version="1.0"?>
<dload xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/php-internal/dload/refs/heads/1.x/dload.xsd"
temp-dir="./runtime">
<actions>
<download software="rr" version="^2025.1" />
<download software="temporal" />
<download software="frontend" extract-path="frontend" />
</actions>
</dload>DLoad supports three download types that determine how assets are processed:
<!-- Explicit type specification -->
<download software="psalm" type="phar" /> <!-- Download .phar without unpacking -->
<download software="frontend" type="archive" /> <!-- Force archive extraction -->
<download software="rr" type="binary" /> <!-- Binary-specific processing -->
<!-- Automatic type handling (recommended) -->
<download software="rr" /> <!-- Uses all available handlers -->
<download software="frontend" /> <!-- Smart processing based on software config -->When type is not specified, DLoad automatically uses all available handlers:
- Binary processing: If software has
<binary>section, performs binary presence and version checking - Files processing: If software has
<file>section and asset is downloaded, processes files during unpacking - Simple download: If no sections exist, downloads asset without unpacking
<!-- registry list -->
<software name="complex-tool">
<binary name="tool" pattern="/^tool-.*/" />
<file pattern="/^config\..*/" extract-path="config" />
</software>
<!-- actions list -->
<!-- Uses both binary and files processing -->
<download software="complex-tool" />| Type | Behavior | Use Case |
|---|---|---|
binary |
Extracts the matched executable/files, flattened into the destination | CLI tools, executables |
phar |
Downloads .phar files as executables without unpacking |
PHP tools like Psalm, PHPStan |
archive |
Unpacks the whole asset, preserving its directory structure | Multi-file tools, frontend bundles, docs |
Note
Use type="phar" for PHP tools that should remain as .phar files.
Using type="archive" will unpack even .phar archives.
type="archive" unpacks the entire asset into extract-path, keeping the archive's internal directory layout instead of flattening matched files into a single folder:
- A single top-level directory wrapping the whole archive is stripped, like
tar --strip-components=1(sopkg-1.2.3/bin/applands asbin/app). <file>elements, when present, act as an include filter (matched by file name); omit them to extract everything.- A
<binary>, if configured, is used only to locate the executable inside the extracted tree (for the version check) and to set its executable bit — it is not moved out of its subdirectory.
This is required when files reference each other by relative path — for example a binary that resolves a shared library through an $ORIGIN/../lib rpath. Flattening would break that link; structure-preserving extraction keeps bin/ and lib/ in place relative to each other.
<!-- A self-contained tool: a binary plus its shared library, kept nested under ./runtime -->
<software name="Rapira" alias="rapira" description="PHP application server" homepage="https://rapira.rs/">
<repository type="github" uri="rapira-rs/rapira" asset-pattern="/^rapira-v.*-linux-.*/" />
<binary name="rapira" />
</software>
<download software="rapira" type="archive" extract-path="./runtime" />Use Composer-style version constraints:
<actions>
<!-- Exact version -->
<download software="rr" version="2.12.3" />
<!-- Range constraints -->
<download software="temporal" version="^1.20.0" />
<download software="dolt" version="~0.50.0" />
<!-- Stability constraints -->
<download software="tool" version="^1.0.0@beta" />
<!-- Feature releases (automatically sets preview stability) -->
<download software="experimental" version="^1.0.0-experimental" />
</actions><dload temp-dir="./runtime">
<actions>
<!-- Different extraction paths -->
<download software="frontend" extract-path="public/assets" />
<download software="config" extract-path="config" />
<!-- Target different environments -->
<download software="prod-tool" version="^2.0.0@stable" />
<download software="dev-tool" version="^2.0.0@beta" />
</actions>
</dload>Resolving a version means asking GitHub or GitLab for the repository's release list. DLoad keeps
what it learns in a local version registry: a small database of the releases and assets every
known repository offers, one JSON file per repository. Versions never expire from it. What expires
is the last check of a repository: while the check is younger than cache-ttl, dload get is
answered from the registry without a single API request. When it is older, DLoad asks the API only
for the releases published since the last check, which is usually one request.
Release pages are still loaded lazily. The first run fetches only as many pages as it takes to find a release that satisfies the requested version, and older releases are fetched later, on demand, when a run actually needs one of them.
The registry is on by default and lives in the per-user cache directory ($XDG_CACHE_HOME/dload,
%LOCALAPPDATA%\dload\cache on Windows, ~/.cache/dload otherwise):
<dload temp-dir="./runtime" cache-dir="./runtime/dload-cache" cache-ttl="3600">
<actions>
<download software="rr" />
</actions>
</dload>| Attribute | Environment variable | Default | Meaning |
|---|---|---|---|
cache-dir |
DLOAD_CACHE_DIR |
user cache directory | Directory of the version registry. |
cache-ttl |
DLOAD_CACHE_TTL |
600 |
Seconds the last check of a repository stays valid. 0 disables the registry. |
The environment variable takes precedence over the attribute in dload.xml, and the command line option over both.
# Check the repositories for new releases even if the last check is still fresh
./vendor/bin/dload get rr --refresh
# Forget the repositories a software package is served from, or the whole registry
./vendor/bin/dload cache:clear rr
./vendor/bin/dload cache:clear --forceNote
The registry holds release metadata only: tags, names and asset download links. Downloads never
go through it and credentials are never stored in it, so the directory can be shared or committed
to a CI cache freely. When a check fails because of a network error or a rate limit, the stored
releases are used instead, and a repository that was never seen before still fails loudly.
A stored release whose assets have disappeared upstream is dropped from the registry as soon as
its download fails, and the release list is fetched again before the run gives up.
GitHub draft releases are never served; the registry keeps only their tags, as hidden placeholders that hold the position in the listing. A release that a provider inserts below the top of its listing, such as a GitLab release with a backdated released_at, is not noticed by a check; run cache:clear for that software to pick it up.
In GitHub Actions the directory can be carried between workflow runs, so a run spends the rate limit only on releases published since the previous one:
- name: Restore DLoad version registry
uses: actions/cache@v4
with:
path: ./runtime/dload-cache
key: dload-registry-${{ github.run_id }}
restore-keys: dload-registry-
- run: ./vendor/bin/dload get
env:
DLOAD_CACHE_DIR: ./runtime/dload-cacheThe github.run_id in the key makes every workflow run save its registry, while restore-keys
lets the next run start from the most recent one. Jobs that run in parallel within one workflow do not
see each other's cache, since actions/cache saves it when a job ends.
DLoad supports building custom RoadRunner binaries using the Velox build tool. This is useful when you need RoadRunner with custom plugin combinations that aren't available in pre-built releases.
<actions>
<!-- Basic configuration using local velox.toml -->
<velox config-file="./velox.toml" />
<!-- With specific versions -->
<velox config-file="./velox.toml"
velox-version="2025.1.1"
golang-version="^1.22"
roadrunner-ref="v2025.1.2"
binary-path="./bin/rr"
debug="true" />
<!-- Custom plugins -->
<velox>
<plugin name="temporal" />
<plugin name="kv" />
</velox>
</actions>| Attribute | Description |
|---|---|
velox-version |
Version constraint for the Velox build tool to use |
golang-version |
Go version constraint required for building RoadRunner |
roadrunner-ref |
RoadRunner Git reference (tag, commit, or branch) to use as the base for building |
config-file |
Path to base configuration file that may be merged with remote API responses or other sources |
binary-path |
Output path for the built RoadRunner binary. File extension is automatically added based on OS (.exe for Windows). Defaults to current working directory |
debug |
Build RoadRunner with debug symbols to profile it with pprof (boolean, defaults to false) |
DLoad automatically handles the build process:
- Golang Check: Verifies Go is installed globally (required dependency)
- Velox Preparation: Uses Velox from global installation, local download, or downloads automatically if needed
- Configuration: Copies your local velox.toml to build directory
- Building: Executes
vx buildcommand with specified configuration - Installation: Moves built binary to target location and sets executable permissions
- Cleanup: Removes temporary build files
Note
DLoad requires Go (Golang) to be installed globally on your system. It does not download or manage Go installations.
You can generate a velox.toml configuration file using the online builder at https://build.roadrunner.dev/
For detailed documentation on Velox configuration options and examples, visit https://docs.roadrunner.dev/docs/customization/build
This web interface helps you select plugins and generates the appropriate configuration for your custom RoadRunner build.
You can download Velox as part of your build process instead of relying on a globally installed version:
<actions>
<download software="velox" extract-path="bin" version="2025.1.1" />
<velox config-file="velox.toml"
golang-version="^1.22"
roadrunner-ref="2024.1.5" />
</actions>This ensures consistent Velox versions across different environments and team members.
<?xml version="1.0"?>
<dload xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/php-internal/dload/refs/heads/1.x/dload.xsd">
<actions>
<velox config-file="./velox.toml"
velox-version="^1.4.0"
golang-version="^1.22"
roadrunner-ref="2024.1.5"
binary-path="./bin/rr" />
</actions>
</dload># Build RoadRunner using velox.toml configuration
./vendor/bin/dload build
# Build with specific configuration file
./vendor/bin/dload build --config=custom-rr.xml<dload>
<registry overwrite="false">
<!-- Binary executable -->
<software name="RoadRunner" alias="rr"
homepage="https://roadrunner.dev"
description="High performance Application server">
<repository type="github" uri="roadrunner-server/roadrunner" asset-pattern="/^roadrunner-.*/" />
<binary name="rr" pattern="/^roadrunner-.*/" />
</software>
<!-- Archive with files -->
<software name="frontend" description="Frontend assets">
<repository type="github" uri="my-org/frontend" asset-pattern="/^artifacts.*/" />
<file pattern="/^.*\.js$/" />
<file pattern="/^.*\.css$/" />
</software>
<!-- Mixed: binaries + files -->
<software name="development-suite" description="Complete development tools">
<repository type="github" uri="my-org/dev-tools" />
<binary name="cli-tool" pattern="/^cli-tool.*/" />
<file pattern="/^config\.yml$/" extract-path="config" />
<file pattern="/^templates\/.*/" extract-path="templates" />
</software>
<!-- PHAR tools -->
<software name="psalm" description="Static analysis tool">
<repository type="github" uri="vimeo/psalm" />
<binary name="psalm.phar" pattern="/^psalm\.phar$/" />
</software>
<!-- GitLab repository -->
<software name="My cool project" alias="cool-project"
homepage="https://gitlab.com/path/to/my/repository"
description="">
<repository type="gitlab" uri="path/to/my/repository" asset-pattern="/^cool-.*/" />
<binary name="cool" pattern="/^cool-.*/" />
</software>
</registry>
</dload>- type: Currently supports "github"
- uri: Repository path (e.g., "username/repo")
- asset-pattern: Regex pattern to match release assets
- name: Binary name for reference
- pattern: Regex pattern to match binary in assets
- Automatically handles OS/architecture filtering
- pattern: Regex pattern to match files
- extract-path: Optional extraction directory
- Works on any system (no OS/architecture filtering)
# One-time setup for new developers
composer install
./vendor/bin/dload init # First time only
./vendor/bin/dload get# Start a new project with DLoad
composer init
composer require internal/dload -W
./vendor/bin/dload init
./vendor/bin/dload get# GitHub Actions
- name: Download tools
run: GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} ./vendor/bin/dload getEach developer gets the correct binaries for their system:
<actions>
<download software="rr" /> <!-- Linux binary for Linux, Windows .exe for Windows -->
<download software="temporal" /> <!-- macOS binary for macOS, etc. -->
</actions><actions>
<!-- Download as executable .phar files -->
<download software="psalm" type="phar" />
<download software="phpstan" type="phar" />
<!-- Extract contents instead -->
<download software="psalm" type="archive" /> <!-- Unpacks psalm.phar -->
</actions><software name="ui-kit">
<repository type="github" uri="company/ui-components" />
<file pattern="/^dist\/.*/" extract-path="public/components" />
</software>
<actions>
<download software="ui-kit" type="archive" />
</actions>Use a personal access token to avoid rate limits:
GITHUB_TOKEN=your_token_here ./vendor/bin/dload get
GITLAB_TOKEN=your_token_here ./vendor/bin/dload getAdd to CI/CD environment variables for automated downloads.
Note
In GitHub Actions, secrets.GITHUB_TOKEN is scoped to the current repository and shares a limit of
1,000 requests per hour across all jobs of the repository. With a large job matrix the limit may run out,
and downloads from other repositories may be rejected. Use a personal access token if that happens.
Release lists are also kept in a local version registry, so repeated runs and runs that carry the registry between them spend the rate limit only on new releases: see Version Registry.
dload get exits with a non-zero code when at least one requested package was not installed, and prints
the reason for every failed download: the API error (invalid token, exhausted rate limit, missing repository),
the number of matched releases, the assets each checked release contains, and the filters that rejected them.
Failed to download `rr`
Requested: version `any`, OS `linux`, architecture `amd64`, minimum stability `stable`, asset type `any`.
Tried 1 repository(ies):
1) github `roadrunner-server/roadrunner`
GitHub API rejected the credentials (HTTP 401: Bad credentials).
The API token from the GITHUB_TOKEN environment variable is invalid, expired or revoked. Provide a valid
token or unset the variable to use anonymous access.
1 of 1 download(s) failed.
Run with -vvv to also get stack traces and the full request log.
When you make a release in Gitlab, make sure to upload your assets to the release page via
package manager. This can easily be done via Gitlab CLI and the glab release upload --use-package-registry
command.
# .gitlab-ci.yml
Build artifacts:
stage: push
script:
- mkdir bin
- echo "Mock binary for darwin arm" > bin/cool-darwin-arm64
- echo "Mock binary for darwin amd" > bin/cool-darwin-amd64
- echo "Mock binary for linux arm" > bin/cool-linux-arm64
- echo "Mock binary for linux amd" > bin/cool-linux-amd64
artifacts:
expire_in: 2 hours
paths:
- $CI_PROJECT_DIR/bin/cool-*
rules:
- if: $CI_COMMIT_TAG
Release artifacts:
stage: deploy
image: gitlab/glab:latest
needs: [ "Build artifacts" ]
script:
- glab auth login --job-token $CI_JOB_TOKEN --hostname $CI_SERVER_HOST
- glab release upload --use-package-registry "$CI_COMMIT_TAG" ./bin/*
rules:
- if: $CI_COMMIT_TAGContributions welcome! Submit Pull Requests to:
- Add new software to the predefined registry
- Improve DLoad functionality
- Enhance documentation and translate this on other languages