{"id":49342330,"url":"https://github.com/baiganio/k3s-pi5","last_synced_at":"2026-04-27T05:03:10.471Z","repository":{"id":350473953,"uuid":"1206794133","full_name":"BaiGanio/k3s-pi5","owner":"BaiGanio","description":"Quick instructions on how to setup k3s on Raspberry Pi 5 and tunnel domain name with Cloudflare","archived":false,"fork":false,"pushed_at":"2026-04-10T13:30:57.000Z","size":70,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-10T15:24:20.598Z","etag":null,"topics":["instructions","k3s","raspberry-pi"],"latest_commit_sha":null,"homepage":"https://baiganio.github.io/k3s-pi5/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BaiGanio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-10T09:02:51.000Z","updated_at":"2026-04-10T13:31:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/BaiGanio/k3s-pi5","commit_stats":null,"previous_names":["baiganio/k3s-pi5"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/BaiGanio/k3s-pi5","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BaiGanio%2Fk3s-pi5","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BaiGanio%2Fk3s-pi5/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BaiGanio%2Fk3s-pi5/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BaiGanio%2Fk3s-pi5/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BaiGanio","download_url":"https://codeload.github.com/BaiGanio/k3s-pi5/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BaiGanio%2Fk3s-pi5/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32323215,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T23:26:28.701Z","status":"online","status_checked_at":"2026-04-27T02:00:06.769Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["instructions","k3s","raspberry-pi"],"created_at":"2026-04-27T05:03:08.767Z","updated_at":"2026-04-27T05:03:10.460Z","avatar_url":"https://github.com/BaiGanio.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 👉 [https://baiganio.github.io/k3s-pi5](https://baiganio.github.io/k3s-pi5/)\n\n\n### Step 3: Grant Non-Root Access to kubectl (Optional)\n\nTo avoid typing `sudo` every time:\n\n```bash\n# Create kubeconfig at user level\nmkdir -p ~/.kube\nsudo k3s kubectl config view --raw | sudo tee ~/.kube/config \u003e /dev/null\nsudo chown $(id -u):$(id -g) ~/.kube/config\nsudo chmod 600 ~/.kube/config\n\n# Now you can use kubectl without sudo\nkubectl get nodes\n```\n\n### Step 4: Verify Docker/containerd\n\nK3s uses containerd (not Docker) by default. Check it's working:\n\n```bash\n# List running containers\nsudo crictl ps\n\n# This shows all containers managed by k3s itself\n# (e.g., coredns, metrics-server, local-path-provisioner)\n```\n\n**Why containerd instead of Docker:** Containerd is lighter and more suitable for resource-constrained environments.\n\n---\n\n## Cloudflare Tunnel Configuration\n\n### What's Happening Here\n\nYou'll configure Cloudflare Tunnel (formerly Argo Tunnel) to:\n1. Route external traffic from your Cloudflare domain → your Pi's k3s Nginx Ingress\n2. Create a secure tunnel without exposing your home IP\n3. Let you access apps like `app.yourdomain.com`\n\n### Step 1: Install Cloudflare Tunnel Agent\n\n```bash\n# Download the latest cloudflared binary for ARM64\nwget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64\n\n# Make it executable\nchmod +x cloudflared-linux-arm64\n\n# Move to system path\nsudo mv cloudflared-linux-arm64 /usr/local/bin/cloudflared\n\n# Verify installation\ncloudflared --version\n```\n\n### Step 2: Authenticate with Cloudflare\n\n```bash\n# This opens your browser to Cloudflare login\ncloudflared tunnel login\n\n# You'll:\n# 1. Log in to your Cloudflare account\n# 2. Select your domain\n# 3. A cert file is saved locally\n\n# Check it was saved\ncat ~/.cloudflared/cert.pem\n# Should show: -----BEGIN CERTIFICATE-----\n```\n\n### Step 3: Create a Tunnel\n\n```bash\n# Create a new tunnel (replace \"my-pi\" with a meaningful name)\ncloudflared tunnel create my-pi\n\n# Output:\n# Tunnel credentials written to ~/.cloudflared/[tunnel-id].json\n# Tunnel my-pi created with ID [tunnel-id]\n\n# Save the tunnel ID for next steps\nTUNNEL_ID=$(cloudflared tunnel list | grep my-pi | awk '{print $1}')\necho $TUNNEL_ID\n```\n\n### Step 4: Configure Tunnel Routing\n\nCreate a config file at `~/.cloudflared/config.yml`:\n\n```bash\nnano ~/.cloudflared/config.yml\n```\n\nAdd this content (adjust domains as needed):\n\n```yaml\ntunnel: my-pi  # Match the tunnel name from Step 3\ncredentials-file: /home/pi/.cloudflared/[TUNNEL_ID].json\n# Replace [TUNNEL_ID] with actual tunnel ID from previous step\n\ningress:\n  # Route your domain to k3s Nginx Ingress (running on port 80)\n  - hostname: yourdomain.com\n    service: http://localhost/\n  \n  - hostname: \"*.yourdomain.com\"\n    service: http://localhost/\n  \n  # Catch-all for any other traffic\n  - service: http_status:404\n```\n\n**What this does:**\n- `hostname: yourdomain.com` → Routes root domain to your k3s Ingress\n- `\"*.yourdomain.com\"` → Routes all subdomains (app.yourdomain.com, api.yourdomain.com, etc.)\n- `service: http_status:404` → Returns 404 for unmapped hostnames\n\n### Step 5: Test Tunnel Connection\n\n```bash\n# Start tunnel manually to test\ncloudflared tunnel run my-pi\n\n# You should see:\n# ... tunnel registered with Cloudflare\n# ... routing rules active\n# ... connected to edge\n\n# Press Ctrl+C to stop (you'll create a systemd service next)\n```\n\n### Step 6: Create Systemd Service for Tunnel\n\n```bash\n# Create systemd unit file\nsudo nano /etc/systemd/system/cloudflared.service\n```\n\nAdd:\n\n```ini\n[Unit]\nDescription=Cloudflare Tunnel\nAfter=network.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=pi\nWorkingDirectory=/home/pi/.cloudflared\nExecStart=/usr/local/bin/cloudflared tunnel run my-pi\nRestart=on-failure\nRestartSec=5s\n\n[Install]\nWantedBy=multi-user.target\n```\n\n```bash\n# Enable and start the service\nsudo systemctl daemon-reload\nsudo systemctl enable cloudflared\nsudo systemctl start cloudflared\n\n# Check status\nsudo systemctl status cloudflared\n\n# View logs\nsudo systemctl journalctl -u cloudflared -f\n```\n\n---\n\n## Persistent Storage Setup\n\n### What's Happening\n\nFor databases (PostgreSQL, MySQL) to survive pod restarts, we need persistent storage. K3s provides a built-in storage provisioner, but we'll also set up proper volumes.\n\n### Step 1: Check Default Storage Class\n\nK3s ships with a \"local-path-provisioner\" storage class:\n\n```bash\n# List storage classes\nkubectl get storageclass\n\n# Expected output:\n# NAME                   PROVISIONER             RECLAIM POLICY\n# local-path (default)   rancher.io/local-path   Delete\n```\n\nThis stores data at `/var/lib/rancher/k3s/storage/` on the Pi.\n\n### Step 2: Create a Data Directory for Databases\n\nFor better organization, create a dedicated directory:\n\n```bash\n# Create directory for persistent data\nsudo mkdir -p /mnt/k3s-data\nsudo mkdir -p /mnt/k3s-data/databases\nsudo mkdir -p /mnt/k3s-data/applications\n\n# Set permissions (allow k3s user to access)\nsudo chown -R 1000:1000 /mnt/k3s-data\nsudo chmod -R 755 /mnt/k3s-data\n```\n\n**Why:**\n- Separates Kubernetes data from OS system files\n- Easier to back up or expand storage later\n- If needed, you can mount an external USB drive here\n\n### Step 3: Create Persistent Volume (Manual Example)\n\nFor testing, we'll create a PersistentVolume manually:\n\n```bash\n# Create a manifest for persistent storage\nnano pv-example.yaml\n```\n\nAdd:\n\n```yaml\napiVersion: v1\nkind: PersistentVolume\nmetadata:\n  name: database-pv\nspec:\n  capacity:\n    storage: 5Gi\n  accessModes:\n    - ReadWriteOnce\n  storageClassName: local-path\n  hostPath:\n    path: \"/mnt/k3s-data/databases\"\n    type: Directory\n---\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: database-pvc\n  namespace: default\nspec:\n  accessModes:\n    - ReadWriteOnce\n  storageClassName: local-path\n  resources:\n    requests:\n      storage: 5Gi\n```\n\nApply it:\n\n```bash\nkubectl apply -f pv-example.yaml\n\n# Verify\nkubectl get pv\nkubectl get pvc\n```\n\n**What this does:**\n- **PersistentVolume (PV):** Allocates 5GB from `/mnt/k3s-data/databases`\n- **PersistentVolumeClaim (PVC):** Applications claim storage through this\n\n---\n\n## Nginx Ingress Controller\n\n### What's an Ingress?\n\nAn Ingress is a Kubernetes resource that:\n- Routes external traffic to internal services\n- Handles hostname-based routing (example.com → Service A, api.example.com → Service B)\n- Provides load balancing and SSL termination\n\n### Step 1: Check If Traefik is Already Installed\n\nK3s comes with Traefik (an ingress controller) by default:\n\n```bash\n# Check Traefik pods\nkubectl get pods -n kube-system | grep traefik\n\n# Expected: traefik pod should be running\n```\n\nTraefik is simpler than Nginx for k3s. We'll use it, but if you prefer Nginx, see the Alternative section below.\n\n### Step 2: Verify Traefik Service\n\n```bash\n# Check Traefik service\nkubectl get svc -n kube-system traefik\n\n# Expected output shows:\n# TYPE: LoadBalancer\n# EXTERNAL-IP: 192.168.x.x (your Pi's IP)\n# PORT(S): 80:xxxxx/TCP, 443:xxxxx/TCP\n```\n\nThis means Traefik is already listening on ports 80 and 443.\n\n### Step 3: Test with a Simple Ingress\n\nCreate a test ingress:\n\n```bash\nnano test-ingress.yaml\n```\n\nAdd:\n\n```yaml\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: test-ingress\n  annotations:\n    # Traefik annotation (different from standard Nginx)\n    traefik.ingress.kubernetes.io/router.entrypoints: web\nspec:\n  rules:\n    - host: test.yourdomain.com\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: nginx-test\n                port:\n                  number: 80\n```\n\n```bash\nkubectl apply -f test-ingress.yaml\n```\n\n---\n\n## K3s Dashboard\n\n### What's the Dashboard?\n\nA web UI to visualize:\n- Pods, Services, Deployments\n- Resource usage (CPU, memory)\n- Logs from containers\n- Deploy new applications\n\n### Step 1: Install K3s Dashboard\n\n```bash\n# K3s doesn't include dashboard by default, install it:\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml\n\n# Wait for dashboard pod to start (takes ~30 seconds)\nkubectl wait --for=condition=ready pod \\\n  -l k8s-app=kubernetes-dashboard \\\n  -n kubernetes-dashboard \\\n  --timeout=300s\n```\n\n### Step 2: Create Admin User and Get Access Token\n\n```bash\n# Create admin user\nkubectl apply -f - \u003c\u003cEOF\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: admin-user\n  namespace: kubernetes-dashboard\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: admin-user\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- kind: ServiceAccount\n  name: admin-user\n  namespace: kubernetes-dashboard\nEOF\n\n# Get access token\nkubectl -n kubernetes-dashboard create token admin-user\n# Copy this token - you'll need it to log in\n```\n\n### Step 3: Create Ingress for Dashboard\n\n```bash\nnano dashboard-ingress.yaml\n```\n\nAdd:\n\n```yaml\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: kubernetes-dashboard\n  namespace: kubernetes-dashboard\n  annotations:\n    traefik.ingress.kubernetes.io/router.entrypoints: web\nspec:\n  rules:\n    - host: dashboard.yourdomain.com\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: kubernetes-dashboard\n                port:\n                  number: 443\n```\n\n```bash\nkubectl apply -f dashboard-ingress.yaml\n```\n\n### Step 4: Access Dashboard\n\n1. Update your Cloudflare tunnel config to include:\n```yaml\n  - hostname: dashboard.yourdomain.com\n    service: http://localhost:80\n```\n\n2. Restart Cloudflare tunnel:\n```bash\nsudo systemctl restart cloudflared\n```\n\n3. Open: `https://dashboard.yourdomain.com` in browser\n4. Paste the token from Step 2\n\n---\n\n## Sample Applications\n\n### Application 1: Nginx Welcome Page\n\n#### What It Does\nA simple Nginx container serving the default welcome page. Great for testing that basic pod deployment works.\n\n#### Deployment\n\n```bash\nnano nginx-deployment.yaml\n```\n\nAdd:\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx-welcome\n  labels:\n    app: nginx\nspec:\n  replicas: 1  # 1 pod instance\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n      - name: nginx\n        image: nginx:latest  # ARM64 compatible\n        ports:\n        - containerPort: 80\n        # Optional: Add resource limits (important on Pi!)\n        resources:\n          requests:\n            memory: \"64Mi\"\n            cpu: \"100m\"\n          limits:\n            memory: \"128Mi\"\n            cpu: \"250m\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-welcome\nspec:\n  selector:\n    app: nginx\n  ports:\n  - protocol: TCP\n    port: 80\n    targetPort: 80\n  type: ClusterIP\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: nginx-welcome\n  annotations:\n    traefik.ingress.kubernetes.io/router.entrypoints: web\nspec:\n  rules:\n  - host: nginx.yourdomain.com\n    http:\n      paths:\n      - path: /\n        pathType: Prefix\n        backend:\n          service:\n            name: nginx-welcome\n            port:\n              number: 80\n```\n\nDeploy it:\n\n```bash\nkubectl apply -f nginx-deployment.yaml\n\n# Check deployment status\nkubectl get deployment nginx-welcome\nkubectl get pods -l app=nginx\n\n# View logs\nkubectl logs -l app=nginx\n```\n\nVisit: `https://nginx.yourdomain.com` (through your Cloudflare tunnel)\n\n---\n\n### Application 2: .NET Hello World\n\n#### Build .NET Image\n\nFirst, create a simple .NET application and Dockerfile:\n\n```bash\n# Create project directory\nmkdir -p ~/dotnet-hello-world\ncd ~/dotnet-hello-world\n\n# Create a simple Dockerfile for ARM64\nnano Dockerfile\n```\n\nAdd:\n\n```dockerfile\nFROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS builder\nWORKDIR /app\n\n# Create minimal hello world app\nRUN dotnet new web -n HelloWorld --force\n\nWORKDIR /app/HelloWorld\nRUN dotnet publish -c Release -o /app/publish\n\n# Runtime stage\nFROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine\nWORKDIR /app\n\nCOPY --from=builder /app/publish .\n\nEXPOSE 80\nENTRYPOINT [\"dotnet\", \"HelloWorld.dll\"]\n```\n\n```bash\n# Build for ARM64 (this may take 5-10 minutes)\ndocker build -t hello-world-dotnet:latest .\n\n# Note: `docker` here refers to `crictl` in k3s\n# To use with k3s, you need to import to k3s:\nsudo k3s ctr images import \u003c(docker save hello-world-dotnet:latest)\n```\n\n#### Alternative: Use Pre-built Image\n\nIf building takes too long, use a minimal pre-built image:\n\n```bash\n# Create simpler Kubernetes manifest using a pre-built image\nnano dotnet-deployment.yaml\n```\n\nAdd:\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: dotnet-hello\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: dotnet-hello\n  template:\n    metadata:\n      labels:\n        app: dotnet-hello\n    spec:\n      containers:\n      - name: dotnet-app\n        image: mcr.microsoft.com/dotnet/samples:aspnetapp-nanoserver-ltsc2022\n        # Note: For Raspberry Pi, use Alpine images (lighter than nanoserver)\n        # image: mcr.microsoft.com/dotnet/samples:aspnetapp-alpine\n        ports:\n        - containerPort: 80\n        env:\n        - name: ASPNETCORE_URLS\n          value: \"http://+:80\"\n        resources:\n          requests:\n            memory: \"128Mi\"\n            cpu: \"100m\"\n          limits:\n            memory: \"256Mi\"\n            cpu: \"500m\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: dotnet-hello\nspec:\n  selector:\n    app: dotnet-hello\n  ports:\n  - protocol: TCP\n    port: 80\n    targetPort: 80\n  type: ClusterIP\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: dotnet-hello\n  annotations:\n    traefik.ingress.kubernetes.io/router.entrypoints: web\nspec:\n  rules:\n  - host: dotnet.yourdomain.com\n    http:\n      paths:\n      - path: /\n        pathType: Prefix\n        backend:\n          service:\n            name: dotnet-hello\n            port:\n              number: 80\n```\n\nDeploy:\n\n```bash\nkubectl apply -f dotnet-deployment.yaml\n\nkubectl get deployment dotnet-hello\nkubectl get pods -l app=dotnet-hello\n```\n\nVisit: `https://dotnet.yourdomain.com`\n\n---\n\n### Application 3: PostgreSQL Database (Optional)\n\nFor testing persistent storage with a real database:\n\n```bash\nnano postgres-deployment.yaml\n```\n\nAdd:\n\n```yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: postgres-config\ndata:\n  POSTGRES_DB: testdb\n  POSTGRES_USER: postgres\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: postgres-secret\ntype: Opaque\nstringData:\n  POSTGRES_PASSWORD: \"supersecure123\"  # Change this!\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: postgres\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: postgres\n  template:\n    metadata:\n      labels:\n        app: postgres\n    spec:\n      containers:\n      - name: postgres\n        image: postgres:15-alpine  # Lightweight Alpine version\n        ports:\n        - containerPort: 5432\n        envFrom:\n        - configMapRef:\n            name: postgres-config\n        - secretRef:\n            name: postgres-secret\n        volumeMounts:\n        - name: postgres-storage\n          mountPath: /var/lib/postgresql/data\n          subPath: postgres\n        resources:\n          requests:\n            memory: \"256Mi\"\n            cpu: \"250m\"\n          limits:\n            memory: \"512Mi\"\n            cpu: \"500m\"\n      volumes:\n      - name: postgres-storage\n        persistentVolumeClaim:\n          claimName: database-pvc\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: postgres\nspec:\n  selector:\n    app: postgres\n  ports:\n  - protocol: TCP\n    port: 5432\n    targetPort: 5432\n  type: ClusterIP\n```\n\nDeploy:\n\n```bash\nkubectl apply -f postgres-deployment.yaml\n\n# Wait for postgres pod\nkubectl wait --for=condition=ready pod -l app=postgres --timeout=300s\n\n# Access postgres from inside the cluster:\nkubectl run -it --rm postgres-client --image=postgres:15-alpine \\\n  --restart=Never -- psql -h postgres -U postgres -d testdb\n```\n\n---\n\n## Verification \u0026 Testing\n\n### Step 1: Verify All Pods Are Running\n\n```bash\n# List all pods\nkubectl get pods -A\n\n# Expected output shows all pods in Running state:\n# NAMESPACE              NAME                                    READY   STATUS\n# kube-system            coredns-57...                           1/1     Running\n# kube-system            local-path-provisioner-...               1/1     Running\n# kube-system            traefik-...                              1/1     Running\n# default                nginx-welcome-...                        1/1     Running\n# default                dotnet-hello-...                         1/1     Running\n# kubernetes-dashboard   kubernetes-dashboard-...                 1/1     Running\n```\n\n### Step 2: Check Ingress Routes\n\n```bash\n# List all ingress rules\nkubectl get ingress -A\n\n# Should show:\n# NAMESPACE   NAME                   CLASS    HOSTS                    ADDRESS\n# default     nginx-welcome          \u003cnone\u003e   nginx.yourdomain.com     192.168.x.x\n# default     dotnet-hello           \u003cnone\u003e   dotnet.yourdomain.com    192.168.x.x\n```\n\n### Step 3: Test DNS Resolution\n\n```bash\n# From your Pi, test if Cloudflare tunnel is routing correctly\ncurl -H \"Host: nginx.yourdomain.com\" http://localhost/\n\n# Expected: HTML content from Nginx welcome page (or your dashboard)\n```\n\n### Step 4: Monitor Cloudflare Tunnel\n\n```bash\n# Check tunnel status\ncloudflared tunnel list\n\n# View recent connections\ncloudflared tunnel info my-pi\n```\n\n### Step 5: Monitor Resource Usage\n\n```bash\n# Check node resource usage\nkubectl top nodes\n\n# Check pod resource usage\nkubectl top pods -A\n\n# Example output:\n# NAME                    CPU(cores)   MEMORY(Mi)\n# nginx-welcome-...       5m           32Mi\n# dotnet-hello-...        10m          64Mi\n# postgres-...            20m          128Mi\n```\n\nThis shows how much CPU and memory each pod is using.\n\n### Step 6: View Pod Logs\n\n```bash\n# View logs from specific pod\nkubectl logs -l app=nginx\n\n# Follow logs in real-time\nkubectl logs -l app=nginx -f\n\n# View logs from .NET app\nkubectl logs -l app=dotnet-hello\n```\n\n---\n\n## Troubleshooting\n\n### Issue 1: K3s Pod Won't Start (\"ImagePullBackOff\")\n\n**Symptom:** `kubectl get pods` shows pod stuck in \"ImagePullBackOff\"\n\n**Cause:** Image doesn't exist for ARM64 architecture\n\n**Solution:**\n```bash\n# Check what architecture the image supports\ndocker inspect image-name | grep -i \"Architecture\"\n\n# Use only ARM64 compatible images. Safe choices:\n# - alpine (all versions)\n# - debian (all versions)\n# - ubuntu:22.04\n# - nginx:latest\n# - postgres:15-alpine\n\n# Do NOT use:\n# - Images tagged \"amd64\" (Intel only)\n# - Images tagged \"windows\" or \"nanoserver\"\n```\n\n### Issue 2: Pod Running But App Not Responding\n\n**Symptom:** Pod is \"Running\" but website shows 503 or timeout\n\n**Cause:** Service networking issue or app crashed inside container\n\n**Solution:**\n```bash\n# 1. Check if pod is actually running or crashed\nkubectl describe pod \u003cpod-name\u003e\n\n# 2. Check logs for errors\nkubectl logs \u003cpod-name\u003e\n\n# 3. Test connectivity inside cluster\nkubectl exec -it \u003cpod-name\u003e -- /bin/sh\n# Then inside container: curl localhost:80\n\n# 4. Check service\nkubectl get svc \u003cservice-name\u003e\n# Verify CLUSTER-IP is assigned and not \u003cpending\u003e\n```\n\n### Issue 3: Persistent Storage Not Working\n\n**Symptom:** Database loses data after pod restart\n\n**Cause:** PVC not bound or wrong path\n\n**Solution:**\n```bash\n# Check PVC status\nkubectl get pvc\n\n# Should show:\n# NAME           STATUS   VOLUME   ...\n# database-pvc   Bound    pv-1     ...\n\n# If status is \"Pending\":\nkubectl describe pvc database-pvc\n\n# Check PV exists\nkubectl get pv\n```\n\n### Issue 4: Cloudflare Tunnel Not Routing Traffic\n\n**Symptom:** Website shows \"Error 522\" or \"Origin unreachable\"\n\n**Cause:** Tunnel not connected or config incorrect\n\n**Solution:**\n```bash\n# Check tunnel is connected\nsudo systemctl status cloudflared\n\n# Check tunnel logs\nsudo journalctl -u cloudflared -f\n\n# Verify config has correct hostnames and ports\ncat ~/.cloudflared/config.yml\n\n# Test locally without tunnel:\ncurl -H \"Host: nginx.yourdomain.com\" http://localhost/\n```\n\n### Issue 5: High Memory Usage / Pod Eviction\n\n**Symptom:** Pods getting killed or \"MemoryLimitExceeded\"\n\n**Cause:** Pods using more memory than limit\n\n**Solution:**\n```bash\n# Check current resource usage\nkubectl top pods -A\n\n# Increase limits in YAML:\n# resources:\n#   limits:\n#     memory: \"256Mi\"   # Increase from original\n\n# Or reduce replicas:\nkubectl scale deployment nginx-welcome --replicas=1\n```\n\n---\n\n## Next Steps After Testing\n\n1. **Add More Apps:** Create Deployments for your Node.js apps using similar patterns\n2. **Enable HTTPS:** Add SSL certificate with cert-manager\n3. **Backups:** Set up backup strategy for persistent storage\n4. **Monitoring:** Install Prometheus and Grafana for metrics\n5. **CI/CD:** Set up automated deployments with GitHub Actions\n\n---\n\n## Quick Reference Commands\n\n```bash\n# Pod management\nkubectl get pods                           # List pods\nkubectl describe pod \u003cname\u003e                # Detailed info\nkubectl logs \u003cpod\u003e                         # View logs\nkubectl exec -it \u003cpod\u003e -- /bin/sh          # Shell access\nkubectl delete pod \u003cname\u003e                  # Remove pod\n\n# Deployment management\nkubectl get deployments                    # List deployments\nkubectl scale deployment \u003cname\u003e --replicas=3  # Scale up/down\nkubectl set image deployment/\u003cname\u003e app=image:tag  # Update image\n\n# Service and Ingress\nkubectl get svc                            # List services\nkubectl get ingress                        # List ingress rules\n\n# Resource monitoring\nkubectl top nodes                          # Node CPU/memory\nkubectl top pods                           # Pod CPU/memory\n\n# Debugging\nkubectl get events                         # Recent cluster events\nkubectl describe node                      # Node details\n```\n\n---\n\n## Important Security Notes\n\n⚠️ **For Production:**\n- Change all default passwords (postgres, dashboard token)\n- Enable RBAC properly (current setup is admin-only)\n- Use sealed secrets for credentials (not plain ConfigMap)\n- Enable network policies to restrict traffic between pods\n- Regularly update k3s: `sudo systemctl stop k3s \u0026\u0026 sudo systemctl start k3s`\n- Back up your persistent data regularly\n\n---\n\n**Setup Complete!** Your k3s cluster on Raspberry Pi 5 is now ready for testing. Start with the Nginx deployment, verify it works through Cloudflare, then proceed to .NET and database applications.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaiganio%2Fk3s-pi5","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbaiganio%2Fk3s-pi5","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaiganio%2Fk3s-pi5/lists"}