Original post: https://nabeel.dev/2025/09/28/talos-in-five
Talos Linux is an OS designed specifically for running Kubernetes.
It is locked down with no SSH access. All operations are done through a secured API.
The documentation is (understandably) catered to setting up multi-node Kubernetes clusters that are resilient to failure.
But what if you want the cheapest possible Kubernetes cluster, for testing purposes for example, where reliability isn't super important?
In this article I'll show you how to set up a simple single-node Talos cluster in less than five minutes.
By following these instructions, you can have a full Kubernetes cluster running on a single VM,
without the extra costs of control planes and load balancers that cloud providers normally add onto their Kubernetes services.
Summary
The basic outline of steps to create a single-node cluster is:
- Get a Talos ISO image
- Create a blank Talos VM instance
- Update your config to allow workloads on control plane nodes
- Initialize the Talos VM and bootstrap the cluster
- Install MetalLB
- Install Envoy Gateway
Step 0: Get a Talos ISO Image
Okay, this is where I cheat a little. I'm not counting the time it takes to download and upload a Talos VM image as part of the 5 minutes.
This step depends on which cloud provider (or home lab setup) you have.
The good news is that the official documentation is quite good.
Find the section that matches your setup and follow those instructions.
Essentially, you are going to be downloading a Talos Linux ISO.
If you are using a cloud provider (Azure, AWS, OCI, DigitalOcean, etc.),
you will then need to upload that image so that VMs can be created from that image.
I have done this on DigitalOcean and Oracle Cloud. It takes a bit of time, maybe 10-15 minutes,
but it's not hard and you only need to do it once to create as many VMs as you like going forward.
Step 1: Create a Talos VM
Next you will need to create a Talos Linux VM (or server if you're installing on bare metal).
As with the previous section, you will need to follow the instructions based on the infrastructure you are using.
I've been most recently using DigitalOcean and automating everything with PowerShell.
For me, creating a new blank Talos VM looks like this:
doctl compute droplet create --region sfo3 --image $talosImageId --size s-2vcpu-4gb --enable-private-networking --ssh-keys $sshKeyId $vmName --wait
After creating your blank VM, DO NOT follow any other instructions from the documentation!
Specifically, do not execute any of the talosctl
commands described there.
This is where we will diverge from the official documentation.
Once your VM or machine is created, make note of its IP address for the following steps.
Step 2: Bootstrap the Cluster
Now we are going to initialize our Talos Kubernetes cluster.
Do this with the following commands:
talosctl gen config $vmName "https://${VM_IP}:6443" --additional-sans $VM_IP -o $CONFIG_DIR
export TALOSCONFIG="$CONFIG_DIR/talosconfig"
talosctl config endpoint $VM_IP
talosctl config node $VM_IP
This will create a directory and populate it with an auto-generated cert and some default configuration files.
Note the following:
-
--additional-sans
ensures that the certificate is valid for the VM's public IP address - Set the
TALOSCONFIG
environment variable so you don't have to add--talosconfig mydir/talosconfig
every time you usetalosctl
Talos normally configures separate control plane and worker nodes.
This is good practice for production clusters, but is expensive when you just want to test or kick the tires.
Instead, we want to create a single control plane VM that will also be our worker.
To do this, edit controlplane.yaml
in the Talos config directory.
Scroll to the end of the file and uncomment (remove the #
) the line # allowSchedulingOnControlPlanes: true
.
Now we are ready to initialize the VM. By default, a freshly created VM waits for someone to configure it.
Once you run this command, the VM is locked down to only work with the certificate that you generated with the talosctl gen config
command.
Technically, there's a risk that someone could randomly beat you to configuring the VM and take ownership.
The likelihood of this happening is very low, but if it did, you would see a failure in the apply-config
command,
and you would simply delete the VM.
There are more secure ways to do this, specifically generating an ISO that is preconfigured to only respond to your cert.
However, that is beyond the scope of this simple tutorial.
talosctl apply-config --insecure --nodes $VM_IP --file "$CONFIG_DIR/controlplane.yaml"
Give the VM a few seconds (I wait 10) to apply the configuration, then run talosctl bootstrap
.
You can then run talosctl health
or talosctl dashboard
to watch the cluster come alive in real-time.
At this point, your Kubernetes cluster is alive and you just need to generate the kubeconfig to use it:
talosctl kubeconfig $CONFIG_DIR
export KUBECONFIG=$CONFIG_DIR/kubeconfig
You should now be able to run commands like kubectl get pods --all-namespaces
or k9s
.
Step 3: Install MetalLB
Have you ever created a LoadBalancer
type service in AKS, EKS, etc., to create a load balancer that routes traffic to your cluster?
MetalLB will give that same functionality, but for free on your bare VM.
You can install MetalLB as the documentation prescribes with:
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml
kubectl wait --timeout=5m --for=condition=available --all deployments -n metallb-system
After that, we need to configure an IPAddressPool
so MetalLB is aware of the IP address we want it to use:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: lab-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.100/32 # Replace with your VM's public IP
Replace 192.168.1.100
with the public IP address of your VM, save the YAML to metallb-ipaddresspool.yaml
and then run kubectl apply -f metallb-ipaddresspool.yaml
.
Congratulations, you now have MetalLB installed and ready to work with your Gateway Controller.
Step 4: Install Envoy Gateway Controller
Finally, you will probably want to use the Kubernetes Gateway API
to route traffic through the public IP address to services running in your cluster.
I found that Envoy Gateway was the easiest solution to achieve this.
The quick start documentation worked flawlessly, but in summary:
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.5.1 -n envoy-gateway-system --create-namespace
kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
You can test that everything works as it should with the following:
kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/v1.5.1/quickstart.yaml -n default
curl --verbose --header "Host: www.example.com" http://$VM_IP/get
Conclusion
And there you have it! A simple single-node Kubernetes cluster in less than the time it took to read this article.
You can create as many as you like, tear them down, and create more when you need them.
I ended up automating all of this in a PowerShell script, and the time to run is 3-4 minutes.
This script likely won't work right out of the box for you, but it should be fairly easy to adapt it if you like:
echo "Getting VM parameters..."
$sshKey = (doctl compute ssh-key list -o json | ConvertFrom-Json | where {$_.name.Contains('dummy')}).id
$imageId = (doctl compute image list -o json | ConvertFrom-Json | where {$_.name.Contains('Talos')}).id
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$vmName = "kcert-test-$timestamp"
mkdir $vmName | Out-Null
echo "Creating droplet..."
$vmJson = (doctl compute droplet create --region sfo3 --image $imageId --size s-2vcpu-4gb --enable-private-networking --ssh-keys $sshKey $vmName --wait -o json)
$vm = $vmJson | ConvertFrom-Json
$vmIp = $vm[0].networks.v4 | where {$_.type -eq 'public'} | Select-Object -ExpandProperty ip_address
echo "VM created with IP address: $vmIp"
echo $vmIp > $vmName/ip.txt
echo "Initializing Talos cluster at $vmIp"
talosctl gen config $vmName "https://${vmIp}:6443" --additional-sans $vmIp -o $vmName
$env:TALOSCONFIG = (Resolve-Path "$vmName/talosconfig").Path
talosctl config endpoint $vmIp
talosctl config node $vmIp
$yaml = Get-Content -Path "${vmName}/controlplane.yaml"
$yaml = $yaml -replace '# allowSchedulingOnControlPlanes:', 'allowSchedulingOnControlPlanes:'
Set-Content -Path "${vmName}/controlplane.yaml" -Value $yaml
talosctl apply-config --insecure --nodes $vmIp --file "${vmName}/controlplane.yaml"
echo "Sleeping for 10 seconds to allow the node to initialize..."
Start-Sleep -Seconds 10
talosctl bootstrap
echo "Sleeping for 10 seconds to allow the cluster to stabilize..."
Start-Sleep -Seconds 10
talosctl health
talosctl kubeconfig $vmName
$env:KUBECONFIG = (Resolve-Path "$vmName/kubeconfig").Path
echo "Setting up MetalLB"
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml
kubectl wait --timeout=5m --for=condition=available --all deployments -n metallb-system
@"
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: lab-pool
namespace: metallb-system
spec:
addresses:
- $vmIp/32
"@ | kubectl apply -f -
echo "Setting up Envoy"
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.5.1 -n envoy-gateway-system --create-namespace
kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
echo "Here are your environment variables:"
$envVars = @(
"`$env:KUBECONFIG = '$env:KUBECONFIG'",
"`$env:TALOSCONFIG = '$env:TALOSCONFIG'",
"`$env:VMIP = '$vmIp'"
)
$envVars | ForEach-Object { echo $_ }
$envVars | Out-File -FilePath "$vmName/env.txt" -Encoding utf8
Top comments (0)