k3s Kubernetes installation on a single VPS node

Full Kubernetes is overkill for a single VPS. k3s is a CNCF-certified lightweight Kubernetes distribution that installs in 30 seconds, uses ~512 MB RAM, but remains 100% compatible with kubectl and Helm. It's ideal for developers learning Kubernetes or running production workloads on a budget VPS. In this comprehensive guide, we'll walk you through installation, configuration, deployment, and best practices for k3s on AsiaGB VPS.

Why Choose k3s Over Docker Compose or Full Kubernetes?

Docker Compose is great for running a few containers on a single host, but as your application grows in complexity, you'll need container orchestration features that Docker Compose simply cannot provide. Here's why k3s is the sweet spot between simplicity and capability:

Compared to full Kubernetes, k3s reduces overhead: no need to manage etcd separately, no kube-controller-manager as a separate service. k3s bundles everything into a single binary, making it perfect for VPS with limited resources. Yet you get the same kubectl and Helm ecosystem, so skills transfer directly to enterprise Kubernetes environments.

System Requirements and Prerequisites

AsiaGB VPS plans (4 GB RAM / 2 CPU) are ideal starting points for k3s production clusters. They provide enough headroom for control plane + container workloads without CPU throttling.

Step 1 — Installing k3s on Your VPS

The k3s installation is refreshingly simple thanks to the official installer script. SSH into your VPS and run the following command:

# Download and run k3s installer
curl -sfL https://get.k3s.io | sh -

# Wait 1-2 minutes for the control plane to initialize
sudo systemctl status k3s

# Verify the cluster is ready
sudo k3s kubectl get nodes

The installer automatically:

When the node status shows Ready, your k3s cluster is online. If you see NotReady, wait a bit longer and check system logs with `journalctl -u k3s -f`.

Step 2 — Configuring kubectl on Your Local Machine

To manage your cluster from your laptop or desktop, you need to copy the kubeconfig file from the VPS and configure local kubectl access:

# On the VPS, print the kubeconfig
sudo cat /etc/rancher/k3s/k3s.yaml

Copy the entire output. Then on your local machine:

# Create .kube directory if it doesn't exist
mkdir -p ~/.kube

# Create a new kubeconfig file (don't overwrite existing ones)
nano ~/.kube/config-k3s

# Paste the kubeconfig content
# IMPORTANT: Replace 127.0.0.1 with your VPS's actual IP address or domain
# Change: server: https://127.0.0.1:6443
# To:     server: https://YOUR_VPS_IP:6443

# Set environment variable to use this kubeconfig
export KUBECONFIG=~/.kube/config-k3s

# Test the connection
kubectl get nodes
kubectl get pods -A  # View all pods across all namespaces

You should see your k3s node in the output. If you get connection refused errors, double-check the IP address and that port 6443 is open in your VPS firewall.

Step 3 — Deploying Your First Application

Now that your cluster is running, let's deploy a simple NGINX web server with proper resource management, health checks, and service exposure:

Create a Deployment with Health Checks

# Save this as nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5

Deploy and monitor:

kubectl apply -f nginx-deployment.yaml
kubectl get pods -w  # Watch pods come online
kubectl describe pod nginx-XXX  # View pod details
kubectl logs -f nginx-XXX  # Stream container logs

Expose the Deployment with a Service

# Save as nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP  # Internal service (no external IP)
kubectl apply -f nginx-service.yaml
kubectl get svc  # List services

Create an Ingress with Traefik (Built-in to k3s)

k3s comes with Traefik pre-installed as the default ingress controller. It automatically handles HTTP/HTTPS routing, TLS certificates, and load balancing:

# Save as nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: traefik
  rules:
  - host: nginx.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80
  tls:
  - hosts:
    - nginx.example.com
    secretName: nginx-tls

Apply it with: `kubectl apply -f nginx-ingress.yaml`. Traefik will automatically provision a Let's Encrypt certificate if you have cert-manager installed (see Step 4).

Step 4 — Installing Helm for Package Management

Helm is the package manager for Kubernetes, similar to apt for Ubuntu. It allows you to install pre-configured applications with a single command:

# Download and install Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Verify installation
helm version

Installing Applications with Helm

Let's install cert-manager, which automatically manages TLS certificates for your ingress controllers:

# Add the Jetstack Helm repository (maintainers of cert-manager)
helm repo add jetstack https://charts.jetstack.io
helm repo update

# Install cert-manager with CRD support
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true \
  --set global.leaderElection.namespace=cert-manager

# Verify the installation
kubectl get pods -n cert-manager
kubectl get crds | grep cert-manager

With cert-manager running, your Ingress resources can automatically obtain and renew TLS certificates from Let's Encrypt, keeping your applications secure without manual effort.

Step 5 — ConfigMaps and Secrets for Application Configuration

Never hardcode configuration values or passwords into your container images. Instead, use ConfigMaps for non-sensitive config and Secrets for sensitive data:

Create ConfigMap for Application Settings

# Create a ConfigMap from command line
kubectl create configmap app-config \
  --from-literal=DB_HOST=postgres.default.svc.cluster.local \
  --from-literal=DB_PORT=5432 \
  --from-literal=CACHE_ENABLED=true \
  --from-literal=LOG_LEVEL=info

# Or define it in YAML (ConfigMap.yaml)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  DB_HOST: postgres.default.svc.cluster.local
  DB_PORT: "5432"
  CACHE_ENABLED: "true"
  LOG_LEVEL: info
  nginx.conf: |
    server {
      listen 80;
      location / { return 200 "OK"; }
    }

Create Secrets for Sensitive Data

# Create a Secret from command line (base64 encoded automatically)
kubectl create secret generic app-secret \
  --from-literal=DB_PASSWORD=supersecretpass123 \
  --from-literal=API_KEY=sk_live_abc123def456 \
  --from-literal=JWT_SECRET=your-jwt-signing-key

# Verify (values are base64 encoded for security)
kubectl get secret app-secret -o yaml

Use ConfigMap and Secret in a Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        env:
        # ConfigMap values
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DB_HOST
        - name: DB_PORT
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DB_PORT
        # Secret values
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secret
              key: DB_PASSWORD
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: app-secret
              key: API_KEY
        # ConfigMap as mounted file (nginx.conf)
        volumeMounts:
        - name: config-volume
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
      volumes:
      - name: config-volume
        configMap:
          name: app-config

Step 6 — Persistent Volumes for Stateful Applications

Containers are ephemeral by default — data is lost when a pod is deleted. For databases, file storage, or any stateful data, use Persistent Volumes and Persistent Volume Claims:

Create a PVC for PostgreSQL

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: local-path  # k3s built-in storage class
  resources:
    requests:
      storage: 20Gi

Deploy PostgreSQL with Persistent Storage

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15-alpine
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
      volumes:
      - name: postgres-storage
        persistentVolumeClaim:
          claimName: postgres-pvc

Data in `/var/lib/postgresql/data` is now persisted to disk. Even if the pod is recreated, the data remains intact.

Essential kubectl Commands for Daily Operations

Master these commands to effectively manage your k3s cluster:

# Cluster and node information
kubectl cluster-info
kubectl get nodes -o wide
kubectl describe node k3s-node-1

# View resources across all namespaces
kubectl get all -A
kubectl get pods -A

# Inspect specific resources
kubectl get deployment myapp
kubectl describe deployment myapp
kubectl get svc -A
kubectl get pvc

# Container logs and debugging
kubectl logs deployment/nginx          # Latest logs from deployment
kubectl logs pod-name -f --tail=50    # Stream 50 latest lines
kubectl logs pod-name --previous      # Logs from previous crashed container
kubectl exec -it pod-name -- /bin/sh  # Interactive shell in container

# Scaling and updates
kubectl scale deployment nginx --replicas=5
kubectl set image deployment/nginx nginx=nginx:latest
kubectl rollout history deployment/nginx
kubectl rollout undo deployment/nginx   # Rollback to previous version

# Port forwarding for debugging
kubectl port-forward service/nginx 8080:80   # Access pod via localhost:8080

# Events and troubleshooting
kubectl get events --sort-by=.metadata.creationTimestamp
kubectl get events -A
kubectl describe pod stuck-pod-name

Enable Resource Metrics Monitoring

By default, k3s doesn't come with metrics-server. Install it to enable resource monitoring and horizontal pod autoscaling:

# Install metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# Wait for metrics-server to be ready (30-60 seconds)
kubectl get pods -n kube-system -w | grep metrics-server

# Once ready, you can monitor resource usage
kubectl top nodes
kubectl top pods -A

Networking Deep Dive: Services and Ingress

Understanding networking in k3s is crucial for exposing your applications correctly:

Service Types

For web applications on a VPS, use Ingress with Traefik (comes pre-installed) or NodePort for simple cases.

Storage Classes Beyond Local Path

The built-in local-path storage class works well for single-node clusters. For advanced scenarios, consider:

For most single-VPS setups, local-path is sufficient. If you scale to 3+ nodes, add Longhorn for automatic backups and replication.

Troubleshooting Common Issues

Pod Stuck in Pending State

kubectl describe pod stuck-pod
# Check Events section for reasons:
# - Insufficient CPU/memory
# - PVC not bound
# - Image pull errors

ImagePullBackOff Error

# The pod is trying to pull an image that doesn't exist or is private
# Fix: Use correct image name from Docker Hub
kubectl set image deployment/myapp myapp=correct-image:tag

CrashLoopBackOff

# Container is crashing on startup
# Check logs:
kubectl logs pod-name
# Common causes: wrong environment variables, config file missing, port conflicts

No External Access to Service

# Verify service exists and has endpoints
kubectl get svc -o wide
# Check if selector matches pod labels
kubectl get pods --show-labels
# If using Ingress, check ingress status
kubectl describe ingress myapp

Performance Optimization Tips

k3s vs Docker Compose — Detailed Comparison

Feature k3s Docker Compose
Auto-restart pods✅ Via Liveness Proberestart: unless-stopped
Zero-downtime deployment✅ Rolling Update❌ Brief downtime
Resource limits (CPU/mem)✅ Native supportLimited
Learning curveHigh (Kubernetes concepts)✅ Very easy
RAM overhead~300-500 MB✅ ~50 MB
Helm chart support✅ Full compatibility❌ None
Industry adoptionGrowing (startups, edge)Widespread (dev/small apps)
Migration to K8s✅ Skills transfer directlyNeed to rewrite YAML

Security Best Practices for k3s

Pro Tip — Access k3s from Multiple Machines: Copy `/etc/rancher/k3s/k3s.yaml` from your VPS to multiple local machines (replace 127.0.0.1 with VPS IP on each). This lets your entire team manage the cluster, or use CI/CD pipelines to deploy applications automatically.

Ready to Run k3s on a VPS?

AsiaGB VPS with 4–8 GB RAM provides the perfect foundation for k3s clusters. Full root access, SSD storage, and 99% uptime SLA ensure your Kubernetes workloads stay online. Starting at 500 THB/month.

View VPS Plans

View all affordable VPS Thailand plans →