Vodmill docs
v1.0.0
Get help
● Start · Install

Installation
a VPS, step by step.

Install Vodmill on Ubuntu with Node.js, put https in front of it with Caddy or nginx, and run it as a systemd service. Docker is described at the end as an alternative.

Ubuntu 22.04 / 24.04systemdCaddy or nginxDocker option
Which terminal

The commands on this page are for the shell of a Linux server (bash). You type them after connecting with ssh root@your-server from the terminal of your own computer. On Windows use PowerShell's built-in ssh, or WSL.

01How it fits together

Vodmill is one Node.js process. It listens on port 8787 by default and keeps everything it stores in one folder, DATA_DIR (data inside the product folder unless you change it): two SQLite databases and the files of every workspace. ffmpeg runs on the same machine. Caddy or nginx in front serves https on your domain.

PieceWhere
Product folder/opt/vodmill in this guide
Settings file/opt/vodmill/.env, read by the server itself on start
Data/opt/vodmill/data: app.sqlite, admin.sqlite, files/
Port8787, reached only through the https proxy

021. Install Node.js 22 and ffmpeg

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs ffmpeg python3 make g++ unzip
node --version
ffmpeg -version | head -n 1
ffprobe -version | head -n 1

node --version must print v22.12 or newer. ffprobe comes with the ffmpeg package. The build tools (python3 make g++) are only used when no prebuilt database driver fits your system.

032. Unpack Vodmill

Copy the ZIP to the server (for example with scp from your computer) and unpack the source folder into /opt/vodmill:

sudo mkdir -p /opt/vodmill
sudo unzip vodmill.zip -d /opt/vodmill
ls /opt/vodmill               # package.json must be listed here

If package.json is one folder deeper, move the contents of that folder up into /opt/vodmill.

043. Create the settings file

cd /opt/vodmill
cp .env.example .env
openssl rand -hex 32
nano .env

Set three values and save (Ctrl+O, Enter, Ctrl+X):

  • PUBLIC_URL: the https address people will use, for example https://clips.your-agency.com.
  • ADMIN_SECRET_KEY: the 64 characters that openssl rand -hex 32 printed. It seals the API keys you save in the console. Keep a copy somewhere safe: without it the saved keys cannot be read after a restore.
  • TRUST_PROXY_HOPS=1, because Caddy or nginx will sit in front.

Every variable is explained in Configuration. Everything else is set later in the console.

054. Install, build, start

cd /opt/vodmill
npm install
npm run build
npm start

npm install installs the packages listed in package-lock.json. npm run build compiles the server into dist/ and the app into web/dist/. npm start runs node dist/src/server/index.js. The log then shows:

[admin] No owner account yet. Open /admin and create it with this one-time setup code:

    XXXX-XXXX-XXXX-XXXX

[vodmill] 1.0.0 on http://localhost:8787  app: /app  console: /admin

In a second SSH window check that it answers:

curl http://127.0.0.1:8787/healthz

The answer is {"ok":true,"version":"1.0.0"}. Stop the server with Ctrl+C; the next steps run it properly.

065. Run it as a service (systemd)

A process manager starts Vodmill on boot and restarts it if it stops. On Ubuntu that is systemd. Create a user for it and hand it the folder:

sudo useradd --system --home /opt/vodmill vodmill
sudo chown -R vodmill:vodmill /opt/vodmill
sudo nano /etc/systemd/system/vodmill.service

Paste this and save:

[Unit]
Description=Vodmill
After=network.target

[Service]
WorkingDirectory=/opt/vodmill
ExecStart=/usr/bin/node dist/src/server/index.js
Restart=always
User=vodmill
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now vodmill
sudo systemctl status vodmill
journalctl -u vodmill -n 50   # the log, with the setup code

The server reads .env from its own folder by itself (src/server/index.ts), so the unit file needs no EnvironmentFile. Later: sudo systemctl restart vodmill after you change .env, and journalctl -u vodmill -f to follow the log.

076. https with a reverse proxy

Point your domain's DNS A record at the server first. Then choose one.

Caddy (gets the certificate by itself)

sudo apt install -y caddy
sudo nano /etc/caddy/Caddyfile
clips.your-agency.com {
    reverse_proxy 127.0.0.1:8787
}
sudo systemctl reload caddy

nginx with Certbot

sudo apt install -y nginx certbot python3-certbot-nginx
sudo nano /etc/nginx/sites-available/vodmill
server {
    listen 80;
    server_name clips.your-agency.com;

    client_max_body_size 64m;

    location / {
        proxy_pass http://127.0.0.1:8787;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_request_buffering off;
        proxy_read_timeout 300s;
    }
}
sudo ln -s /etc/nginx/sites-available/vodmill /etc/nginx/sites-enabled/vodmill
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d clips.your-agency.com
Why the body size matters

Videos are uploaded in pieces of 8 MB (CHUNK_BYTES in src/server/routes/sources.ts), and a music track in the console may be up to 30 MB. nginx refuses bodies over 1 MB unless client_max_body_size is raised; Caddy has no limit by default.

Keep TRUST_PROXY_HOPS=1 in .env so sign-in rate limits see the visitor's address, not the proxy's. Block direct access to the port with the firewall:

sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable

087. Check the installation

  • curl https://clips.your-agency.com/healthz answers {"ok":true,"version":"1.0.0"}.
  • https://clips.your-agency.com/admin opens the console and asks for the setup code.
  • After you sign in, the console's System health page shows ffmpeg and ffprobe as found, free disk in DATA_DIR and the keys that are still missing.
The console's System health page on a read-only demo: app version, free disk, missing keys, and ffmpeg and ffprobe found with their versions

Continue with First steps: the console owner, the secret key and your first app account.

09Alternative: Docker

The package has a Dockerfile. It builds in two stages and runs plain node on node:22-bookworm-slim with Debian's ffmpeg, as the unprivileged node user, with DATA_DIR=/app/data.

cd /opt/vodmill
cp .env.example .env
nano .env                     # PUBLIC_URL, ADMIN_SECRET_KEY, TRUST_PROXY_HOPS=1
docker build -t vodmill .
docker volume create vodmill-data
docker run -d --name vodmill --restart unless-stopped \
  -p 127.0.0.1:8787:8787 --env-file .env \
  -v vodmill-data:/app/data \
  vodmill
docker logs vodmill           # the one-time setup code for /admin is here
  • -p 127.0.0.1:8787:8787 makes the server reachable only from the machine itself, so visitors always go through the https proxy.
  • The image contains the app, the web interface, the console, the face detection model (models/) and the backup scripts (Dockerfile). Everything that changes is in the vodmill-data volume.
  • Commands inside the container run with docker exec, for example docker exec vodmill npm run backup.
  • The image contains neither whisper.cpp nor yt-dlp. If you want local transcription, or imports from YouTube, Twitch or Kick links, with Docker, mount them into the container (and the whisper model) and enter the paths as the container sees them in the console.

For this guide the build, start, health check, account, backup, restore and upgrade-check commands were run on version 1.0.0 with plain Node.js. The Docker, systemd, Caddy and nginx commands follow the files in the package and the usual Ubuntu setup but were not run for this release; if a Docker step fails, use the Node.js path above.