renoki-co/laravel-php-k8s
Laravel PHP K8s is a Laravel-friendly wrapper around the Kubernetes PHP client, simplifying cluster configuration and letting you query, create, and manage Kubernetes resources from your app with fluent, familiar APIs. It streamlines service-account and kubeconfig setup for automation, deployments,...
renoki-co/laravel-php-k8s is a thin Laravel wrapper around the php-k8s/php-k8s client: a service provider, a published config file, a K8s facade, and named multi-cluster connections. All the resource work (pods, deployments, services, CRDs) is done with the underlying php-k8s model API, so plan on reading two sets of docs.
Install and publish
composer require renoki-co/laravel-php-k8s
php artisan vendor:publish \
--provider="RenokiCo\LaravelK8s\LaravelK8sServiceProvider" \
--tag="config"
This gives you config/php-k8s.php. Package discovery registers the provider; on older apps check that 'K8s' => RenokiCo\LaravelK8s\LaravelK8sFacade::class is in config/app.php aliases.
Version constraints for 3.4.0 — this release adds Laravel 9 support and drops PHP 7.3/7.4 and Laravel 7.x. If you are on Laravel 7 or PHP 7.3/7.4, stay pinned to ^3.3; otherwise ^3.4 requires PHP 8.0+ and Laravel 8.x or 9.x. Check composer why-not before bumping on a legacy app.
Configure a connection
// config/php-k8s.php
'default' => env('K8S_DEFAULT_CONNECTION', 'local'),
'connections' => [
'local' => [
'kubeconfig_path' => env('K8S_KUBECONFIG', storage_path('kubeconfig')),
'context' => env('K8S_CONTEXT'),
],
'staging' => [
'master_url' => env('K8S_STAGING_URL'),
'token' => env('K8S_STAGING_TOKEN'),
'ca_cert_path' => env('K8S_STAGING_CA'),
'namespace' => 'staging',
],
],
First use case — list and inspect pods from tinker or a command:
use RenokiCo\LaravelK8s\LaravelK8sFacade as K8s;
$cluster = K8s::getCluster(); // default connection
$pods = $cluster->getAllPods('default'); // or $cluster->setDefaultNamespace('default')
foreach ($pods as $pod) {
dump($pod->getName(), $pod->getPhase());
}
Where to look first: config/php-k8s.php for connection shape, then the php-k8s docs for the resource API ($cluster->pod, $cluster->deployment, ->getByName(), ->create(), ->update(), ->delete(), ->watch()).
Environment-per-connection, never a single global cluster. Keep local, staging, prod connections named in config and select explicitly. Avoid defaulting to prod via env in code paths that also run in tests.
$cluster = K8s::connection(config('k8s.target'));
Model-style CRUD. Every resource behaves like an Eloquent model, which is the main ergonomic win:
$cluster->deployment->create([
'metadata' => ['name' => 'api', 'namespace' => 'staging'],
'spec' => [
'replicas' => 3,
'selector' => ['matchLabels' => ['app' => 'api']],
'template' => [/* ... */],
],
]);
$deployment = $cluster->getDeploymentByName('api', 'staging');
$deployment->setReplicas(5)->update();
$deployment->delete();
From-YAML for anything you already maintain as manifests. Keep the YAML in resources/k8s or a GitOps repo and apply it in a deploy command rather than duplicating it as PHP arrays:
$cluster->fromFile(resource_path('k8s/deployment.yaml'))->create();
Artisan commands are the integration surface. Wrap cluster operations in commands (k8s:provision, k8s:rotate-secrets) so they can be scheduled, run in CI, or invoked from a deploy pipeline. Don't call the cluster inline in web requests — latency and failure modes are unbounded.
Jobs for provisioning flows. Dispatching a job that creates a namespace, deployment, service, and ingress in sequence is a natural fit; make each step idempotent (getByName() first, create() only if missing) since retries and partial failures are normal.
Watch loops for reconciliation. ->watch() is the way to build controllers or cheap monitors, but only from a long-running daemon:
$cluster->pod->watch(function ($type, $pod) {
logger()->info($type, ['pod' => $pod->getName()]);
});
Run it under Supervisor or as a dedicated worker container, not inside a queue worker that restarts on deploy.
In-cluster config when the app itself runs in Kubernetes. Point the connection at the mounted service account and the client picks up the token and CA automatically — this is how you build tooling that manages its own cluster.
Testing. Swap the facade in unit tests (K8s::shouldReceive('getCluster')->andReturn($fakeCluster)) and, for integration-ish tests, inject a mocked Guzzle handler into the cluster's API client so no real HTTP leaves the test. Keep a thin ClusterService wrapper around the facade so most tests can mock your class rather than the facade.
composer.json before composer update; pin ^3.3 if you can't move yet.~ is not expanded — use storage_path() or an absolute path. A relative path resolves against the process working directory, which differs between web, queue, and test runs.aws/gcloud; the binary must exist in the container, and long-running workers hold a stale token and start throwing 401s hours later. Re-resolve the connection after credential rotation rather than caching the cluster instance forever.kubectl auth can-i for the service account before debugging the package.watch() blocks indefinitely. Pass a timeout/resource-version if your client supports it, and reconnect with backoff. Without a timeout, a wedged watch silently stops processing events.ca_cert_path per connection instead.base64_decode, and never log an instantiated Secret object.$cluster->getNamespaceByName('x')->delete() removes every resource inside it. Guard destructive commands behind a confirmation or an explicit --force.kubectl as the sanity check. When a call behaves oddly, reproduce the same operation with kubectl -v=8 and compare payloads; the underlying client is a straightforward REST wrapper and differences are usually in the request body.How can I help you explore Laravel packages today?