---
title: "Configuration — Kubernetes API for Laravel | Roundly"
description: "Every config/kubernetes.php key with its env var and default — clusters, HTTP client options, rate limits, the Traefik group and the resource map."
url: https://roundly-consulting.com/open-source/docs/kubernetes-api-for-laravel/configuration
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Kubernetes API for Laravel](https://roundly-consulting.com/open-source/docs/kubernetes-api-for-laravel.md)

# Configuration

The published config/kubernetes.php in full:

```php
use RoundlyConsulting\KubernetesApi\Resources;

return [
    'default' => env('KUBERNETES_CLUSTER', 'default'),
    'clusters' => [
        'default' => [
            'source' => env('KUBERNETES_SOURCE', 'url'),      // url | kubeconfig | in-cluster
            'url' => env('KUBERNETES_URL'),
            'token' => env('KUBERNETES_TOKEN'),
            'certificate' => env('KUBERNETES_CLIENT_CERTIFICATE'),
            'private_key' => env('KUBERNETES_CLIENT_KEY'),
            'ca_certificate' => env('KUBERNETES_CA_CERTIFICATE'),
            'verify' => env('KUBERNETES_VERIFY_SSL', true),
            'kubeconfig' => env('KUBERNETES_KUBECONFIG'),     // null = every KUBECONFIG file merged, or ~/.kube/config
            'context' => env('KUBERNETES_CONTEXT'),           // null = current-context
            'namespace' => env('KUBERNETES_NAMESPACE'),       // null = the kubeconfig context's, else default
            'manager' => env('KUBERNETES_MANAGER'),
            'follow_redirects' => env('KUBERNETES_FOLLOW_REDIRECTS', false),
            'redact_errors' => env('KUBERNETES_REDACT_ERRORS', false),
        ],
    ],
    'client' => [
        'options' => [
            'timeout' => 5,
        ],
        'stream_timeout' => env('KUBERNETES_STREAM_TIMEOUT', 0),
    ],
    'rate_limits' => [
        'enabled' => env('KUBERNETES_RATELIMIT_ENABLED', true),
        'owner' => env('KUBERNETES_RATELIMIT_OWNER', 'app'),
        'max_attempts' => env('KUBERNETES_RATELIMIT', 400),
        'timespan' => env('KUBERNETES_RATELIMIT_TIMESPAN', 'minute'), // second|minute|hour|day
        'adaptive' => env('KUBERNETES_RATELIMIT_ADAPTIVE', true),     // honour 429 Retry-After
        'max_wait' => env('KUBERNETES_RATELIMIT_MAX_WAIT'),           // ms; null = pace, set = fail fast
        'jitter' => env('KUBERNETES_RATELIMIT_JITTER'),               // ms; null = none
    ],
    'traefik' => [
        'group' => 'traefik.io/v1alpha1',
    ],
    'resources' => [
        'clusterRoles' => Resources\ClusterRole::class,
        'clusterRoleBindings' => Resources\ClusterRoleBinding::class,
        'configMaps' => Resources\ConfigMap::class,
        'cronJobs' => Resources\CronJob::class,
        'daemonSets' => Resources\DaemonSet::class,
        'deployments' => Resources\Deployment::class,
        'endpoints' => Resources\Endpoints::class,
        'events' => Resources\Event::class,
        'horizontalPodAutoscalers' => Resources\HorizontalPodAutoscaler::class,
        'ingresses' => Resources\Ingress::class,
        'jobs' => Resources\Job::class,
        'limitRanges' => Resources\LimitRange::class,
        'namespaces' => Resources\KubernetesNamespace::class,
        'networkPolicies' => Resources\NetworkPolicy::class,
        'nodes' => Resources\Node::class,
        'persistentVolumes' => Resources\PersistentVolume::class,
        'persistentVolumeClaims' => Resources\PersistentVolumeClaim::class,
        'pods' => Resources\Pod::class,
        'replicaSets' => Resources\ReplicaSet::class,
        'replicationControllers' => Resources\ReplicationController::class,
        'resourceQuotas' => Resources\ResourceQuota::class,
        'roles' => Resources\Role::class,
        'roleBindings' => Resources\RoleBinding::class,
        'secrets' => Resources\Secret::class,
        'serviceAccounts' => Resources\ServiceAccount::class,
        'services' => Resources\Service::class,
        'statefulSets' => Resources\StatefulSet::class,
        'storageClasses' => Resources\StorageClass::class,
        'traefikIngressRoutes' => Resources\TraefikIngressRoute::class,
        'traefikMiddlewares' => Resources\TraefikMiddleware::class,
        'traefikServersTransports' => Resources\TraefikServersTransport::class,
        'traefikTlsOptions' => Resources\TraefikTlsOption::class,
        'traefikTlsStores' => Resources\TraefikTlsStore::class,
    ],
];
```

## Every key

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `default` | `KUBERNETES_CLUSTER` | default | The cluster Kubernetes::pods(), Kubernetes::ping() and every other default-cluster shortcut use — and what kubernetes:ping checks without an argument. |
| `clusters.<name>.source` | `KUBERNETES_SOURCE` | url | Where the connection comes from: url (the keys below), kubeconfig (a file and context) or in-cluster (the pod’s service account). |
| `clusters.<name>.url` | `KUBERNETES_URL` | null | The apiserver URL for the url source. |
| `clusters.<name>.token` | `KUBERNETES_TOKEN` | null | Bearer token. |
| `clusters.<name>.certificate / private_key / ca_certificate` | `KUBERNETES_CLIENT_CERTIFICATE · …_CLIENT_KEY · …_CA_CERTIFICATE` | null | Paths to the client certificate, its key and the CA bundle. |
| `clusters.<name>.verify` | `KUBERNETES_VERIFY_SSL` | true | TLS verification. Only turn it off for local development. A blank KUBERNETES\_VERIFY\_SSL= is not set and keeps verification on. |
| `clusters.<name>.kubeconfig / context` | `KUBERNETES_KUBECONFIG · KUBERNETES_CONTEXT` | null | For the kubeconfig source: the file (null = every file in KUBECONFIG merged like kubectl — missing files skipped, the first file to set a value wins — or ~/.kube/config) and the context (null = current-context). |
| `clusters.<name>.namespace` | `KUBERNETES_NAMESPACE` | null | The default namespace for namespaced resources on this cluster. Not set: the kubeconfig context’s namespace for a kubeconfig source, else default. |
| `clusters.<name>.manager` | `KUBERNETES_MANAGER` | null | Your app’s field manager, sent as fieldManager on every write and as the user agent. A server-side apply without one uses kubernetes-api-for-laravel. It doesn’t key the rate-limit budget (that is per apiserver). |
| `clusters.<name>.follow_redirects` | `KUBERNETES_FOLLOW_REDIRECTS` | false | Opt the cluster in to following HTTP redirects (Guzzle’s defaults). Off, a 3xx throws KubernetesException — see Connecting to a cluster. Applies whatever the source. |
| `clusters.<name>.redact_errors` | `KUBERNETES_REDACT_ERRORS` | false | KubernetesException messages carry status and reason only (HTTP 409 AlreadyExists) — see Error handling. Applies whatever the source. |
| `client.options` | — | \['timeout' => 5\] | Laravel HTTP client options merged into every request the package sends (timeout, proxy, headers…). The timeout — seconds as an int, a float or a numeric string, 0 = none — bounds ordinary requests; for watches and streamLogs() it bounds only connecting and the response headers, and exec() ignores it. An allow\_redirects set here (a boolean, or Guzzle’s options array; not shipped) is the global redirect policy for clusters that don’t opt in. |
| `client.stream_timeout` | `KUBERNETES_STREAM_TIMEOUT` | 0 | Idle timeout for streams in seconds (an integer from 0 to 86400): after this much silence a watch or log follow ends cleanly, and an unfinished exec() reports no exit code. 0 waits indefinitely, like kubectl. |
| `rate_limits.enabled` | `KUBERNETES_RATELIMIT_ENABLED` | true | Toggle client-side rate limiting; false sends raw, unthrottled requests. |
| `rate_limits.owner` | `KUBERNETES_RATELIMIT_OWNER` | app | Namespaces the budget key, so several apps or workers can share — or isolate — a cluster budget. |
| `rate_limits.max_attempts` | `KUBERNETES_RATELIMIT` | 400 | Requests allowed per cluster per window before pacing kicks in — an integer of at least 1. |
| `rate_limits.timespan` | `KUBERNETES_RATELIMIT_TIMESPAN` | minute | Window length: exactly second, minute, hour or day. A typo such as minutes throws instead of becoming a minute. |
| `rate_limits.adaptive` | `KUBERNETES_RATELIMIT_ADAPTIVE` | true | Honour the apiserver’s Retry-After header on a 429, self-tuning the limiter. |
| `rate_limits.max_wait` | `KUBERNETES_RATELIMIT_MAX_WAIT` | null | null paces (waits). A ceiling in ms (0 or more) fails fast with RateLimitExceededException instead. |
| `rate_limits.jitter` | `KUBERNETES_RATELIMIT_JITTER` | null | Random spread in ms (0 or more) added to each defer, smoothing thundering-herd bursts. |
| `traefik.group` | — | traefik.io/v1alpha1 | API group/version of the bundled Traefik resources; traefik.containo.us/v1alpha1 for Traefik older than v3. |
| `resources` | — | 33 built-in entries | Accessor name → resource class, read at call time. Point a built-in name at your subclass to swap it, or add your own CRDs. Every entry must name a Resource subclass. |

## Environment

The default cluster and the rate-limit knobs are env-driven, so you often don’t need to publish the config at all:

```bash
KUBERNETES_CLUSTER=default            # the default cluster's name
KUBERNETES_SOURCE=url                 # url | kubeconfig | in-cluster
KUBERNETES_URL=https://api.my-cluster.example:6443
KUBERNETES_TOKEN=service-account-token
KUBERNETES_NAMESPACE=shop             # default namespace for namespaced resources
KUBERNETES_MANAGER=shop-app           # field manager (fieldManager on writes) and user agent
KUBERNETES_STREAM_TIMEOUT=0           # idle seconds before a watch or log follow ends (0 = never)
# KUBERNETES_FOLLOW_REDIRECTS=false   # opt in to following HTTP redirects
# KUBERNETES_REDACT_ERRORS=false      # status + reason only in exception messages
```

```bash
KUBERNETES_RATELIMIT=400            # attempts per window, per cluster
KUBERNETES_RATELIMIT_TIMESPAN=minute
KUBERNETES_RATELIMIT_ADAPTIVE=true  # honour 429 Retry-After
# KUBERNETES_RATELIMIT_MAX_WAIT=2000  # ms — fail fast instead of waiting
# KUBERNETES_RATELIMIT_JITTER=250     # ms of random spread per defer
# KUBERNETES_RATELIMIT_OWNER=app
# KUBERNETES_RATELIMIT_ENABLED=false  # the raw, unthrottled client
```

## What strict config refuses

Every key is read strictly: nothing falls back to a default except a key that is not set, and a set but invalid value throws an exception naming the key. Blank means not set: an absent key, null and a blank value (a host’s KEY=, empty or whitespace only) all take the default — on the switches too, where a blank is never false.

- Switches — clusters.<name>.verify, follow\_redirects and redact\_errors, client.options.allow\_redirects, rate\_limits.enabled and rate\_limits.adaptive — accept true/false, 1/0, on/off and yes/no. Anything else throws, so a typo in KUBERNETES\_VERIFY\_SSL can never switch TLS verification off — and a blank KUBERNETES\_VERIFY\_SSL= is not set, so verification stays on.
- Numbers take an int or an integer string ('400'): rate\_limits.max\_attempts (at least 1), rate\_limits.max\_wait and jitter (0 or more) and client.stream\_timeout (0–86400). client.options.timeout also takes a float or a decimal string ('2.5'), 0 or more. 'five', '5.5' for an integer, '5s' or a negative number throws. A blank KUBERNETES\_STREAM\_TIMEOUT= is the default 0, and a blank max\_wait or jitter is unset — never a 0 ms fail-fast ceiling.
- rate\_limits.timespan must be exactly second, minute, hour or day — minutes throws instead of becoming a minute.
- Strings — default, a cluster’s url, credential paths, kubeconfig, context, namespace and manager, plus rate\_limits.owner and traefik.group — must be strings. A blank env such as KUBERNETES\_TOKEN= is not set: no token is sent, exactly as if the line were missing. Likewise KUBERNETES\_NAMESPACE= is the kubeconfig context’s namespace (kubeconfig source) or default, and KUBERNETES\_CLUSTER= the default cluster. A wrong-typed token is named by its type only (\[int\] given.), never its value. An unknown or non-string source throws; a blank one is url.
- Maps — clusters, each clusters.<name> entry, client.options and resources — must be arrays, and every resources entry must name a Resource subclass.

Cluster and client settings throw ClusterConfigurationException — the cluster switches when the cluster is resolved, client.options.allow\_redirects on the first request — resources throws InvalidResourceException, and the rate limits and traefik.group throw the toolkit’s InvalidConfigurationException.

## The resource map

The typed accessors read the resources map at call time and fall back to the packaged class when their name is missing, so a published resources array only needs the entries you add or swap — the published file lists all 33 for reference. Custom names resolve only while they are in the map. An entry that isn’t a Resource subclass, or a resources value that isn’t an array, throws InvalidResourceException.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/kubernetes-api-for-laravel.md)
