A production guide for running open-source Uffizzi, a platform for on-demand ephemeral preview environments, on a single RamNode VPS backed by k3s.
Overview
Uffizzi is an open-source engine for creating lightweight, ephemeral preview environments. It lets teams spin up a live, shareable URL for every pull request or feature branch and tear it down automatically when the PR closes or a TTL expires. Developers define environments with familiar Docker Compose files, so they never need to write Kubernetes manifests directly, even though Uffizzi runs on Kubernetes underneath.
Open-source Uffizzi is licensed under Apache 2.0 and installs via Helm chart. It ships four components: an API server, a controller, a cluster operator, and a CLI. Together these provide the core ephemeral environment functionality, including virtual clusters, dev clusters, and Docker Compose environments triggered from your CI pipeline.
Uffizzi requires Kubernetes v1.19 or greater. On a single RamNode VPS the practical path is to install k3s, a lightweight certified Kubernetes distribution, then install Uffizzi on top of it. This guide is well suited to individuals and small teams (roughly five to seven developers) evaluating or running ephemeral environments in-house.
Prerequisites
- A RamNode VPS running Ubuntu 24.04 LTS. Kubernetes plus Uffizzi plus your preview workloads need headroom: an 8 GB RAM plan is recommended, with 4 GB as a realistic floor for light use. Give it at least 2 vCPU.
- A domain you control with the ability to create a wildcard DNS record. Preview environments are served on subdomains, so you will point something like
*.uffizzi.example.comat the VPS. - Root or sudo access.
- Comfort with
kubectlandhelm.
1. Initial server hardening
Create a non-root user:
adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deployHarden SSH in /etc/ssh/sshd_config (PermitRootLogin no, PasswordAuthentication no), then systemctl reload ssh.
Configure the firewall. k3s bundles the Traefik ingress controller, which serves HTTP and HTTPS. You need SSH plus 80 and 443 open. The Kubernetes API on 6443 should stay closed to the public and be reached over an SSH tunnel.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable2. Install k3s
Install k3s. Traefik is enabled by default and will act as the ingress controller for both Uffizzi and the preview environments.
curl -sfL https://get.k3s.io | sh -
# Confirm the node is ready
sudo k3s kubectl get nodesMake kubectl usable as your deploy user without sudo:
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
# Set the current context to use this file
export KUBECONFIG=~/.kube/config
echo 'export KUBECONFIG=~/.kube/config' >> ~/.bashrc
kubectl get nodes3. Install Helm
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version4. Wildcard DNS
Uffizzi serves each preview environment on its own hostname. Create a wildcard A record pointing at your VPS public IP:
*.uffizzi.example.com A <your-ramnode-ip>
uffizzi.example.com A <your-ramnode-ip>The controller hostname (where the Uffizzi API is reached) and the preview environment wildcard both resolve to the same VPS in this single-node setup.
5. Install cert-manager for TLS
Preview URLs should be served over HTTPS. cert-manager issues Let's Encrypt certificates automatically. A wildcard certificate requires a DNS-01 challenge, which means giving cert-manager access to your DNS provider's API. If you prefer to avoid that, you can issue per-hostname certificates with HTTP-01 instead, at the cost of a certificate request per preview.
Install cert-manager:
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=trueCreate a ClusterIssuer. This HTTP-01 example works without DNS API credentials:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: you@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: traefikkubectl apply -f clusterissuer.yamlFor true wildcard preview certificates, switch the solver to dns01 with your provider's credentials in a secret.
6. Install Uffizzi
Add the Uffizzi Helm repository and install the platform. Uffizzi bundles its own PostgreSQL and Redis dependencies through the chart, so you do not need to provision them separately for an evaluation deployment.
helm repo add uffizzi https://uffizzicloud.github.io/uffizzi
helm repo update
kubectl create namespace uffizziCreate a values.yaml to configure the controller hostname and TLS:
global:
uffizzi:
controller:
host: uffizzi.example.com
# Wildcard domain used for preview environment URLs
previewsHost: uffizzi.example.com
ingress:
class: traefik
tls:
enabled: true
clusterIssuer: letsencrypt-prodInstall the chart:
helm install uffizzi uffizzi/uffizzi \
--namespace uffizzi \
--values values.yamlWatch the pods come up. The API server, controller, cluster operator, PostgreSQL, and Redis should all reach a running state:
kubectl get pods -n uffizzi -wBecause chart structure and value keys evolve, cross-check the current chart's values.yaml against the version you install. The self-hosted installation guide in the Uffizzi documentation is the authoritative reference for the exact keys.
7. Install the Uffizzi CLI and create your first user
Install the CLI on your workstation or on the VPS:
curl -L "https://github.com/UffizziCloud/uffizzi_cli/releases/latest/download/uffizzi_linux_amd64" -o uffizzi
sudo install -m 0755 uffizzi /usr/local/bin/uffizzi
rm uffizziWhen self-hosting, you must create a Uffizzi user and project before running any preview workflow. Create the initial admin user against your API server, then log in:
uffizzi login --server https://uffizzi.example.comFollow the self-hosted docs for the exact user-bootstrap command for your chart version; it is typically run as a one-off job against the API server pod.
8. Wire up preview environments in CI
With the platform running, connect it to your repositories. Uffizzi integrates with GitHub Actions, GitLab CI, Bitbucket, and any CI provider as a pipeline step. The reusable GitHub Actions workflow handles creating, updating, and deleting environments and posts the preview URL as a PR comment.
A self-hosted GitHub Actions job points server at your installation and supplies credentials:
uses: UffizziCloud/preview-action@v2
with:
compose-file: 'docker-compose.uffizzi.yaml'
server: 'https://uffizzi.example.com'
username: 'admin@example.com'
password: ${{ secrets.UFFIZZI_PASSWORD }}
project: 'default'
permissions:
contents: read
pull-requests: write
id-token: writeDevelopers describe the environment in a docker-compose.uffizzi.yaml. Uffizzi supports Docker Compose, Helm, Kustomize, and raw Kubernetes manifests. Note that Uffizzi provisions database containers via Compose but does not provision managed cloud databases, so preview databases run as containers inside the environment.
9. Lifecycle and resource management
Ephemeral environments that never get cleaned up cause sprawl and cost overruns, which matters on a single VPS with finite resources.
- Configure automatic teardown on PR close or merge, which is the default with the reusable workflow.
- Set a TTL on environments so they expire even if a PR lingers.
- Monitor node pressure with
kubectl top nodes(requires metrics-server, bundled with k3s) and cap the number of concurrent environments to what your plan can hold.
10. Backups
The stateful component is the PostgreSQL database backing the Uffizzi API. Back it up on a schedule.
# Dump the Uffizzi database from its pod
kubectl exec -n uffizzi <postgres-pod> -- \
pg_dump -U postgres uffizzi > uffizzi-$(date +%F).sqlShip dumps off-box to external object storage. Preview environments themselves are disposable by design and do not need backing up.
RamNode platform notes
- RamNode prohibits running mail services. Do not configure Uffizzi or any preview environment to run an SMTP server or act as a mail relay. If you want PR notifications, they are delivered through GitHub or GitLab comments via the reusable workflow, not by email from the VPS.
- Keep the Kubernetes API (6443) closed to the public. Administer the cluster over an SSH tunnel:
ssh -L 6443:127.0.0.1:6443 deploy@your-ramnode-ip. - A single VPS is a single point of failure. This topology is right for evaluation and small teams; for anything mission-critical, run k3s across multiple nodes.
Troubleshooting
- Uffizzi pods stuck in Pending: the node is likely out of CPU or memory. Check
kubectl describe podandkubectl top nodes, and move to a larger RamNode plan if you are resource-bound. - Preview URLs return TLS errors: confirm the wildcard DNS record resolves to the VPS and that cert-manager issued a certificate (
kubectl get certificate -A). HTTP-01 cannot issue wildcard certs; use DNS-01 for*.uffizzi.example.com. - CI workflow authenticates but no environment appears: confirm the user and project referenced in the workflow exist on your self-hosted instance, and that the
projectslug matches. - Ingress not routing: confirm Traefik is running (
kubectl get pods -n kube-system) and that the Uffizzi ingress objects reference thetraefikclass.
Inspect logs with:
kubectl logs -n uffizzi deployment/uffizzi-app -f