Getting Started
This guide walks you through provisioning a production-ready k3s cluster from two fresh VPS nodes.
Two ways to use k3s-lab
| Mode | When to use |
|---|---|
| Direct (this guide) | Evaluate the toolkit, test locally, or you don't need a private config repo |
| Infra repo | Production 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
| Tool | Purpose | Install |
|---|---|---|
kubectl | Kubernetes CLI | brew install kubectl |
helm | Helm package manager | brew install helm |
ssh / scp | VPS access | Pre-installed on macOS/Linux |
htpasswd | BasicAuth secret generation | brew install httpd |
envsubst | Variable substitution in manifests | brew install gettext |
bats | Optional: run tests locally | brew install bats-core |
VPS nodes
| Requirement | Value |
|---|---|
| OS | Ubuntu 22.04+ or Debian 12+ |
| Architecture | x86_64 or ARM64 |
| Server: vCPU / RAM | 2 vCPU / 2 GB minimum (4 GB recommended) |
| Agent: vCPU / RAM | 2 vCPU / 1 GB minimum (2 GB recommended) |
| Disk | 20 GB+ (server), 20 GB+ (agent) |
| Public IP | Required on each node |
| DNS | Managed by Cloudflare (A records created automatically by external-dns) |
DNS records
No manual DNS setup required. Once
external-dnsis deployed, it automatically creates and updates Cloudflare A records whenever you add anIngressRoutewith theexternal-dns.alpha.kubernetes.io/hostnameannotation.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
cp .env.example .envEdit .env and fill in at minimum:
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-labSee Configuration for the full variable reference.
Step 2 — Provision the cluster
Option A: Full provisioning (recommended)
make provisionThis runs Ansible to configure all nodes:
- Common setup — packages, kernel modules, sysctl, UFW, swap disabled
- k3s server — installs k3s with Flannel VXLAN, disables Traefik/servicelb
- WireGuard — optional VPN (if
wireguard_enabled: truein group_vars) - k3s agents — joins agent nodes to the cluster
- Kubeconfig — saved locally for
kubectlaccess
Option B: Step by step
make provision-server # Common + k3s server + WireGuard
make provision-agents # Join agent nodes
make kubeconfig # Merge kubeconfig locallyRequires Ansible inventory at
ansible/inventory/hosts.yml— see Configuration.
Step 3 — Verify nodes
kubectl config use-context k3s-lab
make nodes
# NAME STATUS ROLES AGE VERSION
# server Ready control-plane,master 5m v1.32.2+k3s1Step 4 — Deploy base stack
Create the dashboard secret
make deploy-dashboard-secretDeploy Traefik + cert-manager
make deployThis deploys in order:
- Namespaces —
ingress,cert-manager,monitoring,apps - Traefik — Helm chart with values from
charts/platform-base/values.yaml - cert-manager — with CRDs installed
- ClusterIssuers — Let's Encrypt staging + production
- 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
make deploy-grafana-secretDeploy observability stack
make deploy-monitoringThis deploys:
- Grafana — visualization dashboards
- Loki — centralized log storage
- Promtail — log collector DaemonSet
- Grafana IngressRoute — HTTPS at
GRAFANA_DOMAIN
⏱️ Takes ~10 minutes (large chart images).
Step 6 — Verify
make statusAll pods should be Running or Completed.
Access points:
| Service | URL | Credentials |
|---|---|---|
| Traefik dashboard | https://DASHBOARD_DOMAIN/dashboard/ | admin / DASHBOARD_PASSWORD |
| Grafana | https://GRAFANA_DOMAIN | admin / GRAFANA_PASSWORD |
Deploy an example app
Create your app manifests in apps/ (for infra repo) or follow the Deploying an App guide:
# 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 pushSee Traefik → Deploying your own services for the IngressRoute pattern.
Next steps
- Configuration reference — all
.envvariables - k3s details — install flags, firewall, sysctl
- Traefik — IngressRoute, middlewares, TLS
- cert-manager — Let's Encrypt, staging vs production
- Monitoring — Grafana dashboards, LogQL, Prometheus
- Make targets — full reference
- Troubleshooting — common issues