This repository teaches Kubernetes deployment in three steps:
- Deploy one Docker image as a simple web application.
- Deploy a multi-image application stack: n8n plus PostgreSQL.
- Use one AI agent skill to apply the same workflow to any application repository.
flowchart LR
A[Application source] --> B[Dockerfile]
B --> C[Docker image]
C --> D[Registry or local cluster]
D --> E[Kubernetes Deployment]
E --> F[Pod]
F --> G[Service]
G --> H[User]
Install Docker, kubectl, and a Kubernetes cluster. Minikube is a simple local choice:
minikube start
kubectl cluster-info
kubectl get nodesThe demo application is a static NGINX site in app/. It listens on port 8080; Kubernetes checks / for readiness and liveness.
| File | Purpose |
|---|---|
Dockerfile |
Packages the site into a non-root NGINX container. |
app/ |
The application HTML. |
k8s/namespace.yaml |
Creates the isolated kubernetes-demo namespace. |
k8s/deployment.yaml |
Runs two replicas, probes /, and defines resources. |
k8s/service.yaml |
Exposes matching Pods internally on port 80. |
docker build -t kubernetes-demo:1.0.0 .
docker run --rm -p 8080:8080 kubernetes-demo:1.0.0In another terminal:
curl --fail http://localhost:8080/The container image must be accessible to the cluster. For Minikube, load the local image:
minikube image load kubernetes-demo:1.0.0For a remote cluster, push a pinned tag to your registry:
docker tag kubernetes-demo:1.0.0 YOUR_REGISTRY_USERNAME/YOUR_IMAGE_NAME:1.0.0
docker push YOUR_REGISTRY_USERNAME/YOUR_IMAGE_NAME:1.0.0Validate first, then apply only the demo manifests and wait for the rollout:
kubectl apply --dry-run=client -f k8s/
kubectl apply -f k8s/
kubectl -n kubernetes-demo rollout status deployment/kubernetes-demo
kubectl -n kubernetes-demo get deployments,pods,services
kubectl -n kubernetes-demo port-forward service/kubernetes-demo 8080:80Open http://localhost:8080. The ClusterIP Service is internal by design; port forwarding is the most reliable local access method. In cloud Kubernetes, use a LoadBalancer Service or Ingress when public access is required.
kubectl -n kubernetes-demo scale deployment/kubernetes-demo --replicas=3
kubectl -n kubernetes-demo get pods
kubectl -n kubernetes-demo delete pod POD_NAME
kubectl -n kubernetes-demo get pods --watch
kubectl -n kubernetes-demo set image deployment/kubernetes-demo \
kubernetes-demo=YOUR_REGISTRY_USERNAME/YOUR_IMAGE_NAME:1.0.1
kubectl -n kubernetes-demo rollout status deployment/kubernetes-demo
kubectl -n kubernetes-demo rollout undo deployment/kubernetes-demoScaling changes the desired number of Pods. Deleting a Pod demonstrates self-healing: the Deployment creates a replacement. A pinned image update creates a rolling update; rollout undo reverts it.
n8n.yaml is a real multi-workload example. It contains these images:
| Image | Kubernetes workload | Purpose |
|---|---|---|
n8nio/n8n:2.35.0 |
Deployment | n8n editor, workflows, webhooks, and API. |
postgres:16-alpine |
StatefulSet | Persistent database for n8n. |
postgres:16-alpine |
n8n init container | Waits for PostgreSQL before n8n starts. |
This is not a multi-container Pod: n8n and PostgreSQL run in separate Pods because they have independent lifecycles and storage. The Services give each workload stable DNS:
flowchart LR
U[Browser or webhook] --> I[Traefik Ingress]
I --> S[n8n Service]
S --> N[n8n Pod]
N --> P[PostgreSQL Service]
P --> DB[PostgreSQL StatefulSet and PVC]
C[cert-manager] --> T[n8n TLS Secret]
T --> I
The n8n example is intentionally closer to production and requires pre-existing cluster services:
kubectl get ingressclass traefik
kubectl get clusterissuer letsencrypt-prod
kubectl get storageclass longhornIt also requires a DNS record for n8n.nstsdc.org to reach Traefik and a n8n-secrets Secret containing postgres-password and a persistent encryption-key. Do not commit Secret values. Create them locally and apply them directly:
kubectl apply -f namespace.yaml
kubectl -n n8n create secret generic n8n-secrets \
--from-literal=postgres-password='CHOOSE_A_STRONG_PASSWORD' \
--from-literal=encryption-key="$(openssl rand -hex 32)" \
--dry-run=client -o yaml | kubectl apply -f -Keep the original encryption key securely backed up. Changing it prevents n8n from decrypting existing credentials.
kubectl apply --dry-run=client -f namespace.yaml
kubectl apply --dry-run=client -f n8n.yaml
kubectl apply -f namespace.yaml
kubectl apply -f n8n.yaml
kubectl -n n8n rollout status statefulset/postgres --timeout=10m
kubectl -n n8n rollout status deployment/n8n --timeout=10m
kubectl -n n8n wait --for=condition=Ready certificate/n8n-tls --timeout=10m
kubectl -n n8n get pods,svc,pvc,ingress,certificate
curl --fail --location https://n8n.nstsdc.org/healthzThe n8n manifest shows additional Kubernetes concerns that a single app does not need: StatefulSets, PVCs, an init container, Secrets, TLS, and Ingress.
The single reusable skill is .github/skills/kubernetes-deployment/SKILL.md. It follows:
flowchart LR
U[User request] --> A[AI deployment skill]
A --> B[Inspect repository]
B --> C{Image supplied?}
C -- No --> D[Dockerize and build]
C -- Yes --> E[Use existing image]
D --> F[Generate or validate YAML]
E --> F
F --> G[Dry run and deploy]
G --> H[Verify rollout and endpoint]
H --> I{Healthy?}
I -- No --> J[Inspect logs and events]
J --> G
I -- Yes --> K[Report status]
Use it with a bounded prompt:
Use the kubernetes-deployment skill. Inspect this repository, Dockerize it if necessary, and deploy it to my current Kubernetes cluster. Use the kubernetes-demo namespace, validate manifests first, wait for rollout, and verify the health endpoint. Do not modify other namespaces.
If an image already exists, the agent should skip Docker build work:
Use the kubernetes-deployment skill. Deploy image YOUR_REGISTRY_USERNAME/YOUR_IMAGE_NAME:1.0.0 to my current Kubernetes cluster. Use the kubernetes-demo namespace and verify the service endpoint.
The skill detects the runtime, port, commands, environment variables, Docker configuration, image accessibility, and existing Kubernetes assets. It validates YAML before applying, checks rollout health, and inspects events and logs before changing failed resources.
Never give an agent kubeconfig contents, passwords, tokens, private keys, registry credentials, or Kubernetes Secret values. Confirm the context, namespace, image, and every state-changing command.
kubectl config current-context
kubectl -n kubernetes-demo get pods,svc
kubectl -n kubernetes-demo describe pod POD_NAME
kubectl -n kubernetes-demo logs POD_NAME
kubectl -n kubernetes-demo logs POD_NAME --previous
kubectl -n kubernetes-demo get events --sort-by=.metadata.creationTimestamp| Symptom | What to inspect |
|---|---|
ImagePullBackOff or ErrImagePull |
Image name, pinned tag, registry access, or Minikube image loading. |
CrashLoopBackOff |
Container logs, production command, required environment variables, and bind address. |
Pending |
Pod events, resource requests, storage, and node capacity. |
| Service is unreachable | Service selector, Pod labels, targetPort, container port, and endpoint readiness. |
| n8n certificate not ready | DNS, Traefik, letsencrypt-prod, and cert-manager events. |
kubectl delete -f k8s/namespace.yamlThis removes only the kubernetes-demo namespace. Do not delete the n8n namespace or its PVCs unless you explicitly intend to remove the n8n deployment and persistent data.