krymosoftware/php-k8s
A PHP package for integrating Kubernetes into your applications, providing convenient access to Kubernetes APIs for querying clusters, managing resources, and automating common cluster operations from PHP code.
composer require krymo/php-k8s:^3.9
Before anything else, check your PHP version. 3.9.0 drops PHP 8.2 and fixes PHP 8.4 deprecations, so the supported floor is now 8.3. If your Laravel app still runs 8.2 (common on Laravel 9/10 images), composer update will fail — pin ^3.8 until you upgrade PHP.
Start reading at KubernetesCluster (src/KubernetesCluster.php) — it is the single entry point. From there everything funnels into KubernetesClient, and each verb (pods, deployments, jobs, …) is a resource class under src/Kubernetes/. You rarely need to touch the client directly.
Connect to a cluster:
use KrymoSoftware\PhpK8s\KubernetesCluster;
$cluster = KubernetesCluster::fromKubeConfig(
path: base_path('kubeconfig'),
context: 'minikube',
);
In-cluster (running inside a pod with a service account) use the service-account constructor, and for CI/remote use the token variant — both accept named arguments as of 3.9.0, so KubernetesCluster::fromKubeConfig(path: ..., context: ...) reads far better than positional args.
First use case: an artisan command or queued job that lists pods in a namespace and reports their age using the newly exposed creation timestamp — this exercises connect → list → inspect metadata, which is 80% of what you'll do daily.
$cluster->namespace('default')
->pods()
->all()
->each(fn ($pod) => $this->line($pod->name.' '.$pod->createdAt()));
Register the cluster as a container singleton. With Laravel support removed in 3.9.0 there is no auto-registered service provider or Kubernetes facade, so wire it yourself in AppServiceProvider::register():
$this->app->singleton(KubernetesCluster::class, fn () => KubernetesCluster::fromKubeConfig(
path: config('services.kubernetes.kubeconfig'),
context: config('services.kubernetes.context'),
));
Then inject KubernetesCluster into commands, jobs, and actions. If you run multi-cluster, use a small factory that caches one cluster instance per context name rather than re-parsing the kubeconfig on every call.
Keep cluster calls out of the request path. Every call is a network round-trip with its own TLS handshake cost. Listing pods during a page render is fine for a dashboard; anything that creates, patches, or deletes belongs in a queued job with retries, so a flaky API server doesn't turn into a 500.
Use the creation timestamp for lifecycle logic. createdAt() on a resource is the natural key for:
now()->diffInMinutes($pod->createdAt()))Jobs / failed Pods older than N minutesParse it as a date — it's an RFC3339 string from the API server, not a Carbon instance.
Deletion is asynchronous — always wait. ->delete() returns as soon as the API server accepts the request. If a subsequent step creates a resource with the same name, poll ->get() until it 404s, or you'll hit AlreadyExists intermittently.
Streaming (exec, attach, follow-logs, watch) is a different execution model. These run through the ReactPHP/pawl stack, are long-lived, and block. Wrap them in a dedicated command, never a web request, and always pass a timeout.
Testing: bind a stub KubernetesCluster in the container and assert against recorded calls, or point the suite at kind/k3d in CI via a KUBECONFIG fixture. Don't mock the resource classes themselves — they're thin and you'll end up testing your mocks.
"Remove Laravel support" is the headline breaking change. Before upgrading, run composer show krymo/php-k8s and grep your app for the Kubernetes facade, a published config/php-k8s.php, or a service provider registration. If any of those exist, you have two options: stay on ^3.8 until you migrate, or register the singleton yourself as shown above. Do the grep before bumping — the failure mode is a BindingResolutionException at boot, or worse, a silently stale config file left on disk.
Check what actually changed for you. If your app never used the Laravel integration (just KubernetesCluster directly), 3.9.0 is a low-risk upgrade gated only on the PHP version.
PHP 8.2 removal cascades. Your CI matrix, Dockerfile base image, and any platform.php setting in composer.json all need to move together. A config.platform.php pinned to 8.2 will keep the resolver on 3.8.x even after you upgrade the runtime.
The ratchet/pawl bump is easy to miss. It affects exec/attach/streaming paths only — a plain list/create/delete smoke test will pass while the interesting code paths are broken. If you use exec, test it explicitly after upgrading, and watch for React event-loop version conflicts with other packages you've pulled in (broadcasters, websocket clients).
Timestamp quirks. Newly created resources may not have a populated timestamp on the response object you just built locally — re-fetch with ->get() if you need it immediately. Fake API servers and some test harnesses omit the field entirely, so treat it as nullable.
Named parameters apply to KubernetesCluster functions only. Resource builders still take positional arguments; don't assume the whole API got nicer in 3.9.0.
Debugging connection failures in this order: KUBECONFIG / in-cluster service-account token presence → token expiry (in-cluster tokens rotate) → TLS verification (self-signed CAs need to be passed explicitly, not skipped) → RBAC (Forbidden on list looks identical to an empty result if you're swallowing exceptions). Log the full error body; the API server's message is usually precise.
Extension points. For custom resources, extend KubernetesResource, declare the API version and kind, and register it — don't hand-roll raw() requests, because you lose the query/label-selector ergonomics. For anything genuinely unsupported, KubernetesClient exposes the raw request path as an escape hatch.
How can I help you explore Laravel packages today?