cainy/laravel-dockhand 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

cainy/laravel-dockhand

Composer 安装命令:

composer require cainy/laravel-dockhand

包简介

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

README 文档

README

Latest Version on Packagist Total Downloads GitHub Tests Action Status GitHub Code Style Action Status GitHub PHPStan Action Status codecov

A Laravel package for interacting with container registries following the OCI Distribution Specification.

Requirements

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

Installation

Install the package via Composer:

composer require cainy/laravel-dockhand

Publish the config file:

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

Configuration

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

Supported Drivers

Driver Description
distribution Standard OCI Distribution registry (Docker Registry, Harbor, etc.)
zot Zot registry with extension support (search, user preferences, tag deletion)

Supported Auth Drivers

Auth Driver Description
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

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

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

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 & 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<int, string>
$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

// 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<int, ImageLayerDescriptor>
$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

// 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

// 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

// 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.

Multiple Connections

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

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

JWT (ECDSA)

JWT authentication is designed for token-based registry auth as described in the Docker Token Authentication Specification. 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

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

Bearer Token

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

API Key

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

No Auth

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

Webhook Notifications

Dockhand can receive and dispatch registry notification events as Laravel events.

Setup

  1. Enable notifications in your .env:
DOCKHAND_NOTIFICATIONS_ENABLED=true
DOCKHAND_NOTIFICATIONS_ROUTE=/dockhand/notify
  1. Generate a notification token:
php artisan dockhand:notify-token
  1. Configure your registry to send notifications to Dockhand:
notifications:
    endpoints:
        -   name: EventListener
            url: http://your-app.test/dockhand/notify
            headers:
                Authorization: [ "Bearer <your-notify-token>" ]
            timeout: 500ms
            threshold: 5
            backoff: 1s
            ignore:
                actions:
                    - pull

Available Events

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

Event Extends Trigger
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

All events (RegistryBaseEvent) have:

Property Type
$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:

Property Type
$targetMediaType MediaType
$targetSize int
$targetUrl string
$targetTag ?string

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 & Scope Helpers

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

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

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

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

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 <notify token>" ]
            timeout: 500ms
            threshold: 5
            backoff: 1s
            ignore:
                actions:
                    - pull
health:
    storagedriver:
        enabled: true
        interval: 10s
        threshold: 3

Testing

composer test
composer test-coverage
composer analyse

Contributing

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

Security Vulnerabilities

If you find any security vulnerabilities, please contact me via mail at info@techbra.in.

License

The MIT License (MIT). Please see License File for more information.

cainy/laravel-dockhand 适用场景与选型建议

cainy/laravel-dockhand 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 9 次下载、GitHub Stars 达 1, 最近一次更新时间为 2026 年 03 月 06 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「container」 「distribution」 「registry」 「laravel」 「docker」 「oci」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 cainy/laravel-dockhand 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 cainy/laravel-dockhand 我们能提供哪些服务?
定制开发 / 二次开发

基于 cainy/laravel-dockhand 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 9
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 34
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-03-06