PodWarden
Guides

Infrastructure Setup Guide

End-to-end guide to deploying PodWarden and provisioning a K3s cluster from scratch

Overview

This guide walks through a complete PodWarden deployment from bare metal (or VMs) to a working K3s cluster. It is based on real-world deployments on Proxmox VMs, but the same steps apply to bare metal servers, cloud instances, or any mix of the three.

By the end you will have:

  • PodWarden running on a bootstrap machine
  • A K3s control plane provisioned via PodWarden
  • One or more worker nodes joined to the cluster
  • Longhorn distributed storage installed and healthy
  • Traefik ingress controller ready for traffic

Prerequisites

Node Requirements

Each node that will join the K3s cluster needs:

  • Ubuntu 24.04 LTS (server or minimal install)
  • 2 CPU cores / 4 GB RAM minimum (control plane nodes need more for etcd)
  • 40 GB disk minimum (Longhorn reserves space for replicas)
  • Static IP or DHCP reservation on the LAN
  • SSH access with a user that has passwordless sudo

Other Linux distributions may work but are not tested. Ubuntu 24.04 is the only officially supported target.

Bootstrap Machine

The machine where PodWarden itself runs. This can be one of the cluster nodes or a separate management host.

  • Docker and Docker Compose installed
  • Network access to all nodes via SSH (port 22)
  • If nodes are on different networks: Tailscale installed and authenticated

Tailscale (Optional)

Tailscale is optional but recommended if your nodes span multiple networks or locations. Install it on the bootstrap machine and all nodes before starting. PodWarden uses Tailscale for host discovery and can provision nodes across network boundaries.

Warning: Do not enable the --ssh flag on Tailscale. Tailscale SSH replaces the system SSH daemon, which breaks Ansible-based provisioning. PodWarden needs standard OpenSSH on port 22 to provision hosts. Use Tailscale only for network connectivity, not for SSH access management.

Installing PodWarden

One-Line Installer

curl -fsSL https://www.podwarden.com/install.sh | bash

The installer is interactive — it checks prerequisites, asks for configuration, generates .env and docker-compose.yml, pulls images, and starts PodWarden.

Default install directory: /opt/podwarden/

Manual Installation

mkdir -p /opt/podwarden && cd /opt/podwarden

# Download the production compose file
curl -fsSL https://git.mediablade.net/podwarden/podwarden/-/raw/main/docker-compose.prod.yml \
  -o docker-compose.yml

# Create .env (see next section for all variables)
cp .env.example .env

# Pull and start
docker compose pull
docker compose up -d

PodWarden starts three containers:

  • podwarden-db — PostgreSQL 16 (internal)
  • podwarden-api — FastAPI backend (port 8000, host networking)
  • podwarden-ui — Next.js frontend (port 3000)

The API uses network_mode: host so it can SSH directly to target nodes for provisioning.

Environment Configuration

Edit /opt/podwarden/.env with all required settings. Every variable is explained below.

Core Settings

VariableRequiredDefaultDescription
PW_POSTGRES_PASSWORDYesDatabase password. Generate a strong random value.
PW_POSTGRES_HOSTNopw-dbDatabase hostname. Leave default for Docker Compose.
PW_POSTGRES_PORTNo5432Database port. Change if you have a port conflict on 5432.
PW_POSTGRES_DBNopodwardenDatabase name.
PW_POSTGRES_USERNopodwardenDatabase user.
PW_API_PORTNo8000API port on the host.
PW_UI_PORTNo3000UI port on the host.
NEXT_PUBLIC_PW_API_URLYesFull URL to the API from the browser, e.g. http://10.10.0.50:8000
FRONTEND_URLYesFull URL to the UI, e.g. http://10.10.0.50:3000

Encryption Key

VariableRequiredDescription
PW_ENCRYPTION_KEYYes32-byte key, base64-encoded. Used to encrypt secrets at rest.

Generate it with:

openssl rand -base64 32

Warning: The encryption key must be base64-encoded 32 bytes. A common mistake is using openssl rand -hex 32, which produces a 64-character hex string — this is the wrong format and will cause cryptographic errors when PodWarden tries to encrypt or decrypt secrets. If you see errors related to secret decryption, check this first.

Example of a correct key: K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=

Example of an incorrect key (hex): 2b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfe

Authentication

VariableRequiredDescription
PW_TEMP_ADMIN_USERNAMEFor setupTemporary admin username for initial access
PW_TEMP_ADMIN_PASSWORDFor setupTemporary admin password
PW_OIDC_ISSUER_URLFor SSOKeycloak or other OIDC provider URL
PW_OIDC_CLIENT_IDFor SSOOAuth client ID
PW_OIDC_CLIENT_SECRETFor SSOOAuth client secret
PW_OIDC_REDIRECT_URIFor SSOOAuth callback URL

Start with temp admin credentials for initial setup. After configuring OIDC or creating local users, remove the temp admin variables and restart.

Tailscale (Optional)

VariableRequiredDescription
PW_TAILSCALE_API_KEYNoTailscale API key for device discovery
PW_TAILSCALE_TAILNETNoYour tailnet name
PW_HOST_TAG_FILTERNoOnly discover hosts with this Tailscale tag

SSH / Provisioning

VariableDefaultDescription
PW_SSH_KEY_PATHPath to SSH private key inside the API container
PW_SSH_USERrootSSH user for provisioning

Hub Connection (Optional)

VariableDescription
PODWARDEN_HUB_URLHub URL (default: https://apps.podwarden.com)
PODWARDEN_HUB_API_KEYHub API key (starts with pwc_)

Creating the Admin User

  1. Set PW_TEMP_ADMIN_USERNAME and PW_TEMP_ADMIN_PASSWORD in .env
  2. Start PodWarden: docker compose up -d
  3. Open http://<bootstrap-ip>:3000 in your browser
  4. Log in with the temp admin credentials
  5. Go to Settings > Users and create a permanent local user or configure OIDC
  6. Remove PW_TEMP_ADMIN_USERNAME and PW_TEMP_ADMIN_PASSWORD from .env
  7. Restart: docker compose restart podwarden-api

Generating and Installing SSH Keys

PodWarden provisions nodes over SSH using Ansible. You need an SSH key pair that PodWarden can use to connect to all nodes.

Generate a Key Pair in PodWarden

  1. Go to Settings > Secrets
  2. Click Generate SSH Key Pair
  3. Enter a name (e.g. provisioning)
  4. PodWarden generates an ed25519 key pair and stores both keys as encrypted secrets

Install the Public Key on Each Node

Warning: PodWarden generates the SSH key pair but does not automatically install the public key on target nodes. You must do this manually before provisioning.

Copy the public key from Settings > Secrets (the key named {name}_public), then install it on each node:

# On each target node, as the provisioning user:
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "ssh-ed25519 AAAA... podwarden" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Or use ssh-copy-id from the bootstrap machine if you have password-based SSH access:

# From the bootstrap machine
ssh-copy-id -i /path/to/podwarden_key.pub user@node-ip

Verify SSH Access

Before proceeding, verify PodWarden can reach each node. From the bootstrap machine:

ssh -i /path/to/podwarden_key user@node-ip "hostname && whoami"

Each node should respond with its hostname and the provisioning user.

Adding and Probing Hosts

Adding Hosts

There are two ways to add hosts:

Tailscale Discovery (if configured):

  1. Go to Hosts and click Discover
  2. PodWarden queries the Tailscale API and lists all devices matching your tag filter
  3. Select the hosts to import

Manual Add:

  1. Go to Hosts and click Add Host
  2. Enter the hostname, IP address (LAN or Tailscale), and SSH user
  3. Save

Probing Hosts

After adding hosts, probe them to collect system information:

  1. Select a host and click Probe
  2. PodWarden SSHes to the host and collects: OS version, CPU, RAM, disk, network interfaces, GPU info, Docker version
  3. The host detail page shows all discovered information

Probing also auto-detects network types (LAN, mesh, public) based on the host's network interfaces. See the Networking guide for details.

Note: If probing fails, check that SSH keys are installed correctly and the user has passwordless sudo. PodWarden runs commands like lscpu, free, lsblk, and ip addr during probing.

Provisioning the Control Plane

The control plane is the first node in your K3s cluster.

  1. Go to Hosts and select the node you want as the control plane
  2. Click Provision (or go to Provisioning)
  3. Select Control Plane as the role
  4. Choose the K3s version (latest stable is recommended)
  5. Click Start Provisioning

PodWarden runs Ansible playbooks that:

  • Install K3s in server mode
  • Configure the node's network interfaces for flannel
  • Install Longhorn prerequisites (open-iscsi, nfs-common)
  • Install Longhorn for distributed storage
  • Fetch the kubeconfig and store it

Provisioning takes 2-5 minutes. Watch the provisioning log for progress.

Verify the Control Plane

After provisioning completes:

  1. The host status should show provisioned with role control_plane
  2. A new cluster appears in the Clusters page
  3. The cluster detail page shows one node in Ready state
  4. Longhorn volumes should appear in the cluster's storage section

Joining Worker Nodes

With the control plane running, add worker nodes to expand the cluster.

  1. Go to Hosts and select a probed host
  2. Click Provision
  3. Select Worker as the role
  4. Select the cluster to join (the one created by the control plane)
  5. Click Start Provisioning

Note: The provision API uses query parameters (not a JSON body) for the worker join request. This is an implementation detail, but relevant if you are scripting provisioning via the API directly:

POST /api/v1/hosts/{id}/provision?role=worker&cluster_id={cluster_id}

Repeat for each additional worker node. Worker provisioning takes 1-3 minutes per node.

Node Labels and Roles

After joining, you can label nodes for workload scheduling:

  • Gateway nodes: Label with node-role.kubernetes.io/gateway: "true" to pin Traefik. See the Networking & Ingress guide for details.
  • Storage nodes: Longhorn automatically uses all nodes with available disk space.
  • GPU nodes: PodWarden detects NVIDIA GPUs during probing and installs drivers during provisioning if detected.

Verifying the Cluster

After all nodes are provisioned, verify everything is healthy:

From the PodWarden UI

  1. Clusters page: cluster status should be healthy, all nodes Ready
  2. Hosts page: all provisioned hosts show their role and cluster
  3. Click into the cluster to see node details, Longhorn status, and available StorageClasses

From the Command Line (Optional)

If you have SSH access to the control plane node:

# Check node status
sudo k3s kubectl get nodes -o wide

# Check system pods
sudo k3s kubectl get pods -n kube-system

# Check Longhorn
sudo k3s kubectl get pods -n longhorn-system

# Check StorageClasses
sudo k3s kubectl get storageclass

Expected output: all nodes in Ready state, all kube-system and longhorn-system pods running, and at least the longhorn StorageClass available.

Next Steps

With your cluster running:

Infrastructure Setup Guide | PodWarden Hub