Skip to content

Install

One real command on a fresh Ubuntu 22.04 / 24.04 or Debian 12 server. ~3 minutes from bootstrap to a working dashboard. Idempotent — safe to re-run.

[!IMPORTANT] Dogfood phase. Vloud is in an internal operational-validation phase right now — the install endpoint is live and signed, but public GA hasn’t been announced. The signed-release chain, the 30-day install trial, the customer lifecycle, and the support loop are all wired and we’re confirming the experience end-to-end before opening it up. If you hit anything that needs a human, report it via the in-product Help → Report a bug flow (see Bug reporting).

Install (TL;DR)

On a fresh Ubuntu 22.04 / 24.04 or Debian 12 server:

curl -fsSL https://install.vloud.app | sudo -E bash

That’s the whole install command. No placeholders, no internal knowledge required, no developer assistance. Three minutes later the script prints a dashboard URL — open it in a browser and go through the first-boot wizard.

[!TIP] Everything below is detail you only need when something goes sideways or when you want to verify the bytes before you trust them. The single command above is enough for the happy path.

Before you start: prerequisites

Most install failures come from missing one item in this list. Skim it before running anything.

1. Server

ResourceHard floorRecommended
OSUbuntu 22.04 / 24.04 LTS, Debian 12Ubuntu 24.04 LTS
Architectureamd64 / arm64amd64
CPU2 cores4+ cores
RAM1 GB4–8 GB
Disk3 GB free on /40 GB+ on /
Public IPrequired
Root accessrequired (the installer needs sudo)

RHEL / CentOS / Alma / Rocky / Fedora are not supported. The bootstrap refuses with an explicit error. See why.

2. Network

  • Outbound HTTPS — the installer downloads packages from apt mirrors, NodeSource, and the Vloud release host (install.vloud.app). If your VM is behind a corporate firewall, allow outbound 443 to deb.debian.org, archive.ubuntu.com, deb.nodesource.com, and install.vloud.app.
  • Port 2500 — the dashboard listens here by default. Either expose it (firewall rule + cloud security group), or override with VLOUD_PORT=<n> in your install env.
  • Ports 80 + 443 — required for Let’s Encrypt SSL issuance on tenant domains. If you can’t expose them, sites still run but with self-signed certs only.
  • Ports 25, 587, 993 — required only if you’ll run the mail stack. Some VPS providers block outbound 25 by default; check before you commit to email hosting on that host.

3. DNS

You’ll want at least one DNS record before you start. Two scenarios:

  • Have a domain handy? Add an A record pointing your dashboard hostname (e.g. cp.example.com) at the server’s public IP. The first-boot wizard offers to issue SSL for it automatically.
  • No DNS yet? You can still install and run the wizard — tick the “IP fallback” box on the domain step and Vloud serves the test site at the raw IP. Add proper DNS later and re-verify.

Confirm propagation before you start with dig +short cp.example.com — should print your server’s public IP.

Install paths

Three supported entry points. All three pull from the same signed release host (install.vloud.app) — the difference is which tarball gets installed.

curl -fsSL https://install.vloud.app | sudo -E bash

What it does: downloads the latest stable bootstrap script, which embeds the public release key and defaults to fetching https://install.vloud.app/releases/latest/vloud-latest.tar.gz. The tarball is JWS-signed; the embedded key verifies it before extraction.

This is what you want unless you have a specific reason to pin a version.

Specific version install

Pin to a known-good release:

curl -fsSL https://install.vloud.app/bootstrap-0.0.0-dogfood-test.sh | sudo -E bash

The versioned bootstrap script has the version baked in and falls back to fetching the matching pinned tarball under /releases/v<version>/. Use this for reproducible builds, rollback drills, or when validating a specific candidate.

Available versions are listed at https://install.vloud.app/releases/ (autoindex) and machine-readable at https://install.vloud.app/releases/index.json.

Dogfood channel install (explicit)

The default channel is currently dogfood — there’s no separate command yet. When stable + beta channels split (post-GA), you’ll be able to pin a channel with:

# Future syntax — not active yet during the dogfood phase.
export VLOUD_RELEASE_CHANNEL=dogfood
curl -fsSL https://install.vloud.app | sudo -E bash

For now, every install is dogfood-channel and the release-manifest.json declares "channel": "dogfood". The advisory above applies.

Verifying the install bytes (before piping to bash)

If you don’t trust pipe-to-bash by reflex, you shouldn’t. Three independent checks you can run by hand against the public release host.

Step 1 — Inspect the bootstrap before running it

curl -fsSL https://install.vloud.app/bootstrap.sh -o vloud-bootstrap.sh
less vloud-bootstrap.sh                    # human-readable; ~28 KB
sudo -E bash vloud-bootstrap.sh             # run only after you're happy

Step 2 — Verify the tarball’s sha256

curl -fsSL https://install.vloud.app/releases/latest/vloud-latest.tar.gz       -o vloud.tgz
curl -fsSL https://install.vloud.app/releases/latest/vloud-latest.tar.gz.sha256 -o vloud.tgz.sha256
sha256sum -c vloud.tgz.sha256
# expected: vloud.tgz: OK

The current published sha256 is:

a1a7abbc35c0d1bb6bb23a3fa42a5b9405626e96e2f6e2b8499a47ed9ccfe451  vloud-latest.tar.gz

It changes on every release. Always re-fetch the .sha256 from install.vloud.app rather than hardcoding the value above — the docs are eventually consistent; the published manifest is authoritative.

Step 3 — Verify the Ed25519 signature

The tarball is signed by the Vloud release key (kid release-v1). The signature is a compact JWS over a payload that pins the artifact name + sha256 + signing time. To verify by hand:

# Fetch the signature + the release pubkey.
curl -fsSL https://install.vloud.app/releases/latest/vloud-latest.tar.gz.sig -o vloud.sig
curl -fsSL https://install.vloud.app/pubkey/release-v1.pub.pem               -o release-v1.pub.pem

# Or run the official verifier — same chain bootstrap.sh runs at install:
git clone --depth=1 https://github.com/<org>/vloud /tmp/vloud-src
/tmp/vloud-src/scripts/verify-against-url.sh \
    https://install.vloud.app/releases/latest/vloud-latest.tar.gz \
    --pubkey release-v1.pub.pem
# expected: VERIFIED (kid=release-v1, sha256 OK, signature OK)

For reference, the active release-v1 public key is:

-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAJS9Lggn9TYA2uthSW1v1T2400vOUttWHbmi8m56/pPM=
-----END PUBLIC KEY-----

Match the file you fetched against this fingerprint before trusting any signature it verifies.

What actually gets installed

sudo -E bash lays down the following on your server. Nothing else.

LayerPathWhat
Engine/opt/vloudVloud Fastify control plane (compiled JS, ~32 MB)
Engine DB/opt/vloud/packages/server/vloud.dbSQLite database, migrations auto-applied
Env/etc/vloud.env (0640 root:vloud)JWT secret, machine_id, trial state, install_id
Runtime state/var/lib/vloud/Trial HMAC, tamper log, license cache
Logs/var/log/vloud/install.log + journalctl -u vloudInstall + engine output
System uservloudOwns engine tree; not a login user
systemdvloud.service, vloud-job-worker.service, vloud-scheduler.service, vloud.sliceEngine + workers + scheduler
sudoers/etc/sudoers.d/vloudScoped allow-list for privileged operations Vloud needs
logrotate/etc/logrotate.d/vloudDaily rotation of install + engine logs
Packagesnginx, Node 20 (NodeSource), PHP 8.3 + extensions, sqlite3, certbot, dnsutilsStandard apt install — your distro’s mirrors

Plus the daemon stack (Phase 6) — unless cPanel was detected or you opt out:

DaemonUsed forOpt out
PostfixSMTP send/receiveVLOUD_BOOTSTRAP_NO_POSTFIX=1
DovecotIMAP/POP3 mailbox accessVLOUD_BOOTSTRAP_NO_DOVECOT=1
RspamdInbound spam filteringVLOUD_BOOTSTRAP_NO_RSPAMD=1
fail2banBrute-force protectionVLOUD_BOOTSTRAP_NO_FAIL2BAN=1
BINDAuthoritative DNS for tenant zonesVLOUD_BOOTSTRAP_NO_BIND=1
vsftpdFTP/SFTP per tenantVLOUD_BOOTSTRAP_NO_FTP=1
ufwHost firewallVLOUD_BOOTSTRAP_NO_FIREWALL=1
Tenant slicessystemd resource isolationVLOUD_BOOTSTRAP_NO_TENANT_SLICES=1
Storage quotasPer-tenant disk capsVLOUD_BOOTSTRAP_NO_STORAGE_QUOTA=1

Or skip the whole stack with VLOUD_BOOTSTRAP_NO_DAEMON_STACK=1. The engine still runs — you just won’t have mail/DNS/FTP/etc. surfaces. You can layer the daemon stack on later from Settings → Modules in the dashboard (see Modules).

Nothing is installed in /usr/local/bin/, nothing replaces system packages, and nothing edits files you own outside the table above. The installer is auditable — read scripts/bootstrap.sh in the source tree or less it from the URL before piping.

Coexisting with cPanel

When the bootstrap detects /usr/local/cpanel or /var/cpanel, it sets VLOUD_COEXIST=1 automatically. In coexist mode Vloud:

  • does not bind ports 80 / 443 — only 2500;
  • writes nginx vhosts to /etc/nginx/conf.d/vloud-*.conf only;
  • defaults DNS authority to “off” (cPanel keeps owning DNS);
  • skips Phase 6’s daemon stack (cPanel owns mail / DNS / firewall);
  • still imports existing cPanel accounts via WHM API.

You don’t need to do anything special — cPanel detection is automatic. If you have cPanel but Vloud didn’t detect it (or vice versa), force it after install:

sudo systemctl stop vloud
sudo sed -i '/^VLOUD_COEXIST=/d' /etc/vloud.env
echo "VLOUD_COEXIST=1" | sudo tee -a /etc/vloud.env
sudo systemctl start vloud

What you’ll see — phase by phase

Install takes ~3 minutes on a fresh VM with reasonable network. Output is colour-coded — for a step starting, for success, for a tolerated warning, for a hard failure (refuses to proceed).

Phase 1 — Pre-flight (≤5s)

▸ Vloud bootstrap starting on Ubuntu 24.04.1 LTS
  ✓ logging to /var/log/vloud/install.log
  ✓ arch: amd64
  ✓ disk: 38GB free
  ✓ ram: 8192MB
  ✓ outbound HTTPS reachable
  ✓ cPanel not present

OS / arch / disk / RAM / network checks. If any line shows ✗, the installer refuses to continue. Common fixes:

  • “unsupported architecture” → use amd64 or arm64.
  • ”/, only 2GB free; need at least 3GB” → resize / clean disk.
  • “outbound HTTPS appears unreachable” → check firewall / proxy.
  • “port 2500 already bound” → set VLOUD_PORT=8500 or stop the other service.

Phase 2 — apt install (~45s)

▸ installing system packages
  ✓ apt packages ready
  ✓ node v20.18.0, npm 10.8.2
  ✓ pm2 installed (optional)

Installs nginx, Node 20 (NodeSource), PHP 8.3 FPM + extensions, sqlite3, certbot, dnsutils, openssh-client. If your distro’s apt cache is stale you’ll see a one-time apt-get update downloading metadata first.

Phase 3 — Release download + verification (~10s)

▸ fetching Vloud release
  ✓ release verified: vloud-latest.tar.gz (kid=release-v1)
  ✓ release extracted to /opt/vloud

This is the critical signal. “release verified” means the tarball passed every check: sha256 matches, Ed25519 JWS signature validates against the embedded public key, the signed payload’s artifact name matches the on-disk filename.

If you see ✗ release sha256 MISMATCH or ✗ release signature INVALID — the installer refuses to extract the tarball and your system is untouched. Check the mirror is serving the right bytes and re-fetch.

Phase 4 — Configure (~3s)

▸ configuring vloud system user + env file
  ✓ created vloud system user
  ✓ wrote fresh /etc/vloud.env (trial 30 days)
  ✓ /var/lib/vloud/* + /etc/vloud/* directories ready
  ✓ sudoers fragment installed
  ✓ engine tree owned by vloud:vloud
▸ installing logrotate config
  ✓ /etc/logrotate.d/vloud installed

Creates the vloud system user, generates a fresh JWT secret + machine_id-bound 30-day trial in /etc/vloud.env, drops a sudoers fragment scoped to the privileged operations Vloud needs.

Phase 5 — systemd + engine start (~5s)

▸ installing systemd units
  ✓ vloud.{service,job-worker,scheduler} installed + enabled
▸ starting engine
  ✓ engine healthy on :2500
▸ starting workers
  ✓ vloud-job-worker + vloud-scheduler running

Three systemd units land: vloud.service (Type=notify, with a 60s watchdog), vloud-job-worker.service (BullMQ consumer), and vloud-scheduler.service (periodic jobs). The engine polls /api/health for 30s after start to confirm it’s up.

Workers wait on Redis. If you don’t have Redis installed (it’s not part of the bootstrap), you’ll see ⚠ redis.service not active — workers not started — this is fine; deploy flows that need BullMQ fall back to sqlite-queue. Install with apt install redis-server later if you want BullMQ-backed deploys.

Phase 6 — Daemon stack (~90s)

▸ configuring Phase 1 daemon stack
  ▸ Postfix … ✓
  ▸ Dovecot … ✓
  ▸ Rspamd … ✓
  ▸ fail2ban … ✓
  ▸ BIND … ✓
  ▸ FTP … ✓
  ▸ Firewall … ✓
  ▸ Tenant slices … ✓
  ▸ Storage quotas … ✓
  ✓ daemon stack: all installed

Installs the mail / DNS / FTP / firewall / quota daemons. Each is skippable at install time via env var — see “What actually gets installed” above. If cPanel was detected in Phase 1, this phase is skipped entirely (cPanel owns these surfaces already).

Phase 7 — Summary

═══════════════════════════════════════════════════════════════
  ✓ Vloud is up.
═══════════════════════════════════════════════════════════════

  Server IP:    203.0.113.42
  Dashboard:    http://203.0.113.42:2500/onboarding
  Trial:        30-day trial active (state: active)
  Coexist:      no

  Manage:       sudo systemctl {start,stop,restart,status} vloud
  Tail logs:    sudo journalctl -u vloud -f
  Env file:     /etc/vloud.env  (mode 0640 root:vloud)
  Install dir:  /opt/vloud

That’s it. The dashboard URL is what you’ll open in your browser next.

Configuration env vars (all optional)

VariableDefaultEffect
VLOUD_PORT2500Dashboard port
VLOUD_BIND_HOST0.0.0.0Set 127.0.0.1 for SSH-tunnel-only access
VLOUD_TRIAL_DAYS30Local trial length
VLOUD_INSTALL_DIR/opt/vloudWhere the engine lives
VLOUD_RELEASE_URLhttps://install.vloud.app/releases/latest/vloud-latest.tar.gzOverride release source (mirrors, air-gapped)
VLOUD_LICENSE_SERVER_URLhttps://license.vloud.appCloud licence + heartbeat target
VLOUD_BOOTSTRAP_NO_*Skip individual daemons (table above)
VLOUD_BOOTSTRAP_NO_DAEMON_STACKSkip Phase 6 entirely

Example — mail-server-only install on a non-default port:

export VLOUD_PORT=8500
export VLOUD_BOOTSTRAP_NO_BIND=1
export VLOUD_BOOTSTRAP_NO_FTP=1
curl -fsSL https://install.vloud.app | sudo -E bash

Post-install verification (60 seconds)

Run these on the server right after install. Each should pass:

# 1. Engine systemd unit is active.
sudo systemctl status vloud --no-pager | head -10

# 2. Engine answers /api/health with 200.
curl -fsS http://127.0.0.1:2500/api/health
# Expected: {"ok":true, ...}

# 3. Trial state is "active" with 30 days remaining.
curl -fsS http://127.0.0.1:2500/api/install-trial/status
# Expected: {"state":"active","days_remaining":30, ...}

# 4. /etc/vloud.env has the persistent secrets the engine needs across reboots.
sudo grep -c "^VLOUD_JWT_SECRET=\|^VLOUD_MACHINE_ID=\|^VLOUD_TRIAL_EXPIRES_AT=" /etc/vloud.env
# Expected: 3

# 5. systemd watchdog is wired (Type=notify).
sudo systemctl show vloud --property=Type,WatchdogUSec
# Expected: Type=notify, WatchdogUSec=60s

# 6. Migrations applied cleanly.
sudo sqlite3 /opt/vloud/packages/server/vloud.db "SELECT count(*) FROM migrations"
# Expected: a number ≥ 230

If any of these fail, jump to Troubleshooting below.

Open the dashboard → next steps

Open the printed dashboard URL in your browser. What happens next:

  1. Login page in setup mode. Because no admin exists yet, the login page detects this and switches to a “Create admin” form. Fill in your email, password, and a username.
  2. Redirected to /onboarding. The first-boot gate sees the wizard hasn’t finished and sends you there. Walk through the first-boot wizard — five minutes to your first live test site.
  3. Activate a license (or stay on the trial). You have 30 days. Activation happens from Settings → License.
  4. Deploy your first real app. See First deploy for a WordPress + Next.js walkthrough.

Re-running the bootstrap

Idempotent. Every step gates on existing state:

  • /etc/vloud.env is preserved; only missing keys get filled.
  • VLOUD_MACHINE_ID is deterministic from /etc/machine-id and stays stable across reboots and re-installs.
  • Trial state survives — re-running doesn’t reset the 30-day clock.
  • The engine restarts at the end so any release upgrade actually lands.

To upgrade an existing install to the latest release explicitly:

curl -fsSL https://install.vloud.app | sudo -E bash -s -- --upgrade

This preserves your database, /etc/vloud.env, and all tenant data, health-checks the new engine, and rolls back automatically on failure. See Updating Vloud for the full update model.

  • Created hosting accounts, domains, certs, mailboxes are untouched.

Troubleshooting

The install ends with “engine did not become healthy on :2500 within 30s”

The engine started but didn’t answer /api/health in time. Inspect:

sudo journalctl -u vloud -n 200 --no-pager

Common causes:

  • Port already bound by another service → set VLOUD_PORT=<n> + re-run.
  • A migration in /opt/vloud/packages/server/dist/db/migrations failed to apply → see Recovery.
  • Out of memory during boot → check free -m; bump RAM.

”release sha256 MISMATCH” or “signature INVALID”

The downloaded tarball is corrupt or tampered. The installer refused to extract — your system is in the same state it was before the curl. Re-run after the mirror is fixed; the JWS chain refuses to install bytes it can’t verify.

”release download failed”

Outbound 443 to install.vloud.app is blocked, or the host is briefly down. Verify reachability:

curl -fsSL -o /dev/null -w "%{http_code} %{size_download}\n" \
  https://install.vloud.app/releases/latest/vloud-latest.tar.gz
# Expected: 200 <bytes>

Engine refuses to start with exit 21

Boot-integrity check failed — dist/index.js’s sha256 doesn’t match what was written at install time. Something modified the dist tree. See Boot-integrity refusal for the recovery path.

I locked myself out of the dashboard

See Recovery. Most common path: vloud-rescue safe-boot for a dashboard-only boot that skips every plugin except auth + health.

Logs + diagnostics

# Bootstrap log (every run appends)
sudo cat /var/log/vloud/install.log

# Engine logs (live)
sudo journalctl -u vloud -f

# Engine worker logs
sudo journalctl -u vloud-job-worker -f
sudo journalctl -u vloud-scheduler -f

# Engine on-disk audit + tamper events
sudo cat /var/log/vloud/engine.log
sudo cat /var/lib/vloud/.tamper-events

# Quick health probe
curl http://127.0.0.1:2500/api/health
curl http://127.0.0.1:2500/api/install-trial/status

Uninstalling

[!WARNING] The uninstall sequence below removes every tenant account’s home directory and all their data. Take a snapshot of /etc/vloud.env, /etc/vloud/, /var/lib/vloud/, and the engine DB before running any of the rm commands — there’s no undo.

# Step 1 — back up state you might want later.
sudo tar -czf ~/vloud-state-$(date +%s).tgz \
  /etc/vloud.env /etc/vloud /var/lib/vloud \
  /opt/vloud/packages/server/vloud.db

# Step 2 — stop + remove the units.
sudo systemctl disable --now vloud.service vloud-job-worker.service vloud-scheduler.service
sudo rm /etc/systemd/system/vloud*.service /etc/systemd/system/vloud.slice

# Step 3 — remove engine + state + sudoers + logrotate.
sudo rm -rf /opt/vloud /var/lib/vloud /etc/vloud.env /etc/vloud
sudo rm -f /etc/sudoers.d/vloud /etc/logrotate.d/vloud

# Step 4 — remove the system user (and its home).
sudo userdel -r vloud 2>/dev/null
sudo systemctl daemon-reload