Skip to content

Self-Hosting

zod-vault server is a single binary with SQLite storage. No external databases required.

Terminal window
docker run -d \
--name zod-vault \
-p 3000:3000 \
-e JWT_SECRET="$(openssl rand -hex 32)" \
-v vault-data:/app/data \
ghcr.io/nicodlz/zod-vault-server
version: "3.8"
services:
zod-vault:
image: ghcr.io/nicodlz/zod-vault-server
ports:
- "3000:3000"
environment:
- JWT_SECRET=${JWT_SECRET}
volumes:
- vault-data:/app/data
restart: unless-stopped
volumes:
vault-data:
VariableRequiredDefaultDescription
JWT_SECRETYes-Secret for signing JWTs (32+ chars)
JWT_ISSUERNozod-vaultJWT issuer claim
JWT_ACCESS_EXPIRYNo15mAccess token lifetime
JWT_REFRESH_EXPIRYNo7dRefresh token lifetime
DB_PATHNo./data/vault.dbSQLite database path
PORTNo3000HTTP port
Terminal window
openssl rand -hex 32
  1. Create new Application → Docker Image
  2. Image: ghcr.io/nicodlz/zod-vault-server
  3. Add environment variable: JWT_SECRET
  4. Add persistent storage: /app/data
  5. Deploy
  1. New Project → Deploy from GitHub
  2. Select packages/server as root
  3. Add JWT_SECRET environment variable
  4. Deploy
fly.toml
app = "my-zod-vault"
[build]
dockerfile = "packages/server/Dockerfile"
[env]
PORT = "8080"
[mounts]
source = "vault_data"
destination = "/app/data"
Terminal window
fly secrets set JWT_SECRET="$(openssl rand -hex 32)"
fly deploy
Terminal window
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Clone and build
git clone https://github.com/nicodlz/zod-vault.git
cd zod-vault/packages/server
npm install && npm run build
# Create systemd service
sudo tee /etc/systemd/system/zod-vault.service << EOF
[Unit]
Description=zod-vault server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/zod-vault/packages/server
Environment=JWT_SECRET=your-secret-here
ExecStart=/usr/bin/node dist/index.js
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now zod-vault
server {
listen 443 ssl http2;
server_name vault.example.com;
ssl_certificate /etc/letsencrypt/live/vault.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vault.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
}
}
vault.example.com {
reverse_proxy localhost:3000
}

SQLite database is a single file:

Terminal window
# While server is running (safe)
sqlite3 /app/data/vault.db ".backup /backups/vault-$(date +%Y%m%d).db"
Terminal window
curl https://vault.example.com/health
# => {"status":"ok","timestamp":1234567890}
  • HTTPS in production
  • Strong JWT_SECRET (32+ random bytes)
  • Run as non-root user
  • Firewall (only expose 443)
  • Regular backups
  • Keep server updated