Self-Hosted Services and ProductivityDocs and File WorkflowsIntermediate60-90 minutesLab

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.

Last reviewed8/19/2026
self-hosted serviceslightweight Kubernetesingress and persistent storage
K3sKubernetesTraefiklocal-path-provisionerNginx

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

  1. 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 /

  2. 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
  3. 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
  4. 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
  5. 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
  6. 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.

References

Keep Moving

Build on what you just completed

Continue with a related Lab or return to this build path for a different implementation.