On this page
How to Deploy an App to a Cheap VPS With k3s
Install k3s on a single VPS, deploy a containerized app with a Deployment and Service, expose it with an Ingress, and add automatic HTTPS with cert-manager.
Quick answer
- You need a VPS, a domain pointed at it, and SSH access; the rest is one install command and a few manifests.
- k3s installs in a single command and bundles Traefik for ingress, so you do not install an ingress controller separately.
- Deploy with a Deployment for the app and a Service to expose it; kubectl apply handles both.
- Add an Ingress plus cert-manager to serve HTTPS with Let’s Encrypt certificates that renew automatically.
- Verify success with kubectl get pods and by loading your domain over HTTPS, not just by a clean apply.
Before you start
You need a server running a current Linux distribution with at least 2 vCPU and 2 GB of RAM, a domain whose A record points at the server’s IP, and a container image pushed to a registry the server can pull from. Confirm SSH access works and that ports 80 and 443 are open. Note that DNS must resolve before you request a certificate; propagation can take a few minutes.
Step 1 — Install k3s
Run the official install script over SSH. It installs the k3s binary, starts the service, and writes a kubeconfig you can use locally. Copy the kubeconfig to your workstation and point kubectl at it so you can work from your machine.
# On the server
curl -sfL https://get.k3s.io | sh -
sudo cat /etc/rancher/k3s/k3s.yaml # copy this to your workstation
# On your workstation
# save the file as ~/.kube/k3s.yaml, replace 127.0.0.1 with the server IP
export KUBECONFIG=~/.kube/k3s.yaml
kubectl get nodes # expect one node, status ReadyStep 2 — Write the Deployment and Service
Create a manifest that runs the app and exposes it inside the cluster. Pin the image tag, set resource requests and limits, and point the readiness probe at a real health endpoint.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 1
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: app
image: ghcr.io/acme/app:1.0.0
ports:
- containerPort: 3000
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
readinessProbe:
httpGet:
path: /healthz
port: 3000
---
apiVersion: v1
kind: Service
metadata:
name: app
spec:
selector:
app: app
ports:
- port: 80
targetPort: 3000Step 3 — Apply and verify
Apply the manifest and confirm the pod reaches Running and Ready. A pod that is Running but not Ready usually has a failing readiness probe; a pod stuck in ImagePullBackOff has a wrong image name or a private registry credential you have not added.
kubectl apply -f app.yaml
kubectl get pods # expect READY 1/1, STATUS Running
kubectl describe pod -l app=app # read events if it is not ReadyStep 4 — Add an Ingress and HTTPS
k3s ships Traefik as its ingress controller, so you only need an Ingress object. Install cert-manager for certificates, create a ClusterIssuer for Let’s Encrypt, and annotate the Ingress to use it. Point the host at the domain whose DNS already resolves to the server.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
ingressClassName: traefik
tls:
- hosts: [app.example.com]
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app
port:
number: 80Step 5 — Confirm HTTPS and roll out an update
Load https://app.example.com and confirm the browser shows a valid certificate, then deploy a change by updating the image tag and re-applying. The rollout replaces the pod without dropping traffic because the readiness probe gates it. Verify with kubectl rollout status deployment/app; if a rollout stalls, kubectl rollout undo returns to the previous version.
Where this bites vibecoders
An AI assistant can produce every manifest on this page in one response, and the result will often apply cleanly while silently failing in four predictable ways: an image tag the server cannot pull, a targetPort the app does not listen on, a readiness probe path the app does not serve, or an Ingress host that does not match DNS. The tutorial above is not really about typing the YAML — it is about the verify step after each stage, because the difference between a working deployment and a plausible-looking one is almost always in the events, not the manifest.
Where AI coding assistants get this wrong
- Using a latest or private image tag the node cannot pull.
- Setting targetPort to the Service port instead of the container port.
- Pointing the readiness probe at a path the app does not expose.
- Requesting a certificate before DNS resolves to the server.
- Skipping resource limits and letting one service consume the whole node.
- Enabling k3s without securing the API server or the kubeconfig.
Checklist
- Pin a concrete image tag in the registry.
- Copy the kubeconfig to your workstation and verify kubectl get nodes.
- Check the pod is Ready, not just Running.
- Match the Ingress host to DNS before requesting a certificate.
- Confirm HTTPS loads with a valid certificate.
- Roll out a change and verify zero dropped requests.
- Back up persistent data and the k3s datastore off the node.
FAQ
Do I need to install an ingress controller on k3s?
No. k3s bundles Traefik as its default ingress controller, so an Ingress object works immediately. If you prefer nginx or another controller, you can disable Traefik during install and install your own.
How do I run this without downtime on one node?
Use a readiness probe and a rolling update strategy so the new pod becomes Ready before the old one is removed. With one node there is still a moment of reduced capacity during the rollout, and if the node itself reboots the app is down until it returns.
What is the minimum server size for this?
2 vCPU and 2 GB of RAM is a reasonable floor for k3s plus one small service. Below that, the control plane competes with your app for memory. If your app is memory-hungry, size the node around it, not around the cluster.
Related topics
- What Is k3s (and When Should You Use It)?
- k3s for Vibecoders: Kubernetes on One Cheap Server, Without the Cloud Bill
- What Is a Kubernetes Ingress (and Why Is My App Unreachable)?
- How to Deploy Your First App to Kubernetes
- Kubernetes YAML Explained: Deployments, Services, and What Your AI Writes
- How to Set Up Automated Database Backups