Skip to Content
Installing Marshalsingle-node k3s

Install onto a single k3s node

The simplest deployment — one Linux node, in-cluster Postgres + Redis by default. Two postures via deploy_cluster:

  • reuse (deploy_cluster = false, default, the primary path) — install onto a k3s node you already run.
  • provision (deploy_cluster = true) — Terraform stands up a private GCE VM (no public IP + Cloud NAT) running k3s first, as a separate step — see below.

Prerequisites specific to this target

  • A Linux node running k3s ≥ 1.27 (k3s ships Traefik ingress + the local-path StorageClass — all this flavor needs).
  • terraform ≥ 1.5, kubectl, helm ≥ 3.14 on your workstation.
  • A kubeconfig for the node.

Standing up k3s (if you don’t have it yet) + getting a kubeconfig

curl -sfL https://get.k3s.io | sh - # Relax image-pull throttling for large tool images (optional but recommended): # add --kubelet-arg=registry-qps=0 --kubelet-arg=serialize-image-pulls=false

The admin kubeconfig on the node is /etc/rancher/k3s/k3s.yaml. Copy it to your workstation and rewrite the server address:

scp node:/etc/rancher/k3s/k3s.yaml ~/.kube/marshal-k3s.yaml perl -pi -e 's#https://127\.0\.0\.1:6443#https://<NODE-IP>:6443#' ~/.kube/marshal-k3s.yaml export KUBECONFIG=~/.kube/marshal-k3s.yaml # or set kubeconfig_path in tfvars

Install

Configure terraform.tfvars

cp ../examples/single-node-k3s.tfvars.example terraform.tfvars $EDITOR terraform.tfvars # fill the REQUIRED block (~6 values)

Required: installation_jwt, deployment_id, registry_pull_token, chart_version, ingress_host. Everything else has a default.

Apply

terraform init terraform apply

Verify

terraform output next_steps # verify commands + app URL

Datastores

By default this flavor deploys a minimal in-cluster postgres:16 (on a local-path PVC) + redis:7 and wires their DSNs in — a single node is fully self-contained. Fine for evaluation, but Postgres has no HA or backups this way. For durable data, set provision_datastores = false and pass a managed database_url.

Why not the chart’s own bundled subcharts. This flavor deliberately rolls its own Postgres/Redis pair instead of the chart’s bundled Bitnami subcharts — Bitnami withdrew their free-tier version-pinned tags upstream, so those subcharts 404 on pull today.

DNS / TLS

  • No real domain? Use sslip.io : ingress_host = "marshal.<NODE-IP-WITH-DASHES>.sslip.io". Leave ingress_tls = [] for HTTP.
  • Real TLS — install cert-manager + a ClusterIssuer, set ingress_annotations + ingress_tls, point the host’s DNS A record at the node IP.

Registry

Default is direct pull (registry_auth_mode = "secret"). To point at a shared customer-run cache instead, see Registry options.

Provisioning a private node instead of BYO

Set deploy_cluster = true (+ project_id, zone, admin_source_ranges) for Terraform to create a private GCE VM with Cloud NAT + k3s via startup script. A brand-new private node has no reachable kubeconfig at apply time, so this is a two-step flow:

Stand up the private node (infra only)

terraform apply -var deploy_cluster=true -var project_id=acme-prod \ -var zone=europe-west1-b -var 'admin_source_ranges=["10.8.0.0/24"]' <creds>

Fetch its kubeconfig over IAP, then install onto it

gcloud compute ssh marshal-k3s --zone europe-west1-b --tunnel-through-iap \ --command 'sudo cat /etc/rancher/k3s/k3s.yaml' > k3s.yaml # rewrite https://127.0.0.1:6443 -> the node's internal IP terraform apply -var deploy_cluster=false -var kubeconfig_path=./k3s.yaml <creds>

terraform output next_steps prints these exact commands after step 1.

Day-2

Bump chart_version in terraform.tfvars and terraform apply. Roll back with helm rollback marshal -n marshal.