Day 43 — Optional k3s

Updated

July 30, 2026

Day 43 — Optional k3s

Stage IV · ~4h (or ~30m skip path)
Goal: Either stand up a single-node k3s for literacy or skip cleanly with a written decision—Kubernetes is not required for Stage IV gate.

Important

If you do not need k8s for work or curiosity this month, skip. A forced k3s install can destabilize a small lab (CPU, disk, conceptual overload) right before the gate.

Why this day exists

Some learners hit Stage IV and ask: “Should everything move to Kubernetes?” On NixOS the honest answer is usually no—not for a one-node lab slice with Caddy + Postgres.

This day exists to:

  1. Prevent FOMO-driven architecture
  2. Give a safe optional on-ramp if you truly need k8s APIs
  3. Practice skipping as a professional skill

Theory 1 — Decision rubric

Choose skip if… Choose k3s lite if…
Stage IV slice already works natively You must learn kubectl for a job soon
Host is small on RAM/disk You have ≥8–16 GB RAM lab resources
You have not finished secrets/proxy/DB You already finished Days 33–42 solidly
Curiosity only, no time You will actually deploy one manifest

Default recommendation: skip, write the decision, move to Day 44.


Theory 2 — What k3s is

k3s is a lightweight Kubernetes distribution. On NixOS:

# modules/services/k3s.nix — only if you opt in
{
  services.k3s = {
    enable = true;
    role = "server";
    # extraFlags = [ "--write-kubeconfig-mode=0644" ]; # understand security implications
  };
}

Options and defaults change—read current module docs for 26.05 before enabling.

Implications:

Area Effect
Ports API server, kubelet, etc.—firewall carefully
CPU/RAM Non-trivial idle cost
Storage Container images + etcd/sqlite data
Mental model YAML/Helm vs NixOS modules

Theory 3 — NixOS vs k8s configuration

Concern NixOS native k3s
System packages flake modules images inside cluster
Host firewall networking.firewall + k8s Services/NodePorts
Secrets sops-nix on host also k8s Secrets (different!)
Rollback generations cluster state is separate

Running k3s does not replace host flake discipline. You now have two control planes: NixOS + cluster.


Theory 4 — Single-node honesty

Single-node k3s is fine for learning APIs. It is not:

  • Multi-AZ HA
  • A reason to abandon PostgreSQL module for “cloud native” on one VPS without backups
  • Automatically more secure

Theory 5 — Minimal literacy tasks (if enabled)

  1. kubectl get nodes
  2. Deploy a tiny Deployment + Service
  3. Expose via host proxy or port-forward—not random NodePort to the world
  4. Delete the workload
  5. Document how to disable services.k3s and reclaim resources

Theory 6 — Skip path is first-class

Skipping is documented output:

# docs/k3s-decision.md
Decision: SKIP
Date: …
Reasons:
- Stage IV gate does not require k8s
- Host resources: …
- Will revisit if: job requirement / multi-service mesh need

That file passes the day.


Worked example — Disable / enable toggles

Prefer a role flag:

# modules/roles/server.nix
{ config, lib, ... }:
{
  # imports = lib.optionals config.lab.enableK3s [ ../services/k3s.nix ];
}

Or simply do not import k3s module. Avoid half-enabled clusters.


Lab S — Skip path (most readers)

  1. Write docs/k3s-decision.md with SKIP + reasons
  2. Skim what k3s would open (ports, disk) from docs—no install
  3. List two future triggers to revisit
  4. Commit and proceed to Day 44

Time box: 30 minutes. Do not guilt-install.


Lab K1 — Opt-in: enable k3s

Only if rubric says go:

# add module; ensure plenty of disk
sudo nixos-rebuild switch --flake .#lab
sudo systemctl status k3s
sudo k3s kubectl get nodes

Kubeconfig location per module—follow module notes. Restrict access modes; do not world-chmod casually without understanding.


Lab K2 — Tiny workload

# manifests/hello.yaml — apply via kubectl
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello
spec:
  replicas: 1
  selector:
    matchLabels: { app: hello }
  template:
    metadata:
      labels: { app: hello }
    spec:
      containers:
        - name: hello
          image: docker.io/library/nginx:alpine
          ports:
            - containerPort: 80
kubectl apply -f manifests/hello.yaml
kubectl get pods
kubectl port-forward deploy/hello 8088:80
curl -sS http://127.0.0.1:8088/ | head

Lab K3 — Tear-down plan

Document:

kubectl delete -f manifests/hello.yaml
# disable services.k3s; rebuild; confirm resources freed

Update firewall if you opened API ports. Prefer API on localhost / VPN only.


Lab K4 — Compare control planes

Write a short note: what still belongs in the NixOS flake vs what you put in the cluster. If the answer is “everything in cluster,” push back—host still owns kernel, disks, proxy in many designs.



Theory 7 — When NixOS modules beat k8s on a single node

Workload Prefer
One reverse proxy + one DB NixOS modules (Days 38–39)
Many identical microservices with k8s API dependency k3s/k8s
GPU training cluster Often specialized schedulers
Homelab “because CNCF” Usually modules + compose/podman

NixOS is already a declarative control plane for one machine. k3s adds a second control plane—justify it.


Theory 8 — Resource floor honesty

Single-node k3s wants real RAM/CPU. On a 2 GB VM:

k3s + postgres + caddy + prometheus = thrash

Skip is the professional choice when resources are tight before Day 44 gate.


Worked example — Feature flag module

# modules/services/k3s.nix
{ config, lib, ... }:
let cfg = config.myLab.k3s; in {
  options.myLab.k3s.enable = lib.mkEnableOption "optional single-node k3s";
  config = lib.mkIf cfg.enable {
    services.k3s = {
      enable = true;
      role = "server";
      # extraFlags = [ "--disable traefik" ]; # if you keep Day 38 proxy as edge
    };
    # networking.firewall.allowedTCPPorts = [ 6443 ]; # admin API — lock down
  };
}

Default enable = false on the gate host.


Lab K5 — API surface inventory (if enabled)

sudo k3s kubectl get nodes
sudo k3s kubectl get pods -A | head
ss -tlnp | grep -E '6443|10250' || true

Add every k3s port to docs/ports.md with “cluster-internal / admin-only” labels.


Lab S2 — Skip artifact quality bar

Your skip note must include:

  1. Decision (skip)
  2. Resource or complexity reason
  3. What you use instead (NixOS modules / podman)
  4. Revisit criteria (“if job requires CKA lab…”)

A one-word “skipped” is not enough for the gate dossier.

Common gotchas

Symptom / mistake What to do
Guilt install before gate Use skip path
Opened k8s API to world Close; bind/firewall
Disk full of images Prune; disable k3s
Secrets only in k8s, host ignored Host sops still matters
Two proxies fighting One ingress story
Undocumented cluster Decision doc mandatory either way

Checkpoint

If skipped

  • docs/k3s-decision.md with SKIP + reasons
  • Future revisit triggers listed
  • No half-installed k3s units left

If enabled

  • Single-node ready; kubectl get nodes works
  • One workload applied and removed or still intentional
  • API not casually public
  • Disable/tear-down plan written
  • ports.md updated

Commit

git add .
git commit -m "day43: k3s optional — decision recorded"

Write three personal gotchas before continuing.


Tomorrow

Day 44 — Stage IV gate: prove a secret-safe lab slice—proxy + secret + data/app path + firewall story—rebuildable from flake + keys.