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 versionIf 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 versionDocker 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 versionIn 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| Variable | Safe starting value |
|---|---|
AUTH_MODE | required |
COOKIE_SECURE | false for private HTTP; true when the public-facing origin uses HTTPS |
HOST | 0.0.0.0 in Docker; 127.0.0.1 for a native server behind a local reverse proxy |
APP_BIND_ADDRESS | 127.0.0.1 for Docker until another trusted LAN device must connect |
.env. Keep a protected off-machine copy of DEVICE_CREDENTIAL_KEY; changing it makes saved printer access codes unreadable.Docker Compose deployment
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
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, andAPP_BIND_ADDRESS=127.0.0.1.Build and start
docker compose up -d --build docker compose ps
Verify readiness
curl --fail http://127.0.0.1:3000/api/ready
Continue only when the response reports
readyand the database check isok.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 -vduring normal maintenance.
Native Node.js deployment
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.
Install and build
npm ci npm run build
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
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
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 nodeis not/usr/bin/node, editExecStartin the unit before copying it.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
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
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 {} \;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.localto the server’s private IP.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
- Reserve each supported printer’s IP address in the router.
- Enable LAN-only Developer Mode and record the exact serial number, IP address, and access code.
- In VoltVault, open 3D printers and pair one printer at a time.
- 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
| Problem | Run |
|---|---|
| Docker container is unhealthy | docker compose logs --tail=200 voltvault-serverdocker compose psdf -h |
| Native service will not start | sudo journalctl -u voltvault-server -n 200 --no-pagercommand -v nodesudo -u voltvault test -w /opt/voltvault-server/data |
| Client loads but API fails | curl --fail http://127.0.0.1:3000/api/readysudo caddy validate --config /etc/caddy/Caddyfilesudo journalctl -u caddy -n 100 --no-pager |
| Login repeats | curl -I http://voltvault.local/; confirm every visit uses the same hostname and protocol, then clear stale cookies. |
| Database is locked | pgrep -af 'dist/index.js'docker ps --filter name=voltvault-server; exactly one server process should use the database. |
