Setup guide
From an empty Ubuntu server to the first deployed VM. Paths below use /opt/verlocx — any folder works.
Requirements
| What | Details |
|---|---|
| Server | Ubuntu 24.04 LTS · 4 vCPU · 8 GB RAM · 40 GB disk (more for the database over time) |
| Software | Docker Engine with the compose plugin · PostgreSQL 16 |
| Name & certificate | A DNS name for the portal (e.g. verlocx.example.com) and a TLS certificate for it |
| Hypervisor | Proxmox VE — an API token (recommended) or an admin account such as root@pam |
| Directory (optional) | An Active Directory account allowed to join computers to the domain |
| Network | Outbound internet during the build (npm and Rust packages); the rules on the firewall page |
Install PostgreSQL and create the database
apt update && apt install -y postgresql-16 postgresql-client-16sudo -u postgres psql -c "CREATE ROLE verlocx LOGIN PASSWORD 'choose-a-password';"sudo -u postgres psql -c "CREATE DATABASE verlocx OWNER verlocx;"Let the containers reach PostgreSQL
The portal runs in a container and connects to the host through host.docker.internal. Postgres must listen beyond localhost and accept the Docker networks — and the firewall must let them in, but nobody else.
sed -i "s/^#\?\s*listen_addresses\s*=.*/listen_addresses = '*'/" /etc/postgresql/16/main/postgresql.confecho "host verlocx verlocx 172.16.0.0/12 scram-sha-256" >> /etc/postgresql/16/main/pg_hba.confsystemctl restart postgresqlufw allow from 172.16.0.0/12 to any port 5432 proto tcp comment 'verlocx portal -> postgres'Put the release in place
/opt/verlocx├── docker-compose.yml # portal + backend├── portal/ # the portal (with .env.local)├── backend/ # the backend (with config.toml and its keys)├── agent-release/ # the signed agent build the portal serves for self-updates└── traefik/ # reverse proxy: docker-compose.yml, certs/, dynamic/tls.ymlConfigure the portal
Create portal/.env.local. The minimum is below; every variable is explained in Configuration.
DATABASE_URL=postgres://verlocx:[email protected]:5432/verlocxVERLOCX_DB_ENCRYPTION_KEY= # openssl rand -base64 32 — back it up!ROOT_PASSWORD= # local admin login until directory sign-in is set upVERLOCX_BACKEND_URL=wss://verlocx_backend:8788/ws/portalVERLOCX_PORTAL_SECRET= # openssl rand -hex 32 — the same value as portal_secret in the backend configVERLOCX_PUBLIC_BASE_URL=https://verlocx.example.comVERLOCX_VNC_PUBLIC_BASE=wss://verlocx.example.comAGENT_RELEASE_DIR=/app/agent-releaseConfigure the backend
listen = "0.0.0.0:8787" # agents + browser console relaysportal_listen = "0.0.0.0:8788" # the portal only — not publishedportal_secret = "…" # the same value as VERLOCX_PORTAL_SECRETallow_bootstrap_enrollment = false # agents enroll with one-time tokens onlysecret_key_path = "server.key" # created on first startpublic_key_path = "server.pub" # copy this into every agent configtls_cert_path = "cert.pem" # created (self-signed) if missingtls_key_path = "tls.key"agents_store_path = "registered_agents.toml" # enrolled agentsOn first start the backend creates server.key / server.pub. The public key is what every agent pins — back up both files together with registered_agents.toml.
portal_secret is the portal's alone and opens the portal routes — on portal_listen, which guests can't reach. Publish only 8787 in docker-compose.yml, never 8788. Upgrading an installation whose templates still carry a bootstrap_secret? Keep allow_bootstrap_enrollment = true until they don't — see Configuration.Certificate and routing
Put the certificate and key in traefik/certs/ and reference them in traefik/dynamic/tls.yml. In docker-compose.yml, the portal's router and the console router (PathPrefix("/vnc")) use your host name in their Host(...) rule.
tls: certificates: - certFile: /certs/verlocx.example.com.crt keyFile: /certs/verlocx.example.com.keyStart
docker network create web # shared by Traefik and Verlocx (once)cd /opt/verlocx/traefik && docker compose up -dcd /opt/verlocx && docker compose up -d --builddocker compose logs -f --tail=80The portal applies its database migrations on start. The backend logs two lines — listening (wss) — agents + console relays and portal listener (wss) — and the portal connected to wss://verlocx_backend:8788/ws/portal.
.env.local. After changing it, rebuild: docker compose up -d --build verlocx_portal.Open the firewall
ufw allow 80/tcpufw allow 443/tcpufw allow 8787/tcp comment 'verlocx backend (agents)'The full list — including what the server needs to reach — is on Firewall & ports.
First sign-in and your first cluster
- Open the portal and sign in as
rootwithROOT_PASSWORD. - Clusters → Add cluster: enter a node's API address and choose API token or Admin account (
root@pam+ password). Both work — the admin account is only needed for snapshots on raw LVM storage. Details and commands in Connecting Proxmox. - Identity: connect Active Directory and/or Microsoft sign-in; then grant teams access under Access.
- Templates: pick which templates each project may use, with sizes, VMID range and naming.
- Settings → Deployment (per project): domain, OU and the domain-join account.
Prepare a Windows template with the agent
Install the agent in the template, point its config.toml at your backend, add your release_pubkey (see Agent releases) and convert the VM to a template. In Templates, switch on One-time agent enrollment token for it. Details in Verlocx agent. Then create your first VM from the portal and watch the deployment live.
Back up and move
To restore — or move Verlocx to another server — you need exactly these:
pg_dump -h 127.0.0.1 -U verlocx -d verlocx -Fc -f verlocx.dump # the databaseportal/.env.local # incl. VERLOCX_DB_ENCRYPTION_KEYbackend/server.key backend/server.pub backend/registered_agents.toml backend/config.tomlWith the same keys, agents reconnect to the new server as soon as their backend_url points to it — a DNS name you can move is the easiest way.
Updating Verlocx
cd /opt/verlocx# replace portal/ and backend/ with the new release (keep .env.local, config.toml and the key files)docker compose up -d --builddocker compose logs -f --tail=80 # migrations are applied on startSign new agent builds into agent-release/ (below); agents with an update_url pick them up within a day, or use Fleet → Update agent.
Agent releases and the release key
Agents install only builds signed with your release key. You sign on a separate machine with Node.js — the key never goes onto the Verlocx server, so neither the server nor a compromised guest can push code to your agents. The signing script ships with the portal as portal/scripts/agent-release.mjs.
Once: create the key pair.
node agent-release.mjs keygen --out ~/verlocx-release# writes release.key (private, mode 600 — back it up, keep it offline)# and release.pub, and prints the line for the agents' config.toml:# release_pubkey = "8a3c…91be"Put that release_pubkey line into your templates' and running agents' config.toml. The script refuses to overwrite an existing key — a new key locks out every agent that pins the old one.
For every new agent build: sign it on the signing machine.
node agent-release.mjs sign --file <agent.exe> --version 0.0.36 --release-key ~/verlocx-release/release.key --out ./agent-releaseThis copies the build and writes release.json with its version, SHA-256 and signature. Copy both files into /opt/verlocx/agent-release/ on the server. The portal checks that the build matches before offering it; agents check the signature.
--backend-key backend/server.key (copy it to the signing machine just for this, then delete it there). Move every agent to 0.0.36 quickly; afterwards sign without --backend-key.Troubleshooting
| You see | Fix |
|---|---|
network web declared as external, but could not be found | docker network create web, then start Traefik before Verlocx. |
CONNECT_TIMEOUT host.docker.internal:5432 | Postgres listens only on localhost, or ufw blocks the Docker networks — steps 1–2. |
no pg_hba.conf entry | Add the 172.16.0.0/12 line to pg_hba.conf and restart Postgres. |
| Browser warns about the certificate | The certificate in traefik/certs doesn't cover your host name — check its subjectAltName. |
| Port 80/443 already in use | Another web server (nginx, Apache) still runs on the host — stop it first. |
| Agents connected, but VMs show offline | You're looking at a different portal than the backend they connect to — check VERLOCX_BACKEND_URL. |
| Portal log: ECONNREFUSED …:8788 | The backend runs without portal_listen, or wasn't restarted after editing config.toml — the backend log must show two listening lines. |
| Portal log: Unexpected server response: 401 | VERLOCX_PORTAL_SECRET in .env.local and portal_secret in config.toml differ (or the variable is misnamed) — same value, both names. |
| Backend won't start: portal_secret … | It equals bootstrap_secret or is shorter than 24 characters — generate one with openssl rand -hex 32. |
| Update agent: no signed release published | agent-release/ has no valid release.json, or the build was changed after signing — sign it again with agent-release.mjs sign. |