Production-grade Kubernetes manifests and debugging for containerized applications. This skill should be used when users ask to deploy to Kubernetes, create K8s manifests, containerize for K8s, set up Deployments/Services/Jobs/StatefulSets/CronJobs, create namespaces with resource quotas, set up multi-team isolation, configure ResourceQuota/ LimitRange, secure with RBAC (ServiceAccount, Role, RoleBinding), configure init containers (model download, db wait, migrations), set up sidecars (logging, metrics), or debug pods (CrashLoopBackOff, logs, exec, describe, events). Auto-detects from Dockerfile/code, generates hardened manifests with educational comments. CKAD-aligned.
Production-grade K8s manifests with security-first defaults and educational comments.
Before generating manifests, detect the target environment:
# Detect node resources
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}: {.status.capacity.memory}, {.status.capacity.cpu}{"\n"}{end}'
# Detect if Docker Desktop (local) or real cluster
kubectl get nodes -o jsonpath='{.items[0].metadata.labels.node\.kubernetes\.io/instance-type}' 2>/dev/null || echo "local"
# Detect available resources
kubectl describe nodes | grep -A 5 "Allocated resources"
Adapt configurations based on detection:
| Detected Environment | Profile | Default Limits | Agent Action | |---------------------|---------|----------------|--------------| | Docker Desktop < 6GB | Minimal | 128Mi-256Mi | Warn, reduce replicas | | Docker Desktop 6-10GB | Standard | 256Mi-512Mi | Normal deployment | | Cloud/Real cluster | Production | Based on node size | Full features |
(node_memory * 0.7) / expected_pod_countLocal/Constrained (< 6GB allocatable):
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 256Mi
cpu: 500m
Standard (6-16GB allocatable):
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 1000m
Production (> 16GB or cloud):
resources:
requests:
memory: 512Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 2000m
Before applying manifests, agent should verify:
# Check if deployment would exceed node capacity
kubectl get nodes -o jsonpath='{.items[0].status.allocatable.memory}'
If insufficient: warn user and suggest scaling down or increasing Docker Desktop resources.
Analysis & Detection:
Generation:
Validation:
Security:
Gather context to ensure successful implementation:
| Source | Gather | |--------|--------| | Codebase | Dockerfile, existing manifests, port/health patterns | | Conversation | Target environment, namespace, special requirements | | Skill References | Security contexts, health probes, resource limits | | User Guidelines | Cluster conventions, naming standards |
After auto-detection, confirm with user if ambiguous:
| Question | When to Ask | |----------|-------------| | Target environment | "Deploying to local (kind/minikube) or remote cluster?" | | Namespace | "Use existing namespace or create new?" | | Image availability | "Is image in registry or needs to be built/loaded?" | | Service exposure | "Internal only (ClusterIP) or external access needed?" | | Namespace governance | "Need ResourceQuota/LimitRange for resource isolation?" | | Multi-team setup | "Single team or multi-team with namespace isolation?" | | Environment progression | "Creating dev/staging/prod namespaces with quota progression?" |
Before generating manifests, verify:
# 1. Cluster access
kubectl cluster-info
# 2. Current context
kubectl config current-context
# 3. Target namespace (create if needed)
kubectl get namespace $NAMESPACE || kubectl create namespace $NAMESPACE
# 4. Image exists (or build it)
docker images | grep $IMAGE_NAME || docker build -t $IMAGE_NAME .
# 5. For local clusters: load image
kind load docker-image $IMAGE_NAME # or minikube image load
If any check fails → stop and report. Don't generate manifests for broken state.
| Detect | How | Example |
|--------|-----|---------|
| Port | EXPOSE instruction | EXPOSE 8000 → containerPort: 8000 |
| Health | CMD with health endpoint | uvicorn → /health or /healthz |
| User | USER instruction | USER 1000 → runAsUser: 1000 |
| Workdir | WORKDIR instruction | Context for volume mounts |
Privileged ports (<1024) conflict with runAsNonRoot: true.
| Detected Port | Action | |---------------|--------| | 80, 443 | ⚠️ Use unprivileged variant (nginx-unprivileged:8080) or remap | | 8080, 8000, 3000+ | ✅ Compatible with non-root |
Common remappings:
| Standard Image | Security-Compatible Alternative |
|----------------|--------------------------------|
| nginx (port 80) | nginxinc/nginx-unprivileged (port 8080) |
| httpd (port 80) | Configure Listen 8080 or use unprivileged image |
| redis (port 6379) | ✅ Already unprivileged |
| postgres (port 5432) | ✅ Already unprivileged |
Service abstracts this: Service port: 80 → targetPort: 8080 keeps external API stable.
| Detect | How | Example |
|--------|-----|---------|
| Framework health | Route definitions | FastAPI /health, Express /healthz |
| Readiness | DB connection check | /health/ready with DB ping |
| Startup time | Heavy imports | ML models → startupProbe needed |
Is this a one-time task that completes?
→ Job (or CronJob if scheduled)
Does it need stable network identity or ordered deployment?
→ StatefulSet
Must run on every node?
→ DaemonSet
Otherwise → Deployment (default)
1. PRE-FLIGHT
- Verify kubectl context
- Check namespace exists
- Verify image exists or build it
↓
2. ANALYZE PROJECT
- Read Dockerfile for EXPOSE, HEALTHCHECK, USER
- Scan code for health endpoints
- Check existing k8s/ directory
- Detect GPU requirements (torch, tensorflow)
↓
3. DETERMINE WORKLOAD TYPE
- Deployment (default)
- Job/CronJob (batch processing)
- StatefulSet (databases, ordered)
- DaemonSet (node-level agents)
↓
4. GENERATE MANIFESTS
- Deployment/Job/StatefulSet with hardened security
- Service (ClusterIP, NodePort, or LoadBalancer)
- ConfigMap for non-secret config
- HPA if autoscaling needed
- PDB for availability
- All with educational comments
↓
5. VALIDATE
- kubectl apply --dry-run=server
- kubectl apply -n $NAMESPACE
- kubectl wait --for=condition=Ready pod
- kubectl logs to verify startup
↓
6. DELIVER
- Files in k8s/base/ directory
- Summary of what was created
- Next steps for production
k8s/
├── base/ # Raw manifests (ArgoCD-compatible)
│ ├── namespace.yaml # Optional, if new namespace
│ ├── resourcequota.yaml # Namespace-wide resource caps
│ ├── limitrange.yaml # Per-container defaults and bounds
│ ├── networkpolicy.yaml # Namespace isolation rules
│ ├── deployment.yaml # Or job.yaml, statefulset.yaml
│ ├── service.yaml # ClusterIP by default
│ ├── configmap.yaml # Non-secret configuration
│ ├── hpa.yaml # If autoscaling enabled
│ ├── pdb.yaml # Pod Disruption Budget
│ └── kustomization.yaml # For future Kustomize use
└── README.md # Deployment instructions
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${APP_NAME}
labels:
# Standard K8s labels (see references/labels-annotations.md)
app.kubernetes.io/name: ${APP_NAME}
app.kubernetes.io/instance: ${APP_NAME}-${ENV}
app.kubernetes.io/version: "${VERSION}"
app.kubernetes.io/component: api # or worker, frontend
app.kubernetes.io/part-of: ${PROJECT}
app.kubernetes.io/managed-by: kubectl
spec:
replicas: 2 # WHY: Minimum for availability during rolling updates
selector:
matchLabels:
app.kubernetes.io/name: ${APP_NAME}
template:
metadata:
labels:
app.kubernetes.io/name: ${APP_NAME}
spec:
# WHY: Security hardening - never run as root
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: ${APP_NAME}
image: ${IMAGE}:${TAG}
# WHY: Never use :latest - breaks reproducibility
imagePullPolicy: IfNotPresent
ports:
# WHY: Port must be >=1024 for runAsNonRoot (privileged ports need root)
# Use Service port:80 → targetPort:8080 to expose standard ports externally
- containerPort: ${PORT} # Must be >=1024 (e.g., 8080, 8000, 3000)
protocol: TCP
# WHY: Container-level security context
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
# WHY: Prevent resource starvation, enable HPA
resources:
requests:
cpu: "100m" # 0.1 CPU cores
memory: "128Mi"
limits:
cpu: "500m" # 0.5 CPU cores
memory: "512Mi"
# WHY: K8s restarts if app deadlocks
livenessProbe:
httpGet:
path: /health/live
port: ${PORT}
initialDelaySeconds: 10
periodSeconds: 15
failureThreshold: 3
# WHY: Only route traffic when ready
readinessProbe:
httpGet:
path: /health/ready
port: ${PORT}
initialDelaySeconds: 5
periodSeconds: 10
# WHY: Slow-starting apps (ML models) need longer startup
startupProbe:
httpGet:
path: /health/live
port: ${PORT}
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 30 # 5 minutes to start
# WHY: Graceful shutdown for in-flight requests
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
# WHY: Allow time for graceful shutdown
terminationGracePeriodSeconds: 30
apiVersion: v1
kind: Service
metadata:
name: ${APP_NAME}
labels:
app.kubernetes.io/name: ${APP_NAME}
spec:
# WHY: ClusterIP is safest default - internal only
# Use NodePort for dev/testing, LoadBalancer for prod external access
type: ClusterIP
ports:
# WHY: Service abstracts internal port - clients connect to :80, Pod runs on :8080
# This allows standard external ports while container runs unprivileged
- port: 80 # WHY: Service port (what clients connect to)
targetPort: ${PORT} # WHY: Pod port (>=1024, e.g., 8080)
protocol: TCP
name: http
selector:
# CRITICAL: Must EXACTLY match Pod template labels from Deployment
# Mismatch = zero endpoints = Service routes to nothing
app.kubernetes.io/name: ${APP_NAME}
Verify Service→Pod connection: kubectl get endpoints ${APP_NAME}
<none> if selector MISMATCHES Pod labelsSee references/security-contexts.md for full patterns.
# Pod level
securityContext:
runAsNonRoot: true # WHY: Never run as root
runAsUser: 1000 # WHY: Consistent non-root UID
runAsGroup: 1000 # WHY: Consistent GID
fsGroup: 1000 # WHY: Volume permissions
seccompProfile:
type: RuntimeDefault # WHY: Block dangerous syscalls
# Container level
securityContext:
allowPrivilegeEscalation: false # WHY: Prevent root escalation
readOnlyRootFilesystem: true # WHY: Immutable container
capabilities:
drop: ["ALL"] # WHY: Minimal capabilities
Before delivering, verify:
app.kubernetes.io/* labelskubectl apply --dry-run=server passeskubectl get endpoints shows Pod IPs, not <none>)| File | Purpose |
|------|---------|
| references/security-contexts.md | CRITICAL: Hardened security patterns |
| references/health-probes.md | CRITICAL: Liveness/readiness/startup |
| references/resource-limits.md | CRITICAL: CPU/memory guidance |
| references/namespace-governance.md | CRITICAL: ResourceQuota, LimitRange, NetworkPolicy, multi-team isolation |
| File | When to Read |
|------|--------------|
| references/debugging-workflow.md | CRITICAL: CrashLoopBackOff, command safety, logs, exec, debug containers |
| references/deployment-gotchas.md | CRITICAL: Architecture mismatch, ImagePull failures, pre-deploy validation, Helm gotchas |
| references/networking-patterns.md | DEBUGGING: Service has no endpoints, selector mismatch, DNS issues |
| references/control-plane.md | DEBUGGING: When deployments fail, pods stuck, rollback needed |
| File | When to Read |
|------|--------------|
| references/workload-types.md | Choosing Deployment vs Job vs StatefulSet |
| references/init-sidecar-patterns.md | Init containers (model download, db wait), sidecars (logging, metrics) |
| references/autoscaling-patterns.md | HPA, custom metrics, KEDA |
| references/gpu-workloads.md | AI/ML workloads with GPU |
| references/keda-patterns.md | Event-driven scale-to-zero |
| File | When to Read |
|------|--------------|
| references/networking-patterns.md | Service types, Ingress, mesh |
| references/storage-patterns.md | PVC, ephemeral, shared storage |
| references/configmap-patterns.md | ConfigMap creation, env vars, volumes, hot-reload |
| references/secrets-patterns.md | ESO, Sealed Secrets, K8s Secrets |
| references/rbac-patterns.md | SECURITY: ServiceAccount, Role, RoleBinding, least privilege |
| references/labels-annotations.md | Standard labels, ArgoCD compat |
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer