Preview Environments
    k3s + Helm

    Self-Host Uffizzi on a VPS

    Run open-source Uffizzi on a RamNode VPS backed by k3s for on-demand ephemeral preview environments — Helm install, wildcard TLS, and Docker Compose-driven previews.

    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.com at the VPS.
    • Root or sudo access.
    • Comfort with kubectl and helm.

    1. Initial server hardening

    Create a non-root user:

    shell
    adduser deploy
    usermod -aG sudo deploy
    rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

    Harden 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.

    shell
    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 enable

    2. Install k3s

    Install k3s. Traefik is enabled by default and will act as the ingress controller for both Uffizzi and the preview environments.

    shell
    curl -sfL https://get.k3s.io | sh -
    
    # Confirm the node is ready
    sudo k3s kubectl get nodes

    Make kubectl usable as your deploy user without sudo:

    shell
    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 nodes

    3. Install Helm

    shell
    curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
    helm version

    4. Wildcard DNS

    Uffizzi serves each preview environment on its own hostname. Create a wildcard A record pointing at your VPS public IP:

    shell
    *.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:

    shell
    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=true

    Create a ClusterIssuer. This HTTP-01 example works without DNS API credentials:

    shell
    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: traefik
    shell
    kubectl apply -f clusterissuer.yaml

    For 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.

    shell
    helm repo add uffizzi https://uffizzicloud.github.io/uffizzi
    helm repo update
    
    kubectl create namespace uffizzi

    Create a values.yaml to configure the controller hostname and TLS:

    shell
    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-prod

    Install the chart:

    shell
    helm install uffizzi uffizzi/uffizzi \
      --namespace uffizzi \
      --values values.yaml

    Watch the pods come up. The API server, controller, cluster operator, PostgreSQL, and Redis should all reach a running state:

    shell
    kubectl get pods -n uffizzi -w

    Because 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:

    shell
    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 uffizzi

    When 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:

    shell
    uffizzi login --server https://uffizzi.example.com

    Follow 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:

    shell
    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: write

    Developers 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.

    shell
    # Dump the Uffizzi database from its pod
    kubectl exec -n uffizzi <postgres-pod> -- \
      pg_dump -U postgres uffizzi > uffizzi-$(date +%F).sql

    Ship 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 pod and kubectl 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 project slug matches.
    • Ingress not routing: confirm Traefik is running (kubectl get pods -n kube-system) and that the Uffizzi ingress objects reference the traefik class.

    Inspect logs with:

    shell
    kubectl logs -n uffizzi deployment/uffizzi-app -f