Create a Lightweight k3s Lab for Self-Hosted Services
Build a small k3s lab using the bundled Traefik ingress controller and local-path storage, then validate a complete service path before adding real self-hosted workloads.
Expected Outcome
A single-node k3s lab with a validated Nginx workload, Service, Ingress, and persistent volume claim using the components K3s already packages.
Assumptions
A supported 64-bit Linux host or VM with at least 2 GB of RAM and 2 CPU cores.
Console or SSH access with a user that can run `sudo`.
Working DNS and internet access to retrieve the K3s installer and container images.
A maintenance window in which installing K3s and changing local firewall or networking state is acceptable.
Bill of Materials
One Linux host or VM dedicated to the Lab.
A second workstation or terminal for testing DNS/HTTP access to the node.
A place to record the installer hash, node state, workload state, and cleanup results.
Build Steps
- Capture the host baseline
Confirm the operating system, architecture, IP addressing, and available resources before installing anything. K3s already includes a container runtime and the core components needed for this single-node Lab, so a separate Docker installation is not required.
Read-only command: verify target and scope
cat /etc/os-release
Manual or UI step
uname -m
ip -brief address
free -h
df -h /
- Download and review the K3s installer before privileged execution
Download the official installer to a file instead of piping remote content directly into a root shell. Record its SHA-256 value as evidence of exactly what you reviewed. Inspect the script before running it, then install from the stable channel. For a controlled environment that must reproduce an exact K3s release, set INSTALL_K3S_VERSION to a reviewed version rather than following the moving stable channel.
Changes system state: review before running
curl -sfL https://get.k3s.io -o /tmp/k3s-install.sh
Manual or UI step
sha256sum /tmp/k3s-install.sh
less /tmp/k3s-install.sh
Review before running: verify target, scope, and execution context
sudo env INSTALL_K3S_CHANNEL=stable sh /tmp/k3s-install.sh
- Validate the packaged cluster components
K3s packages CoreDNS, Traefik, ServiceLB, and the local-path storage provisioner. Confirm the node becomes Ready and those components converge instead of installing a second ingress controller or a separate storage provisioner.
Read-only command: verify target and scope
sudo k3s kubectl get nodes -o wide sudo k3s kubectl get pods -A sudo k3s kubectl get storageclass sudo k3s kubectl -n kube-system get deployment,daemonset,service
- Create persistent storage and a test workload
Use the packaged local-path storage class for a small persistent volume claim. Local-path storage is node-local and is appropriate for this single-node Lab; it is not a highly available storage design.
Changes system state: review before running
sudo k3s kubectl apply -f - <<'EOF' apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ops-stack-web-data spec: accessModes: - ReadWriteOnce storageClassName: local-path resources: requests: storage: 1Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: ops-stack-web spec: replicas: 1 selector: matchLabels: app: ops-stack-web template: metadata: labels: app: ops-stack-web spec: containers: - name: web image: nginx:stable-alpine ports: - containerPort: 80 volumeMounts: - name: web-data mountPath: /usr/share/nginx/html volumes: - name: web-data persistentVolumeClaim: claimName: ops-stack-web-data --- apiVersion: v1 kind: Service metadata: name: ops-stack-web spec: selector: app: ops-stack-web ports: - port: 80 targetPort: 80 EOF - Add an Ingress that uses the packaged Traefik controller
Create an Ingress for the Service. This Lab relies on K3s's packaged Traefik deployment; do not apply a separate historical Traefik manifest on top of it.
Changes system state: review before running
sudo k3s kubectl apply -f - <<'EOF' apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ops-stack-web spec: rules: - host: ops-stack-web.local http: paths: - path: / pathType: Prefix backend: service: name: ops-stack-web port: number: 80 EOF sudo k3s kubectl get ingress ops-stack-web -o wide - Validate the complete request path
Wait for the workload to become available. Validate the request through Traefik rather than treating a successful kubectl apply as proof of service health. For a remote test workstation, resolve ops-stack-web.local to the node IP temporarily before testing.
Read-only command: verify target and scope
sudo k3s kubectl rollout status deployment/ops-stack-web --timeout=120s sudo k3s kubectl get pod,service,ingress,pvc -o wide curl -fsS -H 'Host: ops-stack-web.local' http://127.0.0.1/ > /dev/null && echo 'Ingress validation passed'
Validation
Confirm the K3s node and packaged components are healthy before deploying real workloads.
Read-only command: verify target and scope
sudo k3s kubectl get nodes sudo k3s kubectl get pods -A
Confirm the application path includes a ready pod, Service endpoint, bound PVC, and Ingress.
Read-only command: verify target and scope
sudo k3s kubectl get pod,service,endpoints,ingress,pvc -o wide
Test HTTP through the ingress path.
Read-only command: verify target and scope
curl -fsS -H 'Host: ops-stack-web.local' http://127.0.0.1/ > /dev/null && echo 'HTTP validation passed'
Troubleshooting
If the node is not Ready or packaged components are failing, inspect K3s service and kube-system state before reinstalling or layering on replacement components.
Read-only command: verify target and scope
sudo systemctl status k3s --no-pager sudo journalctl -u k3s --since '-15 minutes' --no-pager sudo k3s kubectl get pods -n kube-system -o wide
If the application does not answer, separate pod readiness, Service endpoint selection, Ingress state, and DNS/hosts-file resolution.
Read-only command: verify target and scope
sudo k3s kubectl describe deployment ops-stack-web sudo k3s kubectl get endpoints ops-stack-web -o yaml sudo k3s kubectl describe ingress ops-stack-web
Cleanup or Rollback
Remove only the example Lab objects if you want to keep K3s for future exercises.
Warning: Deleting the PVC removes the claim and may delete the local-path backing data. Preserve anything you need before cleanup.
Destructive: review before running
sudo k3s kubectl delete ingress ops-stack-web sudo k3s kubectl delete service ops-stack-web sudo k3s kubectl delete deployment ops-stack-web sudo k3s kubectl delete pvc ops-stack-web-data
If the entire K3s Lab is disposable, use the uninstall script created by the official K3s installer after you have preserved any required data.
Read-only command: verify target and scope
sudo /usr/local/bin/k3s-uninstall.sh
Next Improvements
Pin reviewed image digests and an exact K3s release when you need reproducibility beyond a disposable Lab.
Add backup and restore testing before treating local-path data as durable.
Move to multi-node and external or replicated storage only when the availability requirement justifies the added complexity.
