Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Docker Php Laravel Package

beluga-php/docker-php

A PHP client for the Docker Engine API, offering a clean object-oriented way to manage containers, images, networks, and volumes programmatically. Ideal for building Docker-powered tooling, deployment scripts, and infrastructure workflows in PHP.

Deep Wiki
Context7

Getting Started

beluga-php/docker-php is a low-level, typed Docker Engine API client. It is not a Laravel package — there is no service provider, facade, or config file. You get a DockerClient and generated API models (ContainerConfig, HostConfig, Mount, PortBinding, EndpointSettings, …), and you wire it into Laravel yourself. The repo is marked archived, so treat it as vendored infrastructure rather than a dependency you can freely upgrade.

Install and pin an exact version (it is archived; do not rely on floating constraints):

composer require beluga-php/docker-php:^3 --no-update
# then commit the exact resolved version in composer.lock

Confirm the daemon is reachable before writing any application code. Docker::create() returns a DockerClient talking to the local socket by default:

use Docker\Docker;

$docker = Docker::create();
$info = $docker->systemInfo();          // sanity check: daemon alive?
$containers = $docker->containerList(['all' => true]);

foreach ($containers as $container) {
    dump($container->getNames(), $container->getState());
}

First real use case: ephemeral companion services and CI-style checks from Artisan, not interactive container orchestration. A typical small Laravel app uses it to (1) list/inspect containers for a status page, (2) spin up a throwaway service (e.g. redis:7-alpine, a mock SMTP) for tests or a demo, and (3) stream logs or prune stale resources. Bind it once in AppServiceProvider so you don't hand-roll socket plumbing in every command:

use Docker\Docker;
use Docker\DockerClient;

$this->app->singleton(DockerClient::class, fn () => Docker::create());

Read the generated model classes under vendor/beluga-php/docker-php/src/API/Model early — that is the de facto documentation for what you can set on ContainerConfig/HostConfig.

Implementation Patterns

Create → start → wait → remove. Container operations are always explicit and stateful; nothing auto-runs. This is the daily bread-and-butter sequence:

use Docker\API\Model\ContainerConfig;
use Docker\API\Model\HostConfig;
use Docker\API\Model\PortBinding;

$config = (new ContainerConfig())
    ->setImage('redis:7-alpine')
    ->setExposedPorts(['6379/tcp' => new \stdClass()])
    ->setHostConfig(
        (new HostConfig())
            ->setPortBindings(['6379/tcp' => [(new PortBinding())->setHostPort('63790')]])
            ->setAutoRemove(true)
    );

$created = $docker->containerCreate($config, 'app-redis-'.Str::random(6));
$docker->containerStart($created->getId());

$result = $docker->containerWait($created->getId(), 'not-running'); // exit code

Use containerCreate($config, $name) with your own name to make cleanup deterministic; otherwise the daemon assigns one and you must persist the ID.

Wrap the client in an application service. Keep DockerClient calls inside one class (App\Services\DockerService) or a small package of them. That gives you a single place for error handling, timeouts, and the eventual swap away from this archived library. Controllers and jobs then depend on your service, not on Docker\API\Model\* leaking across the codebase.

Streams must be consumed. Image pulls/builds (imageCreate, imageBuild) and containerLogs return streams, not arrays. Image operations are asynchronous from the daemon's perspective — if you never drain the stream, the pull appears to hang and the image may not finish. Iterate chunks (each JSON line in a pull stream carries status/progress/error); for logs, read in chunks or pipe to StreamedResponse:

return response()->stream(function () use ($docker, $id) {
    $stream = $docker->containerLogs($id, [
        'follow' => true, 'stdout' => true, 'stderr' => true, 'tail' => 200,
    ]);

    while (($chunk = $stream->read(8192)) !== '') {
        echo $chunk;
        ob_flush(); flush();
    }
}, 200, ['Content-Type' => 'text/plain', 'X-Accel-Buffering' => 'no']);

Queue the slow paths. Create/start/pull/build/wait are blocking network calls and can take tens of seconds. Put them in queued jobs (php artisan queue:work) and expose progress via broadcasting or a status row; never do a containerWait inside an HTTP request thread.

Ephemeral test fixtures. In tests/TestCase or a trait, boot the services your suite needs in setUpBeforeClass() and force-remove them in tearDownAfterClass(). Always remove by ID with force => true so a failed assertion doesn't leak containers across runs. Persist IDs/names in the trait's static state — do not try to "find" them again by image name, since that will match unrelated containers on a shared daemon.

Artisan commands for ops. Thin commands such as docker:status, docker:prune, and docker:logs {container} are a natural fit: they run in a terminal, tolerate blocking calls, and give you a scriptable interface for cron. Keep them read-only or dry-run by default; destructive pruning should require --force.

Health/status dashboards. containerList() + containerInspect() gives you state, health status, and port mappings; systemDf() (if your version exposes it) gives disk usage. Cache the result for 15–60 seconds — every call is a round trip to the daemon and these pages are usually polled.

Gotchas and Tips

  • Archived = frozen. No security patches, no new Engine API support. Pin the exact version, keep a fork path in mind, and do not expose container control to untrusted users. If you only need docker compose up semantics, shelling out to the CLI or using testcontainers-php may be a better long-term bet.
  • Socket permissions are the #1 first-run failure. The PHP user (www-data, forge, the FPM pool user) must be able to read the socket. Adding it to the docker group is the common fix, but that is effectively root-equivalent access. Prefer a socket proxy (e.g. tecnativa/docker-socket-proxy) and point the client at it, especially on a shared host.
  • Which daemon? Docker::create() talks to the local socket. Under Docker Desktop, WSL, or a remote host, the socket path and DOCKER_HOST/context differ; you will need to construct the HTTP client explicitly (Unix socket vs tcp://host:2376) and supply TLS certificates for remote daemons. Always run systemInfo() in a tinker session to prove you're talking to the daemon you think you are.
  • Multiplexed logs. For non-TTY containers, containerLogs output frames stdout and stderr with an 8-byte header. If you see garbage/control bytes when dumping raw chunks, either enable Tty => true in ContainerConfig (loses the stdout/stderr split) or use the library's stream helper instead of echo-ing raw bytes into a browser.
  • 409 Conflict on names. containerCreate fails if the name exists. Use a unique suffix for per-job containers, or catch the conflict and reuse/inspect the existing one. The same applies to published host ports — pick a free port instead of hardcoding one in tests.
  • Forgotten HostConfig. Port bindings, volume mounts, restart policies, and resource limits all live on HostConfig. Setting setExposedPorts() alone exposes the port inside the container network but does not publish it to the host; you need matching setPortBindings(). Same for Mount entries — a missing bind mount is silently a container-local filesystem.
  • containerWait conditions. Passing the wrong condition (or omitting it on older versions) blocks until the container exits. For long-running services, poll containerInspect()->getState()->getStatus() from a job instead of waiting.
  • Errors are exceptions. Catch Docker\Exception\DockerException (and its specific subclasses such as not-found variants) around every call, not just at the top level. A stopped container, a missing image, and a malformed config all surface differently, and the message includes the daemon's JSON — log it verbatim; it is far more useful than the exception class.
  • Don't buffer build/pull output. A large image build can emit megabytes of JSON. Stream it to a file or an SSE endpoint and discard progress lines you don't render, or you'll exhaust memory in a queue worker.
  • Cleanup is your responsibility. setAutoRemove(true) only removes the container after it exits — it does not clean images, volumes, or networks. A periodic docker:prune command (dangling images, stopped containers, unused volumes beyond a retention window) prevents disk-fill incidents on long-lived daemons.
  • Extension point if you must patch. All HTTP goes through DockerClient/DockerHttpClient and all models are plain POPOs. If you need Engine API features newer than the pinned models, a thin wrapper that issues raw requests is usually easier than forking the generated API/Model tree.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo