PHPackages                             cainy/laravel-dockhand - 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. [PSR &amp; Standards](/categories/psr-standards)
4. /
5. cainy/laravel-dockhand

ActiveLibrary[PSR &amp; Standards](/categories/psr-standards)

cainy/laravel-dockhand
======================

A Laravel Package for interacting with registries following the Open Container Initiative Distribution Specification

v0.3.1(2mo ago)15↓100%[4 PRs](https://github.com/cainydev/laravel-dockhand/pulls)MITPHPPHP ^8.4CI passing

Since Jul 12Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/cainydev/laravel-dockhand)[ Packagist](https://packagist.org/packages/cainy/laravel-dockhand)[ Docs](https://github.com/cainydev/laravel-dockhand)[ GitHub Sponsors]()[ RSS](/packages/cainy-laravel-dockhand/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (4)Dependencies (16)Versions (10)Used By (0)

Laravel Dockhand
================

[](#laravel-dockhand)

[![Latest Version on Packagist](https://camo.githubusercontent.com/e9749e7a64dc321c7b1df142ab11a5454484e12ec588bdae1cf339639669b571/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6361696e792f6c61726176656c2d646f636b68616e642e737667)](https://packagist.org/packages/cainy/laravel-dockhand)[![Total Downloads](https://camo.githubusercontent.com/f4b2ad6c35bb381aa158c6c701df69d1df0e686701e6f6e242e057ed8c21a052/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6361696e792f6c61726176656c2d646f636b68616e642e737667)](https://packagist.org/packages/cainy/laravel-dockhand)[![GitHub Tests Action Status](https://camo.githubusercontent.com/39cb35341c5bc91b1f383da3f0c8e09fa8cc3abaa455a1010b8c1cc75d26b3c6/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6361696e796465762f6c61726176656c2d646f636b68616e642f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473)](https://github.com/cainydev/laravel-dockhand/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/2b4e7d97ee68d669a6c645c0cb5c5ec56d4cb20d11ed028005acdbfb89724bc9/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6361696e796465762f6c61726176656c2d646f636b68616e642f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65)](https://github.com/cainydev/laravel-dockhand/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![GitHub PHPStan Action Status](https://camo.githubusercontent.com/61eaf050636c032122fd3757ee61cf73a4b4817cfc6948a1f56fb213aef3fe1c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6361696e796465762f6c61726176656c2d646f636b68616e642f7068707374616e2e796d6c3f6272616e63683d6d61696e266c6162656c3d7068707374616e)](https://github.com/cainydev/laravel-dockhand/actions?query=workflow%3APHPStan+branch%3Amain)[![codecov](https://camo.githubusercontent.com/9d213119b7274917116ef64c87904452a65b517e64ce920dc3e507f55017323c/68747470733a2f2f636f6465636f762e696f2f67682f6361696e796465762f6c61726176656c2d646f636b68616e642f6272616e63682f6d61696e2f67726170682f62616467652e737667)](https://codecov.io/gh/cainydev/laravel-dockhand)

A Laravel package for interacting with container registries following the [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec).

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

[](#requirements)

- PHP 8.4+
- Laravel 10, 11, 12, or 13

Installation
------------

[](#installation)

Install the package via Composer:

```
composer require cainy/laravel-dockhand
```

Publish the config file:

```
php artisan vendor:publish --tag="dockhand-config"
```

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

[](#configuration)

Dockhand uses a multi-connection architecture. Each connection has its own driver, base URI, authentication, and logging configuration.

### Supported Drivers

[](#supported-drivers)

DriverDescription`distribution`Standard OCI Distribution registry (Docker Registry, Harbor, etc.)`zot`[Zot](https://zotregistry.dev/) registry with extension support (search, user preferences, tag deletion)### Supported Auth Drivers

[](#supported-auth-drivers)

Auth DriverDescription`jwt`ECDSA JWT token authentication (for token-based registries)`basic`HTTP Basic authentication`bearer`Static Bearer token`apikey`API key authentication`null`No authentication### Default Connection

[](#default-connection)

The default connection is configured via environment variables:

```
DOCKHAND_CONNECTION=default
DOCKHAND_DRIVER=distribution
DOCKHAND_BASE_URI=http://localhost:5000/v2/
DOCKHAND_AUTH_DRIVER=jwt
DOCKHAND_PRIVATE_KEY=/path/to/private_key.pem
DOCKHAND_PUBLIC_KEY=/path/to/public_key.pem
DOCKHAND_AUTHORITY_NAME=my_auth
DOCKHAND_REGISTRY_NAME=my_registry
DOCKHAND_LOG_DRIVER=stack
```

### Multiple Connections

[](#multiple-connections)

Define additional connections in `config/dockhand.php`:

```
'connections' => [
    'default' => [
        'driver' => env('DOCKHAND_DRIVER', 'distribution'),
        'base_uri' => env('DOCKHAND_BASE_URI', 'http://localhost:5000/v2/'),
        'logging' => [
            'driver' => env('DOCKHAND_LOG_DRIVER', 'stack'),
        ],
        'auth' => [
            'driver' => env('DOCKHAND_AUTH_DRIVER', 'jwt'),
            'jwt_private_key' => env('DOCKHAND_PRIVATE_KEY'),
            'jwt_public_key' => env('DOCKHAND_PUBLIC_KEY'),
            'authority_name' => env('DOCKHAND_AUTHORITY_NAME', 'auth'),
            'registry_name' => env('DOCKHAND_REGISTRY_NAME', 'registry'),
        ],
    ],

    'staging' => [
        'driver' => 'zot',
        'base_uri' => env('ZOT_STAGING_BASE_URI', 'http://localhost:5050/v2/'),
        'logging' => [
            'driver' => env('ZOT_STAGING_LOG_DRIVER', 'stack'),
        ],
        'auth' => [
            'driver' => 'basic',
            'username' => env('ZOT_STAGING_USERNAME'),
            'password' => env('ZOT_STAGING_PASSWORD'),
        ],
    ],

    'prod' => [
        'driver' => 'zot',
        'base_uri' => env('ZOT_PROD_BASE_URI'),
        'logging' => [
            'driver' => env('ZOT_PROD_LOG_DRIVER', 'stack'),
        ],
        'auth' => [
            'driver' => 'apikey',
            'api_key' => env('ZOT_PROD_API_KEY'),
        ],
    ],
],
```

Usage
-----

[](#usage)

### Basic Usage

[](#basic-usage)

```
use Cainy\Dockhand\Facades\Dockhand;

// Check if the registry is online
Dockhand::isOnline(); // bool

// Get the API version
Dockhand::getApiVersion(); // RegistryApiVersion enum
```

### Repositories &amp; Tags

[](#repositories--tags)

```
// List all repositories
$repos = Dockhand::getRepositories();

// With pagination
$page = Dockhand::getRepositories(limit: 10);
// $page is a PaginatedResult when limit is set
$page->items;    // Collection
$page->hasMore(); // bool
$page->nextUrl;  // ?string — pass to next request

// List tags of a repository
$tags = Dockhand::getTagsOfRepository('library/nginx');

// With pagination
$page = Dockhand::getTagsOfRepository('library/nginx', limit: 20);
```

### Manifests

[](#manifests)

```
// Get a manifest (returns ImageManifest or ManifestList depending on content)
$manifest = Dockhand::getManifest('library/nginx', 'latest');

if ($manifest->isManifestList()) {
    // ManifestList — multi-platform image
    $entry = $manifest->findManifestListEntryByPlatform(
        Platform::create('linux', 'amd64')
    );

    // Fetch the platform-specific manifest from an entry
    $imageManifest = Dockhand::getManifestFromManifestListEntry($entry);
} else {
    // ImageManifest — single-platform image
    $imageManifest = $manifest;
}

// Access manifest properties
$imageManifest->digest;
$imageManifest->config;  // ImageConfigDescriptor
$imageManifest->layers;  // Collection
$imageManifest->getSize();

// Head request (lightweight — returns digest, content length, media type)
$head = Dockhand::headManifest('library/nginx', 'latest');
$head->digest;
$head->contentLength;
$head->mediaType;

// Push a manifest
$result = Dockhand::putManifest('library/nginx', 'latest', $manifest);
$result->digest;
$result->location;
```

### Blobs

[](#blobs)

```
// Download a blob
$data = Dockhand::getBlob('library/nginx', 'sha256:abc123...');

// Get blob size without downloading
$size = Dockhand::getBlobSize('library/nginx', 'sha256:abc123...');

// Get parsed image config from a manifest's config descriptor
$config = Dockhand::getImageConfigFromDescriptor($imageManifest->config);
$config->platform;  // Platform
$config->created;   // Carbon
```

### Blob Uploads

[](#blob-uploads)

```
// Monolithic upload (single request)
$result = Dockhand::uploadBlob('library/nginx', $data, $digest);

// Chunked upload
$upload = Dockhand::initiateBlobUpload('library/nginx');
$upload = Dockhand::uploadBlobChunk($upload, $chunk1);
$upload = Dockhand::uploadBlobChunk($upload, $chunk2);
$result = Dockhand::completeBlobUpload($upload, $digest);

// Mount a blob from another repository (avoids re-uploading)
$result = Dockhand::mountBlob('library/nginx', $digest, 'library/alpine');

// Check upload status / cancel
$upload = Dockhand::getBlobUploadStatus('library/nginx', $uuid);
Dockhand::cancelBlobUpload('library/nginx', $uuid);
```

### Deletion

[](#deletion)

```
// Delete a manifest by digest
Dockhand::deleteManifest('library/nginx', 'sha256:abc123...');

// Delete a blob
Dockhand::deleteBlob('library/nginx', 'sha256:abc123...');
```

> **Note:** Tag deletion (by tag name instead of digest) is only supported by the Zot driver. See [Zot Driver Extensions](#zot-driver-extensions).

### Multiple Connections

[](#multiple-connections-1)

```
use Cainy\Dockhand\Facades\Dockhand;

// Use a specific connection
$repos = Dockhand::connection('staging')->getRepositories();

// Typed accessors (throws if the connection's driver doesn't match)
$zot = Dockhand::zot('staging');        // ZotDriver
$dist = Dockhand::distribution();       // DistributionDriver (default connection)

// Release a connection (useful in long-running workers)
Dockhand::disconnect('staging');
```

### Zot Driver Extensions

[](#zot-driver-extensions)

The Zot driver provides additional features beyond the standard OCI Distribution spec.

```
$zot = Dockhand::zot('staging');

// Discover available extensions
$extensions = $zot->discoverExtensions();

// Search repositories via GraphQL
$results = $zot->search('{ GlobalSearch(query: "nginx") { ... } }');

// Search CVEs for an image
$cves = $zot->searchCVE('library/nginx', 'latest');

// User preferences
$zot->starRepository('library/nginx');
$zot->unstarRepository('library/nginx');
$zot->bookmarkRepository('library/nginx');
$zot->unbookmarkRepository('library/nginx');

// Tag deletion (not supported by standard distribution registries)
$zot->deleteManifest('library/nginx', 'latest');
```

Authentication
--------------

[](#authentication)

### JWT (ECDSA)

[](#jwt-ecdsa)

JWT authentication is designed for token-based registry auth as described in the [Docker Token Authentication Specification](https://distribution.github.io/distribution/spec/auth/token/). Dockhand acts as the token authority — it signs JWTs with your private key, and the registry validates them using the corresponding public key.

Generate an ECDSA key pair:

```
# Generate private key
openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem

# Extract public key
openssl ec -in private_key.pem -pubout -out public_key.pem
```

Configure the connection:

```
DOCKHAND_AUTH_DRIVER=jwt
DOCKHAND_PRIVATE_KEY=/path/to/private_key.pem
DOCKHAND_PUBLIC_KEY=/path/to/public_key.pem
DOCKHAND_AUTHORITY_NAME=my_auth
DOCKHAND_REGISTRY_NAME=my_registry
```

The `authority_name` must match the `issuer` in the registry config, and `registry_name` must match the `service`.

### Basic Auth

[](#basic-auth)

```
'auth' => [
    'driver' => 'basic',
    'username' => env('REGISTRY_USERNAME'),
    'password' => env('REGISTRY_PASSWORD'),
],
```

### Bearer Token

[](#bearer-token)

```
'auth' => [
    'driver' => 'bearer',
    'token' => env('REGISTRY_TOKEN'),
],
```

### API Key

[](#api-key)

```
'auth' => [
    'driver' => 'apikey',
    'api_key' => env('REGISTRY_API_KEY'),
],
```

### No Auth

[](#no-auth)

```
'auth' => [
    'driver' => 'null',
],
```

Webhook Notifications
---------------------

[](#webhook-notifications)

Dockhand can receive and dispatch [registry notification events](https://distribution.github.io/distribution/about/notifications/) as Laravel events.

### Setup

[](#setup)

1. Enable notifications in your `.env`:

```
DOCKHAND_NOTIFICATIONS_ENABLED=true
DOCKHAND_NOTIFICATIONS_ROUTE=/dockhand/notify
```

2. Generate a notification token:

```
php artisan dockhand:notify-token
```

3. Configure your registry to send notifications to Dockhand:

```
notifications:
    endpoints:
        -   name: EventListener
            url: http://your-app.test/dockhand/notify
            headers:
                Authorization: [ "Bearer " ]
            timeout: 500ms
            threshold: 5
            backoff: 1s
            ignore:
                actions:
                    - pull
```

### Available Events

[](#available-events)

Events extend one of two base classes depending on whether the target still exists:

EventExtendsTrigger`ManifestPushedEvent``RegistryEvent`A manifest was pushed`ManifestPulledEvent``RegistryEvent`A manifest was pulled`BlobPushedEvent``RegistryEvent`A blob was pushed`BlobPulledEvent``RegistryEvent`A blob was pulled`BlobMountedEvent``RegistryEvent`A blob was mounted from another repository`ManifestDeletedEvent``RegistryBaseEvent`A manifest was deleted`BlobDeletedEvent``RegistryBaseEvent`A blob was deleted`TagDeletedEvent``RegistryBaseEvent`A tag was deleted`RepoDeletedEvent``RegistryBaseEvent`A repository was deleted### Event Properties

[](#event-properties)

All events (`RegistryBaseEvent`) have:

PropertyType`$id``string``$timestamp``Carbon``$action``EventAction` (pull, push, mount, delete)`$targetDigest``?string``$targetRepository``string``$requestId``string``$requestAddr``string``$requestHost``string``$requestMethod``string``$requestUserAgent``string``$actorName``?string``$sourceAddr``string``$sourceInstanceId``string`Events extending `RegistryEvent` additionally have:

PropertyType`$targetMediaType``MediaType``$targetSize``int``$targetUrl``string``$targetTag``?string`### Listening to Events

[](#listening-to-events)

```
use Cainy\Dockhand\Events\ManifestPushedEvent;

class ManifestPushedListener
{
    public function handle(ManifestPushedEvent $event): void
    {
        $repo = $event->targetRepository;
        $digest = $event->targetDigest;
        $tag = $event->targetTag;

        // Handle the pushed manifest...
    }
}
```

Token &amp; Scope Helpers
-------------------------

[](#token--scope-helpers)

Dockhand provides facades for building JWT tokens and registry scopes, useful when implementing custom token endpoints or testing.

### Scope Facade

[](#scope-facade)

```
use Cainy\Dockhand\Facades\Scope;

// Create from a registry scope string
$scope = Scope::fromString('repository:library/nginx:pull,push');

// Fluent builder
$scope = Scope::repository('library/nginx')->allowPull()->allowPush();
$scope = Scope::readRepository('library/nginx');   // pull only
$scope = Scope::writeRepository('library/nginx');  // push only
$scope = Scope::catalog()->allowPull();            // catalog access

$scope->hasPull();   // bool
$scope->hasPush();   // bool
$scope->hasDelete(); // bool
$scope->toString();  // "repository:library/nginx:pull,push"
```

### Token Facade

[](#token-facade)

```
use Cainy\Dockhand\Facades\Token;
use Cainy\Dockhand\Facades\Scope;

$token = Token::issuedBy('my_auth')
    ->permittedFor('my_registry')
    ->relatedTo('username')
    ->expiresAt(now()->addMinutes(5))
    ->withScope(Scope::readRepository('library/nginx'))
    ->sign();

$jwt = $token->toString();
```

### TokenService Facade

[](#tokenservice-facade)

For low-level token operations:

```
use Cainy\Dockhand\Facades\TokenService;

$builder = TokenService::getBuilder();
$token = TokenService::signToken($builder);
$valid = TokenService::validateToken($jwt, function ($token) {
    // Additional validation logic
});
```

Example Registry Configuration
------------------------------

[](#example-registry-configuration)

A minimal example pairing Dockhand with a Distribution registry (e.g., in Docker Compose):

```
DOCKHAND_PUBLIC_KEY=/path/to/public_key.pem
DOCKHAND_PRIVATE_KEY=/path/to/private_key.pem
DOCKHAND_BASE_URI=http://registry:5000/v2
DOCKHAND_AUTHORITY_NAME=my_auth
DOCKHAND_REGISTRY_NAME=my_registry
DOCKHAND_NOTIFICATIONS_ENABLED=true
DOCKHAND_NOTIFICATIONS_ROUTE=/dockhand/notify
```

```
version: 0.1
log:
    fields:
        service: registry
storage:
    cache:
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
http:
    addr: :5000
    secret: devsecret
    headers:
        X-Content-Type-Options: [ nosniff ]
auth:
    token:
        realm: http://laravel/auth/token
        service: my_registry
        issuer: my_auth
        rootcertbundle: /root/certs/cert.pem
notifications:
    endpoints:
        -   name: EventListener
            url: http://laravel/dockhand/notify
            headers:
                Authorization: [ "Bearer " ]
            timeout: 500ms
            threshold: 5
            backoff: 1s
            ignore:
                actions:
                    - pull
health:
    storagedriver:
        enabled: true
        interval: 10s
        threshold: 3
```

Testing
-------

[](#testing)

```
composer test
composer test-coverage
composer analyse
```

Contributing
------------

[](#contributing)

Contributions are welcome! Just create an issue or pull request, and I'll take a look.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

If you find any security vulnerabilities, please contact me via mail at .

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

40

—

FairBetter than 88% of packages

Maintenance86

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 94.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~59 days

Total

5

Last Release

67d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2e8edb0b16305ac39d79d216c6cbc9dc3168d5116a9e9dd2b838fa3b9cbbc541?d=identicon)[cainy](/maintainers/cainy)

---

Top Contributors

[![cainydev](https://avatars.githubusercontent.com/u/55443800?v=4)](https://github.com/cainydev "cainydev (55 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

dockerdocker-registrydocker-registry-v2laravellaravel-packageocioci-distributionoci-imagecontainerlaraveldockerdistributionregistryoci

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/cainy-laravel-dockhand/health.svg)

```
[![Health](https://phpackages.com/badges/cainy-laravel-dockhand/health.svg)](https://phpackages.com/packages/cainy-laravel-dockhand)
```

###  Alternatives

[maclof/kubernetes-client

A simple yet elegant client for accessing and controlling a Kubernetes cluster.

235889.1k4](/packages/maclof-kubernetes-client)[vormkracht10/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

24149.7k](/packages/vormkracht10-laravel-mails)[nunomaduro/laravel-pot

Set of commands to inspect Laravel's container

991.6k](/packages/nunomaduro-laravel-pot)[daylerees/container-debug

Inspect the Laravel IoC Container from Artisan.

582.8k](/packages/daylerees-container-debug)[godruoyi/easy-container

A small PHP 5.3 dependency injection container extended from Laravel container.

334.9k2](/packages/godruoyi-easy-container)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
