Skip to content

Getting Started

This guide walks you through provisioning a production-ready k3s cluster from two fresh VPS nodes.

Two ways to use k3s-lab

ModeWhen to use
Direct (this guide)Evaluate the toolkit, test locally, or you don't need a private config repo
Infra repoProduction use — private repo holds your .env, secrets, and custom apps; k3s-lab is fetched on-demand

For the infra repo pattern, see Using with a private infra repo once you've read this guide.


Prerequisites

Local machine

ToolPurposeInstall
kubectlKubernetes CLIbrew install kubectl
helmHelm package managerbrew install helm
ssh / scpVPS accessPre-installed on macOS/Linux
htpasswdBasicAuth secret generationbrew install httpd
envsubstVariable substitution in manifestsbrew install gettext
batsOptional: run tests locallybrew install bats-core

VPS nodes

RequirementValue
OSUbuntu 22.04+ or Debian 12+
Architecturex86_64 or ARM64
Server: vCPU / RAM2 vCPU / 2 GB minimum (4 GB recommended)
Agent: vCPU / RAM2 vCPU / 1 GB minimum (2 GB recommended)
Disk20 GB+ (server), 20 GB+ (agent)
Public IPRequired on each node
DNSManaged by Cloudflare (A records created automatically by external-dns)

DNS records

No manual DNS setup required. Once external-dns is deployed, it automatically creates and updates Cloudflare A records whenever you add an IngressRoute with the external-dns.alpha.kubernetes.io/hostname annotation.

TLS certificates use the DNS-01 challenge (Cloudflare API), so they can be issued even before traffic is routed — no chicken-and-egg problem with DNS propagation.


Step 1 — Configure environment

bash
cp .env.example .env

Edit .env and fill in at minimum:

bash
SERVER_IP=1.2.3.4
AGENT_IP=5.6.7.8
SSH_USER=ubuntu
SSH_KEY=~/.ssh/id_ed25519
K3S_VERSION=v1.32.2+k3s1
DOMAIN=example.com
EMAIL=you@example.com
DASHBOARD_DOMAIN=dashboard.example.com
DASHBOARD_PASSWORD=your-secure-password
GRAFANA_DOMAIN=grafana.example.com
GRAFANA_PASSWORD=your-secure-password
KUBECONFIG_CONTEXT=k3s-lab

See Configuration for the full variable reference.


Step 2 — Provision the cluster

bash
make provision

This runs Ansible to configure all nodes:

  1. Common setup — packages, kernel modules, sysctl, UFW, swap disabled
  2. k3s server — installs k3s with Flannel VXLAN, disables Traefik/servicelb
  3. WireGuard — optional VPN (if wireguard_enabled: true in group_vars)
  4. k3s agents — joins agent nodes to the cluster
  5. Kubeconfig — saved locally for kubectl access

Option B: Step by step

bash
make provision-server      # Common + k3s server + WireGuard
make provision-agents      # Join agent nodes
make kubeconfig            # Merge kubeconfig locally

Requires Ansible inventory at ansible/inventory/hosts.yml — see Configuration.


Step 3 — Verify nodes

bash
kubectl config use-context k3s-lab
make nodes
# NAME     STATUS   ROLES                  AGE   VERSION
# server   Ready    control-plane,master   5m    v1.32.2+k3s1

Step 4 — Deploy base stack

Create the dashboard secret

bash
make deploy-dashboard-secret

Deploy Traefik + cert-manager

bash
make deploy

This deploys in order:

  1. Namespacesingress, cert-manager, monitoring, apps
  2. Traefik — Helm chart with values from charts/platform-base/values.yaml
  3. cert-manager — with CRDs installed
  4. ClusterIssuers — Let's Encrypt staging + production
  5. Traefik dashboard — secured IngressRoute at DASHBOARD_DOMAIN

⏱️ Takes ~3 minutes. TLS certificate issuance happens in the background and takes ~30s after DNS resolves.


Step 5 — Deploy monitoring

Create Grafana admin secret

bash
make deploy-grafana-secret

Deploy observability stack

bash
make deploy-monitoring

This deploys:

  1. Grafana — visualization dashboards
  2. Loki — centralized log storage
  3. Promtail — log collector DaemonSet
  4. Grafana IngressRoute — HTTPS at GRAFANA_DOMAIN

⏱️ Takes ~10 minutes (large chart images).


Step 6 — Verify

bash
make status

All pods should be Running or Completed.

Access points:

ServiceURLCredentials
Traefik dashboardhttps://DASHBOARD_DOMAIN/dashboard/admin / DASHBOARD_PASSWORD
Grafanahttps://GRAFANA_DOMAINadmin / GRAFANA_PASSWORD

Deploy an example app

Create your app manifests in apps/ (for infra repo) or follow the Deploying an App guide:

bash
# ArgoCD auto-discovers apps/ directories — just push to git
mkdir -p apps/myapp/
# Add deployment.yaml, service.yaml, ingress.yaml
git add apps/myapp/ && git commit -m "feat: add myapp" && git push

See Traefik → Deploying your own services for the IngressRoute pattern.


Next steps