Self-Hosting
Run VOLT on your own infrastructure and understand the operational pieces behind it.
TL;DR -- Self-hosting VOLT means operating two application layers and several supporting services: the main app, one or more team clusters running ClusterDaemon, and the local MongoDB, Redis, MinIO, Docker, and proxying setup that make those clusters useful.
Overview
Self-hosting VOLT is a good fit when you want the collaboration and orchestration model of the platform while keeping the data plane under your own control.
The key thing to understand up front is that there are really two deployments to think about:
- the main application deployment that handles users, teams, APIs, auth, and the browser experience,
- and the cluster deployment that handles storage, runtime execution, notebooks, trajectory preprocessing, plugin jobs, and remote operations.
If you only prepare the app and ignore the cluster side, the product will load but most of the interesting work will have nowhere to run.
What the app layer needs
At minimum, the main application expects:
- a Linux host,
- Node.js,
- MongoDB,
- Redis,
- MinIO,
- and a reverse proxy such as nginx in front of the server and client.
Those services support the workspace layer: auth, metadata, object storage, queue state, notifications, and the HTTP or WebSocket surfaces the UI talks to.
What the cluster layer needs
Each team cluster is a separate operational concern. A connected cluster is expected to run the daemon plus the local services it depends on, and to have working Docker support because containers and notebooks rely on it.
In practice, a healthy cluster deployment also needs:
- valid cloud connection settings,
- a working daemon password and enrollment flow,
- MinIO, MongoDB, and Redis connectivity,
- and enough free memory and disk to handle trajectory processing and analysis workloads.
Minimum requirements
| Component | Minimum | Recommended |
|---|---|---|
| OS | Ubuntu 20.04+ / Debian 11+ | Ubuntu 22.04 LTS |
| Node.js | 18.x | 22.x |
| RAM | 4 GB | 8 GB+ |
| Disk | 20 GB | Sized to your trajectory and artifact volume |
| Docker | 20.x+ | Latest stable |
Supporting services
| Service | Purpose | Default Port |
|---|---|---|
| MongoDB | Structured metadata and result projections | 27017 |
| Redis | Queues, cache, and runtime state | 6379 |
| MinIO | Trajectory dumps, models, plugins, previews, and assets | 9000 |
| Guacamole (optional) | Browser-accessible remote desktop relay support | 4822 |
Quick start for the app stack
For a local or first-pass deployment, Docker Compose is the simplest way to bring up the supporting services.
version: "3.8"
services:
mongo:
image: mongo:7
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: volt
MONGO_INITDB_ROOT_PASSWORD: changeme
volumes:
- volt-mongo-data:/data/db
redis:
image: redis:7
ports:
- "6379:6379"
command: --requirepass changeme
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: voltadmin
MINIO_ROOT_PASSWORD: changeme
volumes:
- volt-minio-data:/data
command: server /data --console-address ":9001"
volumes:
volt-mongo-data:
volt-minio-data:docker compose up -dBuilding the main application
git clone https://github.com/VoltLabs-Research/Volt.git
cd Volt
cp server/.env.example server/.env
cp client/.env.example client/.envThen configure the environment files with the right MongoDB, Redis, MinIO, server URL, client URL, secret-key, and SSH encryption settings.
The two values that tend to matter most operationally are:
SECRET_KEY, which signs sensitive server-side tokens,- and
SSH_ENCRYPTION_KEY, which protects stored SSH credentials used by import flows.
After that:
cd server && npm install && npm run build
cd ../client && npm install && npm run buildThe server listens on port 8000 by default.
Reverse proxying
You need a reverse proxy in front of the app for three reasons: serving the built client, routing API traffic, and preserving WebSocket support for the real-time layer.
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/ssl/certs/your-domain.crt;
ssl_certificate_key /etc/ssl/private/your-domain.key;
location / {
root /path/to/Volt/client/dist;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 0;
}
location /socket.io/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Cluster deployment matters just as much
Once the app is online, the next milestone is connecting an actual team cluster. The official installer bootstraps Docker support, writes daemon configuration, and waits for the daemon to come online and begin heartbeating.
This is where many self-hosted setups either become fully usable or stall out. A healthy app with no healthy cluster means trajectory uploads, analysis runs, notebooks, container operations, and SSH imports will not have an execution target.
Operational checks that are worth doing
After the stack is up, verify the system as a workflow rather than as isolated services.
- open the app and confirm sign-in works,
- connect a cluster and wait for a clean heartbeat,
- upload a small trajectory,
- make sure it reaches a completed state,
- run a lightweight analysis,
- open the result and verify the full round-trip.
That end-to-end test tells you far more than checking each container individually.
Recommended runbooks
Cluster provisioning runbook
When you connect a new cluster, verify the full bootstrap path instead of stopping at "the installer finished":
- confirm the install command completed without obvious errors,
- verify the local services are up,
- check that the daemon reached its startup log cleanly,
- confirm the team cluster moved from waiting to connected,
- and make sure a fresh heartbeat is visible from the product side.
If the cluster never leaves the waiting state, the issue is often not the installer itself but the daemon's ability to reach the control plane afterward.
Analysis end-to-end runbook
After provisioning, run a tiny analysis on a small trajectory.
That single check exercises most of the system at once: upload, storage, queueing, daemon connectivity, plugin execution, result processing, and UI visibility. It is a much better confidence test than validating MongoDB, Redis, and MinIO separately.
Notebook runbook
For notebook validation, do not stop at "session created." A new Jupyter session can exist while still warming up. The real check is:
- create or open a notebook,
- wait for the runtime to become ready,
- load the proxied Jupyter UI successfully,
- and run a simple cell inside the live session.
Remote service runbook
If your deployment uses containerized services, VNC-capable images, or internal tools exposed through VOLT, validate at least one proxied service path from end to end. That confirms the exposure registry, reverse channel, and relay behavior are all healthy.
Common failure points
- The app loads, but no cluster ever reaches connected.
- The daemon starts, but cannot reach MongoDB, Redis, or MinIO.
- Native runtime dependencies fail, which breaks parsing, GLB generation, or preview rasterization.
- Jupyter startup fails because of port availability or Docker issues.
- The reverse proxy is correct for HTTP but not for WebSockets.
- A build exists for the app, but the daemon deployment was never completed.
Troubleshooting by symptom
Cluster stays in waiting-for-connection
This usually means bootstrap got far enough to register intent, but the daemon never completed a healthy control-plane connection. Start by checking the daemon logs, the VoltCloud URL settings, and whether the machine can actually reach the server over the expected network path.
Cluster becomes disconnected after working earlier
In that case, think heartbeat and control-plane health before anything else. The cluster may still have local services running, but if the daemon stops sending heartbeats or the reverse channel drops for long enough, the product will treat the machine as unavailable.
Jobs remain queued or delayed
It is tempting to blame Redis immediately, but a healthy queue can still stall if the daemon is under memory pressure. Some workers intentionally delay or requeue work when the process is too stressed to continue safely.
Jupyter stays in starting
Notebook startup can fail for reasons that have nothing to do with Jupyter itself: no free host ports, Docker problems, bad public-base-path assumptions, or a runtime container that exists but has not reached readiness yet.
VNC or proxied services never appear
If a container should expose a service but VOLT does not show it, check the container state, the expected labels, and whether the daemon's exposure registry is actually publishing snapshots. This is often a discovery issue, not an application issue.
Update succeeds conceptually but the cluster never comes back
Managed update flows depend on the installation mode and on the daemon being able to replace itself cleanly. If the deployment was not created in the expected image-driven way, the control plane may accept the request while the runtime-side update path still fails.
Delete gets stuck halfway
Cluster deletion spans both the control plane and the runtime host. If remote cleanup does not complete, you can end up with a removed control-plane object but leftover local resources, or the opposite. Treat deletion as an operational workflow, not just a button click.
Deployment hygiene
Two habits pay off quickly in self-hosted setups:
- keep secrets and
.envfiles out of version control, - and keep a short operator checklist for cluster connection, upload, analysis, and notebook validation.
Those are the workflows that tell you whether VOLT is genuinely healthy, not just technically running.
Storage layout and buckets
MinIO is not just a generic object store in this architecture. Different buckets carry different kinds of runtime artifacts.
| Bucket | Content |
|---|---|
volt-models | Generated 3D models and GLB assets |
volt-rasterizer | PNG previews and raster outputs |
volt-plugins | Plugin payloads and many plugin result files |
volt-dumps | Stored trajectory dumps |
volt-whiteboards | Whiteboard state and assets |
volt-avatars | Public avatar images |
volt-chat | Chat attachments |
volt-latex-assets | LaTeX document assets |
Final advice
Treat self-hosting VOLT as operating a coordinated system, not just a Node.js app with a database. The product, the daemon, storage, queues, native runtime, and proxying all matter, and most real issues show up at the boundaries between them.
If you want the internal picture of how those boundaries work, read Architecture next. If you want the code-level ecosystem around the daemon and native tooling, continue with Open Source Ecosystem.