VOLT

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

ComponentMinimumRecommended
OSUbuntu 20.04+ / Debian 11+Ubuntu 22.04 LTS
Node.js18.x22.x
RAM4 GB8 GB+
Disk20 GBSized to your trajectory and artifact volume
Docker20.x+Latest stable

Supporting services

ServicePurposeDefault Port
MongoDBStructured metadata and result projections27017
RedisQueues, cache, and runtime state6379
MinIOTrajectory dumps, models, plugins, previews, and assets9000
Guacamole (optional)Browser-accessible remote desktop relay support4822

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 -d

Building 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/.env

Then 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 build

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

  1. open the app and confirm sign-in works,
  2. connect a cluster and wait for a clean heartbeat,
  3. upload a small trajectory,
  4. make sure it reaches a completed state,
  5. run a lightweight analysis,
  6. open the result and verify the full round-trip.

That end-to-end test tells you far more than checking each container individually.

Cluster provisioning runbook

When you connect a new cluster, verify the full bootstrap path instead of stopping at "the installer finished":

  1. confirm the install command completed without obvious errors,
  2. verify the local services are up,
  3. check that the daemon reached its startup log cleanly,
  4. confirm the team cluster moved from waiting to connected,
  5. 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:

  1. create or open a notebook,
  2. wait for the runtime to become ready,
  3. load the proxied Jupyter UI successfully,
  4. 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 .env files 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.

BucketContent
volt-modelsGenerated 3D models and GLB assets
volt-rasterizerPNG previews and raster outputs
volt-pluginsPlugin payloads and many plugin result files
volt-dumpsStored trajectory dumps
volt-whiteboardsWhiteboard state and assets
volt-avatarsPublic avatar images
volt-chatChat attachments
volt-latex-assetsLaTeX 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.

On this page