cloudbear / api-oci
Build OCI image archives and interact with OCI registries from PHP.
Requires
- php: ^8.4
- ext-zlib: *
- cloudbear/class-mapper: ^2.0
- nesbot/carbon: ^3.0
- php-http/discovery: ^1.20
- psr/cache: ^3.0
- psr/http-client: ^1.0
- psr/http-client-implementation: *
- psr/http-factory: ^1.1
- psr/http-factory-implementation: *
- psr/http-message: ^2.0
- ramsey/uuid: ^4.0
- splitbrain/php-archive: ^1.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.75
- guzzlehttp/guzzle: ^7.0|^8.0
- justinrainbow/json-schema: ^6.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.0
Suggests
- guzzlehttp/guzzle: ^7.0|^8.0 for a PSR-18 HTTP client and PSR-17 factories out of the box; any PSR-18/PSR-17 implementation works.
This package is auto-updated.
Last update: 2026-07-31 06:06:17 UTC
README
A PHP library for building OCI archives and interacting with OCI registries — without a container build daemon.
- Build OCI archives with multi-architecture and multi-layer support (no build system required).
- Push archives to, and read manifests/tags from, OCI registries.
- Produces standard
.tar/.tar.gzarchives thatdocker loadand other OCI tools understand.
Requirements
- PHP 8.4 or newer
ext-zlib(used to gzip archives; archives are assembled entirely in memory)
Installation
composer require cloudbear/api-oci
Building an archive
use Cloudbear\Oci\Container;
use Cloudbear\Oci\Container\Config;
use Cloudbear\Oci\Container\Enums\ChangeTypes;
use Cloudbear\Oci\Container\Files\ContentFile;
use Cloudbear\Oci\Container\Layer;
use Cloudbear\Oci\Enums\PlatformVariants;
use Cloudbear\Oci\Name;
// Single file
$archive = Container::fromFile(
name: Name::parse('gitlab.com/cloudbear/open-source/php-api-oci/example:latest'),
file: new ContentFile(ChangeTypes::Addition, '/readme.txt', 'My archive'),
);
// Multi-architecture, multi-layer
$archive = Container::fromConfig(
Name::parse('gitlab.com/cloudbear/open-source/php-api-oci/example:latest'),
new Config(PlatformVariants::ARM_64BIT, layers: [
new Layer([
new ContentFile(ChangeTypes::Addition, '/readme.txt', "I'm ARM"),
]),
]),
new Config(PlatformVariants::X86_64, layers: [
new Layer([
new ContentFile(ChangeTypes::Addition, '/readme.txt', "I'm X86_64"),
]),
]),
);
// Write a local archive (compression is inferred from the extension)
$archive->writeToDisk('my-archive.tar.gz');
Talking to a registry
Registry is the entry point for all registry access: reading tags and manifests, pushing an
archive, and pulling one into a Container. It is built on a RegistryTransport, which owns the
HTTP client and handles token authentication.
use Cloudbear\Oci\Registry;
use Cloudbear\Oci\Registry\Credentials;
use Cloudbear\Oci\Registry\RegistryTransport;
// Credentials are optional; omit them for anonymous access.
$registry = new Registry(new RegistryTransport(new Credentials($username, $password)));
// Read tags and manifests.
$tags = $registry->tags('ghcr.io/owner/app');
$manifest = $registry->detail('ghcr.io/owner/app:v1');
// Push a locally built archive.
$registry->push($archive);
Pulling from a registry
Registry::pull() returns a Container. The pulled layers keep their exact blob bytes and digests,
so the container can be re-pushed byte-identically, and their files can be read in memory.
use Cloudbear\Oci\Registry;
use Cloudbear\Oci\Registry\RegistryTransport;
$container = new Registry()->pull('ghcr.io/owner/app:v1');
$container->paths(); // file paths in the latest (topmost) layer
$container->has('app/config.yaml'); // check for a path
$container->get('app/config.yaml'); // read a file's contents, or null if absent
$container->files(); // ['path' => 'contents', ...] for the latest layer
$container->latestLayer(); // the Layer itself, or null when there are none
Local Docker daemon
DockerDaemon talks to a local Docker daemon over its unix socket (no docker CLI, no temp files).
It can load a built Container into the daemon (docker load), export an image back out into a
Container (docker save), and list or inspect images.
use Cloudbear\Oci\DockerDaemon;
// Defaults to /var/run/docker.sock; honors a unix:// DOCKER_HOST, or pass a socket path.
$daemon = new DockerDaemon();
// Load a locally built archive into the daemon.
$daemon->load($archive);
// Export an image out of the daemon; exported layers keep their exact digests.
$container = $daemon->export('ghcr.io/owner/app:v1');
$daemon->images(); // GET /images/json (list)
$daemon->inspect('ghcr.io/owner/app:v1'); // GET /images/{ref}/json (inspect)
The whole export is read into memory, so this suits small-to-moderate images; streaming large
exports to disk is not yet supported. Only a unix-socket daemon is supported (not TCP/TLS
DOCKER_HOST or containerd).
Names
Name::parse() accepts the usual reference forms and fills in Docker Hub defaults:
Name::parse('nginx'); // registry-1.docker.io/library/nginx:latest
Name::parse('nginx:1.27'); // ... /library/nginx:1.27
Name::parse('ghcr.io/owner/app:v1'); // ghcr.io/owner/app:v1
Name::parse('registry.example.com/app@sha256:...'); // pinned by digest
Development
composer install
composer test # PHPUnit
composer test:coverage # PHPUnit with a coverage report, needs pcov or Xdebug
composer phpstan # PHPStan, level 8
composer lint # PHP-CS-Fixer, writes fixes in place
composer check # test, phpstan and lint, in order
See CONTRIBUTING.md for the full workflow.
Contributing
This library is partially implemented and contributions are welcome. Please read CONTRIBUTING.md and our Code of Conduct before opening a merge request. For security issues, see SECURITY.md.
License
Released under the MIT License.