VPS hosting

A full virtual machine, one of 17 sizes, with nothing of ours installed on it.

A Xuda VPS is not a container. proxmox_create_server issues POST /nodes/<node>/qemu/<template_vmid>/clone with full: 1, a whole copy of a QEMU template onto Xuda hardware, and deploy_module makes 84 calls to /qemu/ paths and zero to /lxc/. The size you pick is copied straight through rather than interpreted: slug_to_farm_spec returns { cores: s.cpu, ram_gb: s.ram, disk_gb: s.disk }, the create writes cores and memory as ram_gb x 1024 MB, then resizes scsi0 to disk_gb. There are 17 sizes under the vps server group, in four types, running from 1 vCPU with 0.5 GB and 10 GB at $2.50 a month to 32 vCPU with 256 GB and 7200 GB at $2606. Once the clone boots, the platform stops. The install branch is gated if (_conf.is_debug && !isVpsApp), so a customer VPS gets no runtime, no agent and no stack installed on it: it is a bare cloud image holding the keys you supplied. proxmox_apply_firewall returns early with the string vps: customer-managed firewall, and the per-app firewall document written afterwards carries empty inbound and outbound arrays with enforcement off. deploy_vps declares no min_plan, so any account, a free one included, can create one. What you are buying here is the machine and the hardware under it, not a managed platform on top of it.

QEMU clone, full: 1, never a container
17 sizes, $2.50 to $2606 a month
Bare image, five OS choices, no agent
A full virtual machine, one of 17 sizes, with nothing of ours installed on it.
17

VPS sizes, in four types, one server group

$2.50

Entry size: 1 vCPU, 0.5 GB RAM, 10 GB disk

5

OS images on the farm, and no marketplace

730.5

Hours in the billing month, prorated by the hour

The four types

Four types, and the type is what picks your disk.

The 17 vps slugs are grouped into four types, and the type is not a marketing label. It is read twice. Once in config.json, where PRICE_OBJ.server_types carries the one-line description each type ships with, and once in deploy_module, where SERVER_TYPE_DISK_PREF turns the type into an ordered list of acceptable disk classes for placement. Read each card with both of its chips: the left one is the disk chain the placer walks, the right one is the price span and the sizes inside it. Everything else about a tier is arithmetic on three numbers.

The disk chain is a preference walked in order, not a guarantee written on the machine. A farm node with no disk class tagged on it counts as nvme, so across the fleet as it stands today every type, shared included, ends up on NVMe. Placement also demands 20 percent slack before it will use a node: requested cores, RAM and disk are each multiplied by 1 + proxmox.headroom_pct / 100 and compared against the free capacity on that node.
Three flavours, one path

VPS, Full Stack VPS and Xucode are the same clone with a flag on it.

Three products in the dashboard, one function underneath. deploy_full_stack_vps stamps app_type_override and delegates to deploy_vps. deploy_xucode does the same with a different override, and it drops farm_spec and create_datacenter from the request first so the size comes from the entitlement rather than from the caller. That shape is deliberate: a distinct app_type would fall out of the vps branches for firewall, resize, power, web route and status, so the flavour rides on a flag instead. Read the table down a column for what you get, and across a row for how little actually differs.

VPS
Full Stack VPS
Xucode
How it is createddeploy_vps directly. No override.deploy_full_stack_vps sets app_type_override, then calls deploy_vps.deploy_xucode sets the override, forces app_hosting_preference to global, then calls deploy_vps.
What Xuda installsNothing. The install branch is gated if (_conf.is_debug && !isVpsApp), and the app is marked active as soon as the VM reads running.The same bare image, plus two things written around it: a scoped CPI API key minted before provisioning, and a per-app CouchDB from create_db.The same vps path, with one machine per account rather than one per project. A second request is answered with the first machine.
What you are billedThe slug price, prorated against a fixed 730.5-hour month from the app_cost timestamps, with a one-hour minimum billable segment. No billing cron and no accumulator document.The same slug price. Neither the key nor the database adds a line.Nothing on the hosting ledger. cost.mjs returns null for is_xucode, and all three xucode slugs are priced 0, because the plan already charged.
Which sizes you may pickAll 17, from shared_vps_05 up to large_vps_32.Not shared. The server refuses any slug whose type is shared with The shared server type is not available for a Full Stack VPS, while the picker still lists shared_managed_4 and shared_managed_8.No picker at all. The slug is read off the entitlement, and anything the caller sent about sizing is dropped rather than merged.
Who may create oneAny account. deploy_vps carries no min_plan, unlike deploy_datacenter, which is clearance admin with min_plan pro.Also no min_plan, but the shared refusal above is a floor by another name.Gated on xucode.enabled, which is true in config_dev.json and false in config.json.
FirewallNone applied. proxmox_apply_firewall returns early and the app firewall document is created with empty rule arrays and enforcement off.The same. The scoped key narrows what the box may call back into Xuda, not what may reach the box.The one exception: _xucode_sync_guard registers a host guard for the address of the box through fw_xucode_guard_set, and the host firewall has to be re-applied for it to take effect.
Placement

Picking a region narrows two lists. It does not pick a provider, and it does not pick a box.

The region control is not a datacenter picker. MACRO_REGIONS in deploy_module holds five keys, africa, apac, na, latam and emea, and each carries a farm_locations array of metro codes, a DigitalOcean slug-prefix array and one representative coordinate. Choosing a region scopes the candidate pool to those codes and seeds the distance sort with that coordinate. Everything after that is arithmetic on live node capacity, in the order below, and the last step is the one to know before you commit: when the scoped pool comes back empty the deploy is refused rather than quietly moved.

  • The region is a pair of lists. apac names sin, blr and syd; na names ash, hil, sea, nyc, sfo and tor; emea names hel, fsn, nbg, ams, lon and fra. That is 15 of the 15 metro codes in FARM_LOCATIONS. africa and latam name no farm location and no DigitalOcean prefix of their own, so their scoped pool is empty by construction rather than by load.
  • Every candidate is measured against your size plus 20 percent. Placement multiplies requested cores, RAM and disk by 1 + proxmox.headroom_pct/100, and config.json sets headroom_pct to 20. An optimized_vps_4 at 4 vCPU, 16 GB and 100 GB therefore needs 4.8 free cores, 19.2 free GB of RAM and 120 free GB of disk on a node before that node is eligible at all.
  • Then the disk class, then distance. SERVER_TYPE_DISK_PREF scopes performance and large to nvme only, optimized to ssd then nvme, and shared to hdd, ssd, then nvme. Untagged farm nodes count as nvme, so on the fleet as it stands every type lands on NVMe. Survivors are sorted by great-circle distance from the region coordinate, so the nearest node that fits wins.
  • A full region is answered, not worked around. proxmox.do_failover is false, so get_region_hosting_options returns provider farm or provider unavailable with reason no_capacity when the region has metros but no room, and no_farm_region for africa and latam. DigitalOcean is never auto-substituted underneath you: it is reachable only as an explicit pin or from the full options list.
What a region resolves to
Macro region keys
5
africa, apac, na, latam, emea
Farm metros they name between them
15
apac 3, na 6, emea 6
Regions naming no farm metro at all
2
africa and latam, refused structurally
Headroom demanded above your size
20%
cores, RAM and disk, all three
The progress bar

Seven steps are declared. One of them runs.

config.json declares job_steps on deploy_vps, and register_job stores that array verbatim with current_step 1 and total_steps 7. Those seven strings are the literal progress-bar text a customer watches during a deploy. On the farm path the create is farm_create_app, and it emits no job updates at all: the single update_job in the whole VPS create sits in deploy_vps before provisioning is handed off. The two columns below answer the same seven rows in the same order, the declaration on the left and the code on the right, because the gap between them is the point.

http_module carries a second, five-string fallback list for deploy_vps, used only when the config declares none. config.json declares seven, so that fallback never fires. What the two lanes agree on is the outcome: the VM is up. What they disagree about is everything the bar said was happening while you waited.
What you receive

Three addresses, and only two of them are yours to type.

A farm VPS with no dedicated IPv4 has no public IPv4 at all. It holds an internal VNet address on its node, one /24 per node, plus a public IPv6 when that node carries a routed /64. Reachability is built rather than assumed: at first activation the platform provisions a reverse-proxy route on a <sub>.xuda.app hostname for the browser, and a WebSocket tunnel to port 22 for the shell. A dedicated IPv4 has no tier minimum and is gated only on free capacity at the node; if no node can honour it the request is stripped and the deploy continues on the shared hostname rather than failing.

https://vps<32 hex>.xuda.app/
app_hosting_server.ip 10.10.1.x internal VNet address, one /24 per node, not routable from your laptop
ipv6 public /64 slice present only when the node carries a routed /64
a dedicated public IPv4 is $2.50 a month plus a $10 one-time setup, and only if the node has one free
https://vps<32 hex>.xuda.app/ reverse proxied to port 80 on your VM
the subdomain is the app id lowercased, prefix vps plus 32 hex, so it cannot collide. The port is settable.
brew install websocat
websocat -b -E tcp-l:127.0.0.1:2222 wss://vps<32 hex>.xuda.app/__xuda_ssh -H "Authorization: Bearer YOUR_CONNECTION_TOKEN"
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=ERROR -p 2222 [email protected]
sftp rides the same tunnel on the same port, which is what FileZilla and WinSCP point at
the token is shown once and kept only as a sha256 hash, so a lost one is rotated, never recovered
what answers on port 22 is the stock cloud image plus the keys you supplied. Nothing else was installed.
The two long command lines are what the dashboard prints for macOS, with your own host and token substituted. 127.0.0.1:2222 is opted out of known_hosts on purpose: that port maps to a different VM per app and per session, so pinning its host key would hard fail on the next box.
Three moves

Region, size, and then the machine is yours.

deploy_vps carries no min_plan, so any account including a free one can create a VPS, and deploy_datacenter next to it is clearance admin with min_plan pro. Membership changes none of the prices: member_price_id exists only on the ai_workspace plans, and no server slug carries a Stripe price id or a member rate at all. Hosting is metered rather than subscribed, prorated against a fixed 730.5-hour month with a one-hour minimum billable segment.

  1. 01

    Pick a region and a size

    The VPS ladder is 17 slugs in four types: shared at 1 vCPU, 0.5 GB and 10 GB for $2.50 up to 8 vCPU, 16 GB and 320 GB for $94; optimized from $66 to $542; performance from $166 to $1006 and on to 32 vCPU, 256 GB and 800 GB for $1342; large from $650 to 32 vCPU, 256 GB and 7200 GB for $2606. The type is not a marketing label, it is the disk-class chain placement will honour. The OS list is five images, Ubuntu 24.04 LTS, Ubuntu 22.04 LTS, Debian 12, Rocky Linux 9 and AlmaLinux 9. There is no farm marketplace and no one-click app catalogue.

  2. 02

    The VM clones and boots

    A tier is copied straight through, not interpreted: slug_to_farm_spec returns cores, ram_gb and disk_gb from the slug, the create clones a template with full: 1 so it is a full QEMU machine rather than a container or a linked clone, sets cores, sets memory to ram_gb times 1024 MB, resizes scsi0 to disk_gb and starts it. The moment the VM reads running the app is marked active, because for a raw VPS there is nothing left to wait for. deploy_module makes 84 calls to /qemu/ paths and zero to /lxc/.

  3. 03

    Take it over the web route and the tunnel

    The browser hostname and the SSH-over-WebSocket access are both ensured during the create, so the box is reachable before you have configured anything on it. From there it is yours in the literal sense: the platform applies no VM firewall to a raw VPS, does not log in, and installs nothing. Two limits to plan around. Resize is offline and disk is grow-only, so farm_resize_app refuses unless the VM reads stopped, takes the larger of current and requested disk, and leaves the guest filesystem to grow on the boot you perform. And block storage, though shipped, is switched off: proxmox.block_storage.nfs_mount is empty in both configs, so every volume method answers that block storage is not configured.

Honest limits

Nine things a Xuda VPS does not do, four of them promised by the interface around it.

  • Nothing is installed on it. farm_create_app marks a VPS active the moment the VM reports running, and the install branch under that is gated if (_conf.is_debug && !isVpsApp), so on a customer box it never runs. What boots is the bare cloud image with the SSH keys you supplied. cpi/deploy_module/index.mjs lines 13621 to 13641.
  • The platform applies no firewall to it. proxmox_apply_firewall returns early with the string vps: customer-managed firewall at lines 12369 to 12372, and the per-app firewall document written afterwards carries empty inbound and outbound rule arrays with enforcement off, in cpi/firewall_module/index.mjs lines 551 to 596. Closing ports is your job, and nothing on the box does it for you.
  • Block storage is shipped and switched off. proxmox.block_storage.nfs_mount is an empty string in both config.json and config_dev.json, so all six farm_volume_ methods, create attach detach resize list delete, answer block storage not configured. index.mjs line 14022. Your disk is what the slug carries plus whatever you grow it to.
  • The Full Stack VPS picker and the server disagree. Power.vue line 219 lists the managed group minus its two smallest entries, which leaves shared_managed_4 and shared_managed_8 selectable, and index.mjs line 1748 refuses any slug whose type is shared with The shared server type is not available for a Full Stack VPS. config.json also carries no full_stack_vps block at all: the 13 tool groups and 81 methods live only in config_dev.json, so on the production config that key expands to an empty allowlist.
  • The 58 slugs resolve to 29 distinct provider ids, and 16 of those ids are carried by more than one slug. lookup_server_info tries the key first, then scans by .id and returns the first match, cost.mjs lines 46 to 54. For 12 of the 16 the first entry in the file is the deployment-group one, so a legacy document that stored gd-2vcpu-8gb instead of optimized_vps_2 bills $73 a month for a machine listed at $66.
  • The six service_hosting_ slugs, basic cpu general compute balanced memory, all carry disk 50 and are priced $24 to $129 purely on CPU and RAM. They are deployment-group boxes for services whose data lives in the datacenter, and they are not offered in the VPS flow at all. config.json lines 938 to 991.
  • The declared job_steps for deploy_vps promise setting vps firewall, setting vps backup plan, connecting to vps server, installing vps resources and activating the server. farm_create_app emits no progress update of its own, so on the farm path only creating vps server ever appears. The four steps after it are declaration, not work.
  • The vps feature list in config claims SSD-Based Servers, 1-Click App Install and Managed Backups. The shared disk preference is hdd then ssd then nvme, the farm offers five OS images and no marketplace, and backups are a $10 a month opt-in. config.json lines 781 to 791.
  • app_server_type is mandatory: false on deploy_vps. A slug-less global deploy provisions the 1 vCPU, 0.5 GB, 10 GB floor, writes no app_cost, and is_billable then returns false, so the machine runs and bills nothing. The balance check that would have caught it, validate_account_topup, is commented out at index.mjs line 1480.
Before you deploy

The six questions the pricing table does not answer

Answered against the shipped code and the config keys that decide it, including where the answer is no.

Watch

Watch a clone become a running machine.

A full clone of the template VM, cores and memory written onto the copy, scsi0 resized to the slug disk, then the status going active the second the VM reports running. The interesting part is what does not happen next: no install step, no agent, no rule set. What comes up is the OS image you picked with the keys you gave it.

Pick a slug and a region, and the placer picks the box.

Seventeen slugs, from 1 vCPU, 0.5 GB and 10 GB at $2.50 a month up to 32 vCPU, 256 GB and 7200 GB at $2606, every one of them a full QEMU virtual machine cloned with full: 1 rather than a container. The region you choose is a macro key, not a provider and not a machine: placement keeps the farm nodes that clear your cores, RAM and disk plus 20 percent headroom, sorts the survivors by great-circle distance, and takes the nearest. If a region has no room the answer is no_capacity, not a quiet move to DigitalOcean, because do_failover is false. Billing runs on the meter from the hour it boots.