Build self-service infrastructure provisioning that gives developers speed while maintaining security, compliance, and cost controls. Covers Terraform modules, Crossplane, guardrails, approval workflows, and the balance between autonomy and governance.
Self-service infrastructure is the layer that lets developers request, modify, and destroy compute, storage, and network resources through automated workflows rather than ticketing queues. It matters when platform teams want to reduce lead time for new services or environments without abdicating operational control. The pattern appears across Kubernetes‑based platforms, VM pools, and serverless runtimes, but the underlying mechanics—API‑driven provisioning, policy enforcement, and state reconciliation—are the same.
What you’re trying to do: spin up an isolated environment that mirrors production capabilities but lives in a temporary namespace or project, so you can code, test, and tear down without impacting shared resources.
Create a namespace scoped to your identity:
kubectl create namespace dev-$(echo "$USER" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g')
If the cluster enforces a DNS‑1123 subdomain naming policy, the command may reject the label. The exact error from kubectl when the name violates the pattern is:
Error: the namespace "dev-jane-doe" is invalid: metadata.name must match the pattern "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"
Apply a minimal manifest to confirm the sandbox is functional:
kubectl apply -f https://raw.githubusercontent.com/example/infra-templates/main/sandbox.yaml
The sandbox.yaml typically contains a single Deployment with a sidecar for logging and a Service of type ClusterIP. If the template references a container image that doesn’t exist in your registry, kubectl returns:
Error: failed to pull image "registry.example.com/dev/python:latest": repository does not exist or may require authentication
Sharp edges: namespace propagation delay can cause kubectl get pods to return *No resources found* for a few seconds after creation. Additionally, if your cluster uses resource quotas, a sandbox that forgets to set limits may silently consume all available CPU or memory, causing other workloads on the same node to be evicted without a clear error in the sandbox’s own logs.
What you’re trying to do: grant just‑in‑time permissions that let a developer create sandboxes or push images, without opening the cluster to everyone or forcing a multi‑day ticket for each action.
Define a minimal RBAC role scoped to the developer’s own namespace:
kubectl create role developer-sandbox \
--resource=pods,services,configmaps \
--verb=get,list,watch,create,update \
--namespace=dev-{{user}}
The --namespace flag scopes the role to the developer’s own namespace; omitting it makes the role cluster‑scoped, a common misconfiguration that accidentally grants cluster‑wide write access.
Bind the role to a user or service account:
kubectl create rolebinding developer-sandbox-binding \
--role=developer-sandbox \
[email protected]
Verify the permissions before relying on them:
kubectl auth can-i create pods --namespace=dev-alice
kubectl returns one of two exact outputs depending on the outcome:
yesnoIf the user receives no but believes they should have access, the sharp edge is that kubectl auth can-i checks the current aggregation‑rule cache; recent role changes may not propagate immediately, and the command may return no even after a role has been bound, until the kube‑apiserver’s RBAC cache refreshes (typically within a few seconds to a minute, depending on cluster load).
A frequent confusion involves the --dry-run=client flag on kubectl apply. This flag only validates local YAML syntax and does not contact the API server. A developer may assume the command will reject insecure configurations, run it, see no error, and then push a manifest that the API server immediately rejects with a 403. The exact error text in that case is:
Error: the server could not find the requested resource (get pods)
or, if the object kind is unrecognized:
Error: unable to recognize "": the server fails to match "kind" with any registered group/version/resource
(The precise message depends on the API version and whether the object kind is registered.)
What you’re trying to do: iterate on a service deployed through the self‑service pipeline, see changes reflected quickly, and roll back if something breaks.
Build and push a new container image:
docker build -t registry.example.com/dev/myapp:$GIT_SHA .
docker push registry.example.com/dev/myapp:$GIT_SHA
$GIT_SHA is typically git rev-parse --short HEAD. If the registry requires authentication and the docker config is missing or expired, the push emits:
The push refers to repository [registry.example.com/dev/myapp]
error: authentication required
Update the Kubernetes deployment to use the new image:
kubectl set image deployment/my-app my-app=registry.example.com/dev/myapp:$GIT_SHA
If the deployment name doesn’t match a live resource, kubectl returns:
Error: the server could not find the requested resource (deployments.apps "my-app")
Watch the rollout status:
kubectl rollout status deployment/my-app --namespace=dev-alice
A successful rollout outputs:
deployment "my-app" successfully rolled out
If a health check fails, the command hangs until the timeout, and kubectl describe pod my-app-xxx will show ContainersNotReady or CrashLoopBackOff with the container’s stderr output.
Sharp edges: kubectl set image only updates the image tag; it does not restart the pod automatically. The kubelet will pull the new image on the next sync cycle (usually every 30 seconds), but if the pod has restartPolicy: Never or is managed by a ReplicaSet with an old pod template, the change may not propagate. Additionally, image‑pull secrets that work in one namespace may be absent in another, causing a silent pull failure that only surfaces as ImagePullBackOff in kubectl get pods.
Several failure modes in self‑service infrastructure do not produce immediate, obvious errors. Instead, they create subtle state drift that only appears later.
Quota exhaustion without admission enforcement: If a cluster has a ResourceQuota but no LimitRange, a developer can submit a manifest that requests cpu: "500m" and memory: "1Gi" per container without the API server rejecting it at write time. The quota is only enforced at scheduling time, and if the node lacks resources, the pod enters Pending with the reason Insufficient cpu. The developer may never see a clear error because kubectl get pods only shows Pending indefinitely.
Webhook timeouts: Many platforms insert an admission webhook to validate or mutate requests. If the webhook endpoint is slow or unreachable, the API server may return a 504 Gateway Timeout. The exact HTTP response body from a typical Kubernetes API server when a webhook times out is:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "admission webhook timed out",
"reason": "TimeoutError"
}
Developers often mistake this for a network issue and retry the command, each retry hitting the same timeout until the webhook recovers.
Permission cache staleness: As noted, kubectl auth can-i may return yes based on a cached RBAC decision. If a role was just removed, the command may still grant access for up to the cache TTL (often 10–60 seconds). This can lead to a developer successfully creating a resource that is immediately forbidden for subsequent operations, producing confusing error sequences.
Confused options: the --record flag on kubectl apply is frequently mistaken for an audit flag. In reality, --record only adds an annotation kubectl.kubernetes.io/last-applied-configuration to the target resource; it does not write to any audit log or enforce immutability. A developer who relies on --record for change tracking will find the annotation overwritten on every subsequent apply, and no historical diff is retained unless a separate tool (e.g., git) is used.
Lessons learned after breakage: always precede a production‑affecting kubectl apply with --dry-run=server to confirm the API server accepts the object, and inspect the resulting dry‑run output for admission‑review messages. Additionally, habit‑check resource quotas and limit ranges in the target namespace before running load‑generating workloads.
What you’re trying to do: embed policies that restrict resource shapes, namespace naming, or expiration into the provisioning flow so that developers get immediate feedback at the point of request, rather than discovering violations after the fact.
Most self‑service platforms insert an admission webhook or controller that evaluates the incoming request object against a set of predefined rules before the resource is persisted. If the evaluation fails, the API server returns a 403 with a structured problem‑details payload that explains which rule was violated and how to fix it. A typical failure response body follows the Kubernetes admission‑review format:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "admission review denied: resource violates policy compute-resource-limits",
"reason": "AdmissionError"
}
The exact message and reason strings depend on the platform’s policy language, but the surrounding JSON envelope is always the same because it is defined by the Kubernetes admission‑review specification.
Concrete guardrail examples that work across many platforms:
dev-* or feature/*). If it doesn’t, the response message reads: "namespace "staging" does not match allowed pattern "dev-*""
requests.cpu and requests.memory from the container spec and rejects any pod where either request exceeds the namespace‑wide quota. The denial message includes the offending value: "container my-app exceeded allowed cpu request "2" for namespace "dev-alice" (max "500m")"
spec.expiresAt field. The webhook validates that the timestamp is not in the past and that the resource will be automatically garbage‑collected after the declared interval. If the timestamp is missing or already passed, the response is: "spec.expiresAt must be a future timestamp or omitted"
Because these validations happen in the admission layer, the developer receives the error before the resource is created, eliminating the “silent failure” window described earlier. The platform team retains control because the policies are defined in version‑controlled config files that the API server enforces, not in a ticketing system that can be bypassed.
kubectl, docker, git, psql, systemctl) provides the immediate commands you’ll use day‑to‑day, but the real power comes from the admission and RBAC layers that enforce guardrails.kubectl auth can-i, a webhook denial, or a quota violation—is your diagnostic starting point; never assume a missing error means “everything is fine.”--dry-run=server and verify the returned status before committing.This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy