Deploy it your way.

Use Docker Compose for the shortest path, or run the server directly on Node.js. Both paths keep the API private and put the web client behind the same origin.

Protect existing data. Before replacing or upgrading an installation, create and download a verified backup. Never delete a live database or named Docker volume as part of a routine update.

1. Prepare your platform

Docker Compose is the recommended path on every platform. Use the matching section below, then continue to extract the downloads. Native Node.js service installation is supported on Linux and documented separately.

macOS — Apple Siliconarm64

Confirm the processor, install Homebrew if needed, then install the native Apple Silicon builds of Docker Desktop and Caddy:

test "$(uname -m)" = "arm64" || { echo "This is not an Apple Silicon Mac"; exit 1; }
if ! command -v brew >/dev/null 2>&1; then
  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
eval "$(/opt/homebrew/bin/brew shellenv)"
brew install --cask docker
brew install caddy
open -a Docker
until docker info >/dev/null 2>&1; do sleep 2; done
docker version
docker compose version
caddy version

If an Intel-only container tool later requires it, install Rosetta with softwareupdate --install-rosetta --agree-to-license. VoltVault’s supplied images support ARM64 directly.

macOS — Intelx86_64

Confirm the processor, install Homebrew if needed, then install the Intel builds selected for this Mac:

test "$(uname -m)" = "x86_64" || { echo "This is not an Intel Mac"; exit 1; }
if ! command -v brew >/dev/null 2>&1; then
  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
eval "$(/usr/local/bin/brew shellenv)"
brew install --cask docker
brew install caddy
open -a Docker
until docker info >/dev/null 2>&1; do sleep 2; done
docker version
docker compose version
caddy version

Docker supports the current and two previous major macOS releases. Use the official Mac requirements to confirm an older Intel Mac is still supported.

Windows 10 or 11WSL 2

Use Docker Desktop with its WSL 2 backend. First download Docker Desktop Installer.exe, then open PowerShell as Administrator:

wsl --install -d Ubuntu-24.04
wsl --update
Start-Process "$HOME\Downloads\Docker Desktop Installer.exe" -Wait -ArgumentList 'install','--user'
Start-Process "$env:LOCALAPPDATA\Programs\DockerDesktop\Docker Desktop.exe"
wsl --version
do { Start-Sleep -Seconds 2 } until (docker info 2>$null)
docker version
docker compose version

In Docker Desktop, open Settings → General and enable Use the WSL 2 based engine. Then open Settings → Resources → WSL Integration, enable Ubuntu 24.04, and select Apply & restart. Open the Ubuntu terminal and install the remaining tools:

sudo apt update
sudo apt install -y unzip rsync caddy
docker info
caddy version

Run all remaining Linux-style commands inside the Ubuntu terminal, not PowerShell.

Ubuntu22.04 / 24.04 / 26.04

Add Docker’s official Ubuntu repository, install Compose and the deployment tools, then give the current user Docker access:

sudo apt update
sudo apt install -y ca-certificates curl unzip rsync caddy
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
. /etc/os-release
docker_suite="${UBUNTU_CODENAME:-$VERSION_CODENAME}"
docker_arch="$(dpkg --print-architecture)"
printf 'Types: deb\nURIs: https://download.docker.com/linux/ubuntu\nSuites: %s\nComponents: stable\nArchitectures: %s\nSigned-By: /etc/apt/keyrings/docker.asc\n' "$docker_suite" "$docker_arch" | sudo tee /etc/apt/sources.list.d/docker.sources
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker caddy
sudo usermod -aG docker "$USER"

Sign out and back in once, then verify with docker run --rm hello-world, docker compose version, and caddy version.

Debian12 / 13

Add Docker’s official Debian repository, install Compose and the deployment tools, then give the current user Docker access:

sudo apt update
sudo apt install -y ca-certificates curl unzip rsync caddy
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
. /etc/os-release
docker_suite="$VERSION_CODENAME"
docker_arch="$(dpkg --print-architecture)"
printf 'Types: deb\nURIs: https://download.docker.com/linux/debian\nSuites: %s\nComponents: stable\nArchitectures: %s\nSigned-By: /etc/apt/keyrings/docker.asc\n' "$docker_suite" "$docker_arch" | sudo tee /etc/apt/sources.list.d/docker.sources
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker caddy
sudo usermod -aG docker "$USER"

Sign out and back in once, then verify with docker run --rm hello-world, docker compose version, and caddy version.

Fedora or RHELdnf

Use the command matching the distribution to add Docker’s repository, then install and start the services:

. /etc/os-release
if [ "$ID" = "fedora" ]; then
  sudo dnf -y install dnf5-plugins curl unzip rsync
  sudo dnf config-manager addrepo --from-repofile https://download.docker.com/linux/fedora/docker-ce.repo
else
  sudo dnf -y install dnf-plugins-core curl unzip rsync
  sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
fi
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo dnf copr enable -y @caddy/caddy
sudo dnf install -y caddy
sudo systemctl enable --now docker caddy
sudo usermod -aG docker "$USER"

Sign out and back in once, then verify with docker run --rm hello-world, docker compose version, and caddy version. Docker’s supported RHEL releases are listed in its RHEL guide.

Arch Linuxpacman

Install the distribution packages, enable both services, and give the current user Docker access:

sudo pacman -Syu --needed docker docker-compose caddy unzip rsync
sudo systemctl enable --now docker caddy
sudo usermod -aG docker "$USER"

Sign out and back in once, then verify with docker run --rm hello-world, docker compose version, and caddy version. See the Arch Docker guide for host-specific storage and networking options.

2. Extract the downloads

macOS or Linux

Open Terminal in the folder containing both ZIP files:

mkdir -p voltvault-self-hosted
mv voltvault-server-1.0.0.zip voltvault-web-client-1.0.0.zip voltvault-self-hosted/
cd voltvault-self-hosted
unzip voltvault-server-1.0.0.zip
unzip voltvault-web-client-1.0.0.zip

Windows

Run this in PowerShell. It moves the files from Downloads into the Ubuntu WSL filesystem and extracts them there:

wsl bash -lc 'mkdir -p ~/voltvault-self-hosted'
wsl bash -lc "cp /mnt/c/Users/$env:USERNAME/Downloads/voltvault-server-1.0.0.zip ~/voltvault-self-hosted/"
wsl bash -lc "cp /mnt/c/Users/$env:USERNAME/Downloads/voltvault-web-client-1.0.0.zip ~/voltvault-self-hosted/"
wsl bash -lc 'cd ~/voltvault-self-hosted && unzip voltvault-server-1.0.0.zip && unzip voltvault-web-client-1.0.0.zip'
wsl

At the Ubuntu prompt, run cd ~/voltvault-self-hosted.

3. Create secrets and configuration

Enter the server directory, copy the example, generate both secrets, insert them, protect the file, and confirm no placeholder remains:

cd voltvault-server
cp .env.example .env
session_secret="$(openssl rand -hex 32)"
credential_key="$(openssl rand -hex 32)"
awk -v session="$session_secret" -v device="$credential_key" 'BEGIN { FS=OFS="=" } $1=="SESSION_SECRET" {$2=session} $1=="DEVICE_CREDENTIAL_KEY" {$2=device} {print}' .env > .env.new
mv .env.new .env
chmod 600 .env
unset session_secret credential_key
grep -E '^(AUTH_MODE|APP_BIND_ADDRESS|COOKIE_SECURE|HOST)=' .env
if grep -q 'replace-with' .env; then echo 'ERROR: placeholder secret remains'; exit 1; fi
VariableSafe starting value
AUTH_MODErequired
COOKIE_SECUREfalse for private HTTP; true when the public-facing origin uses HTTPS
HOST0.0.0.0 in Docker; 127.0.0.1 for a native server behind a local reverse proxy
APP_BIND_ADDRESS127.0.0.1 for Docker until another trusted LAN device must connect
Never commit .env. Keep a protected off-machine copy of DEVICE_CREDENTIAL_KEY; changing it makes saved printer access codes unreadable.

Docker Compose deployment

  1. Verify the platform tools

    Run these commands after completing your platform section. All three must succeed before deployment:

    docker version
    docker compose version
    caddy version
  2. Check the server configuration

    Complete the shared configuration above, then verify the container paths and bind address:

    grep -E '^(DATABASE_PATH|UPLOAD_DIR|APP_BIND_ADDRESS|APP_PORT)=' .env

    Confirm the output includes DATABASE_PATH=/data/inventory.sqlite, UPLOAD_DIR=/uploads, and APP_BIND_ADDRESS=127.0.0.1.

  3. Build and start

    docker compose up -d --build
    docker compose ps
  4. Verify readiness

    curl --fail http://127.0.0.1:3000/api/ready

    Continue only when the response reports ready and the database check is ok.

  5. Confirm persistence

    docker volume ls --filter name=voltvault
    docker compose exec -T voltvault-server test -f /data/inventory.sqlite
    docker compose exec -T voltvault-server test -d /uploads

    All three commands must succeed. Rebuilding keeps these volumes. Do not use docker compose down -v during normal maintenance.

Native Node.js deployment

  1. Install prerequisites for your Linux distribution

    Ubuntu or Debian
    sudo apt update
    sudo apt install -y ca-certificates curl git python3 build-essential rsync unzip caddy
    curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource_setup.sh
    sudo -E bash /tmp/nodesource_setup.sh
    sudo apt install -y nodejs
    Fedora
    sudo dnf install -y dnf5-plugins curl git python3 gcc-c++ make rsync
    sudo dnf copr enable -y @caddy/caddy
    sudo dnf install -y caddy
    curl -fsSL https://rpm.nodesource.com/setup_22.x -o /tmp/nodesource_setup.sh
    sudo bash /tmp/nodesource_setup.sh
    sudo dnf install -y nodejs
    RHEL
    sudo dnf install -y dnf-plugins-core curl git python3 gcc-c++ make rsync
    sudo dnf copr enable -y @caddy/caddy
    sudo dnf install -y caddy
    curl -fsSL https://rpm.nodesource.com/setup_22.x -o /tmp/nodesource_setup.sh
    sudo bash /tmp/nodesource_setup.sh
    sudo dnf install -y nodejs
    Arch Linux
    sudo pacman -Syu --needed nodejs npm base-devel python git rsync caddy unzip

    Verify the required versions:

    node --version
    npm --version
    git --version
    python3 --version
    make --version
    c++ --version
    rsync --version
    caddy version

    Stop if Node reports a major version below 22.

  2. Install and build

    npm ci
    npm run build
  3. Set native filesystem paths

    Update .env, create the persistent directories, and verify the values:

    sed -i.bak 's|^DATABASE_PATH=.*|DATABASE_PATH=./data/inventory.sqlite|' .env
    sed -i.bak 's|^UPLOAD_DIR=.*|UPLOAD_DIR=./uploads|' .env
    sed -i.bak 's|^HOST=.*|HOST=127.0.0.1|' .env
    sed -i.bak 's|^PORT=.*|PORT=3000|' .env
    mkdir -p data/backups uploads
    chmod 700 data data/backups uploads
    grep -E '^(PORT|HOST|DATABASE_PATH|UPLOAD_DIR)=' .env
  4. Test the production process

    Load .env, start the server, verify readiness in another terminal, then stop it with Control+C:

    set -a
    . ./.env
    set +a
    npm start
    
    # Run in a second terminal:
    curl --fail http://127.0.0.1:3000/api/ready
  5. Install the Linux service

    From the built server directory, create the service account, copy the checkout, set ownership, confirm the Node path, and install the unit:

    sudo useradd --system --home-dir /opt/voltvault-server --shell /usr/sbin/nologin voltvault
    sudo mkdir -p /opt/voltvault-server
    sudo rsync -a --exclude .git ./ /opt/voltvault-server/
    sudo chown -R voltvault:voltvault /opt/voltvault-server
    command -v node
    grep '^ExecStart=' deploy/voltvault-server.service
    sudo cp deploy/voltvault-server.service /etc/systemd/system/voltvault-server.service
    sudo systemctl daemon-reload
    sudo systemctl enable --now voltvault-server
    sudo systemctl status --no-pager voltvault-server
    curl --fail http://127.0.0.1:3000/api/ready

    If command -v node is not /usr/bin/node, edit ExecStart in the unit before copying it.

  6. Verify after reboot

    During a maintenance window, reboot and run the same checks:

    sudo reboot
    
    # After reconnecting:
    sudo systemctl is-active voltvault-server
    curl --fail http://127.0.0.1:3000/api/ready
    sudo journalctl -u voltvault-server -n 50 --no-pager

Deploy the web client for either path

  1. Build the client

    Enter the client directory and create its same-origin environment file:

    cd ../voltvault-web-client
    cp .env.example .env
    printf 'VITE_API_BASE_URL=\n' > .env

    On the native Node.js path, build with the installed runtime:

    npm ci
    npm run typecheck
    npm run build
    test -f dist/index.html

    On the Docker-only path, build in a temporary Node.js container instead:

    docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/app" -w /app node:22-bookworm sh -lc 'npm ci && npm run typecheck && npm run build'
    test -f dist/index.html
  2. Publish the built files

    Use the command for the host running Caddy.

    macOS
    web_root="$HOME/.local/share/voltvault/www"
    mkdir -p "$web_root"
    rsync -a --delete dist/ "$web_root/"
    Linux or Windows WSL
    sudo install -d -m 755 /srv/voltvault-web
    sudo rsync -a --delete dist/ /srv/voltvault-web/
    sudo find /srv/voltvault-web -type d -exec chmod 755 {} \;
    sudo find /srv/voltvault-web -type f -exec chmod 644 {} \;
  3. Configure one origin

    Use the command for the host running Caddy.

    macOS
    config_dir="$HOME/.local/share/voltvault"
    web_root="$config_dir/www"
    sed "s|/srv/voltvault-web|$web_root|" ../voltvault-server/deploy/Caddyfile.example > "$config_dir/Caddyfile"
    caddy validate --config "$config_dir/Caddyfile"
    sudo "$(command -v caddy)" start --config "$config_dir/Caddyfile"
    grep -q 'voltvault.local' /etc/hosts || echo '127.0.0.1 voltvault.local' | sudo tee -a /etc/hosts
    sudo dscacheutil -flushcache
    curl --fail http://voltvault.local/
    curl --fail http://voltvault.local/api/ready
    Linux or Windows WSL
    sudo cp ../voltvault-server/deploy/Caddyfile.example /etc/caddy/Caddyfile
    sudo caddy validate --config /etc/caddy/Caddyfile
    sudo systemctl enable --now caddy
    sudo systemctl reload caddy
    grep -q 'voltvault.local' /etc/hosts || echo '127.0.0.1 voltvault.local' | sudo tee -a /etc/hosts
    curl --fail --resolve voltvault.local:80:127.0.0.1 http://voltvault.local/
    curl --fail --resolve voltvault.local:80:127.0.0.1 http://voltvault.local/api/ready
    Windows browser name resolution

    Run PowerShell as Administrator after Caddy is running in WSL:

    Add-Content -Path "$env:WINDIR\System32\drivers\etc\hosts" -Value "`n127.0.0.1 voltvault.local"
    ipconfig /flushdns
    Invoke-WebRequest http://voltvault.local/api/ready

    For other devices, point local DNS for voltvault.local to the server’s private IP.

  4. Create the administrator

    Open http://voltvault.local/, select Create administrator, enter a unique password of at least 12 characters, submit the form, sign out, and sign back in. Public registration remains unavailable.

LAN and HTTPS decisions

Confirm the API is listening only on loopback. Use lsof -nP -iTCP:3000 -sTCP:LISTEN on macOS or ss -lntp | grep ':3000' on Linux and WSL. The listener should show 127.0.0.1:3000.

Do not port-forward the API or printer ports. When one trusted reverse proxy terminates HTTPS, update the server environment and restart it:

cd "$(dirname "$PWD")/voltvault-server"
sed -i.bak 's/^COOKIE_SECURE=.*/COOKIE_SECURE=true/' .env
sed -i.bak 's/^TRUST_PROXY=.*/TRUST_PROXY=true/' .env
docker compose up -d

# Native Linux service instead of Docker:
sudo cp .env /opt/voltvault-server/.env
sudo chown voltvault:voltvault /opt/voltvault-server/.env
sudo systemctl restart voltvault-server

Optional local printers

  1. Reserve each supported printer’s IP address in the router.
  2. Enable LAN-only Developer Mode and record the exact serial number, IP address, and access code.
  3. In VoltVault, open 3D printers and pair one printer at a time.
  4. Verify telemetry and camera output. Test Stop only on a disposable print.

Local Bambu Lab protocols are unsupported and firmware-sensitive. Bambu Studio remains responsible for slicing and print submission.

Backups and upgrades

Before every upgrade, open Settings → Backups, select Create backup, wait for the verified result, then select Download and copy the file off the server.

For Docker, also copy the persistent data and uploads to a dated host directory before rebuilding:

cd "$(dirname "$PWD")/voltvault-server"
backup_dir="./host-backups/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$backup_dir/data" "$backup_dir/uploads"
docker compose stop voltvault-server
docker compose cp voltvault-server:/data/. "$backup_dir/data/"
docker compose cp voltvault-server:/uploads/. "$backup_dir/uploads/"
docker compose start voltvault-server
docker compose up -d --build
curl --fail http://127.0.0.1:3000/api/ready
docker compose ps

For a native service, back up, replace the application files, rebuild, restart, and verify:

cd "$(dirname "$PWD")/voltvault-server"
backup_dir="/var/backups/voltvault/$(date +%Y%m%d-%H%M%S)"
sudo install -d -m 700 "$backup_dir"
sudo systemctl stop voltvault-server
sudo cp -a /opt/voltvault-server/data /opt/voltvault-server/uploads "$backup_dir/"
sudo rsync -a --delete --exclude .git --exclude .env --exclude data --exclude uploads ./ /opt/voltvault-server/
sudo chown -R voltvault:voltvault /opt/voltvault-server
sudo -u voltvault npm --prefix /opt/voltvault-server ci
sudo -u voltvault npm --prefix /opt/voltvault-server run build
sudo systemctl start voltvault-server
curl --fail http://127.0.0.1:3000/api/ready
sudo systemctl status --no-pager voltvault-server

Migrations run automatically and create a verified pre-migration database copy when existing application tables are present.

Troubleshooting

ProblemRun
Docker container is unhealthydocker compose logs --tail=200 voltvault-server
docker compose ps
df -h
Native service will not startsudo journalctl -u voltvault-server -n 200 --no-pager
command -v node
sudo -u voltvault test -w /opt/voltvault-server/data
Client loads but API failscurl --fail http://127.0.0.1:3000/api/ready
sudo caddy validate --config /etc/caddy/Caddyfile
sudo journalctl -u caddy -n 100 --no-pager
Login repeatscurl -I http://voltvault.local/; confirm every visit uses the same hostname and protocol, then clear stale cookies.
Database is lockedpgrep -af 'dist/index.js'
docker ps --filter name=voltvault-server; exactly one server process should use the database.