Skip to content
VoxNotch

Install

Get VoxNotch running.

One script scaffolds the environment file, generates the secrets, builds the images, starts the stack, and waits until the API reports ready. Everything it asks you for is listed below before you start, so you are not answering prompts you have not thought about.

Best for
Local demo and self-hosted evaluation
Install
Docker Compose on your machine
Edge
Caddy handles local or TLS routing
Hardware
CPU works; GPU is optional

Step 1

Check you have what you need.

All five, before you run anything. The install fails fast and honestly when one is missing, but reading them now is faster than reading an error later.

  1. A Linux host that stays on

    A NAS, a home server, a small VPS, or a spare desktop. The stack runs four long-lived containers plus an edge proxy, and the worker is the one that gets hungry.

    The compose file caps the API at 3g and the worker at 6g. Those are ceilings, not measured minimums.

  2. Docker, with the Compose v2 plugin

    The launcher checks for docker compose as a subcommand and refuses the standalone docker-compose v1 binary, which is end of life. It also checks that the daemon is actually running.

    docker compose version && docker info

  3. A hostname pointing at the machine, and ports 80 and 443 open

    Caddy requests a certificate on first boot, which needs inbound port 80 and public DNS. For a local test you can pass :80 as the site address instead and get plain HTTP on localhost.

  4. Disk proportional to your media

    Audio is small. Whisper model weights are downloaded on first use into a volume. Rendered video is the thing that grows, and it grows at the rate you render.

Step 1 — check it

shell
docker compose version
docker info

The first must print a v2 version. The second must not print a "cannot connect to the Docker daemon" error. If either fails, fix that before going on — the installer will only tell you the same thing more slowly.

Fast local demo

shell
cd web
./scripts/launch/demo.sh up --build

For an evaluator path with no domain and no Supabase account, start here. The demo uses local auth, generated secrets, tiny Whisper defaults, and an isolated compose project you can reset.

Production install

shell
cd web
./scripts/launch/first-run.sh

Run this from a checked-out VoxNotch repository on the production host. The script writes web/.env with a 077 umask, generates every secret with openssl, validates the compose configuration before it starts a long build, and then waits up to 180 seconds for the API to report ready.

Step 2 — what it asks you

Two values, and it generates the rest.

Re-running after a failure? Use --resume. Without it the script refuses to overwrite an existing web/.env, on purpose.

It prompts for the public hostname and a contact email for Let’s Encrypt. Both can be exported into the environment beforehand instead, and --non-interactive requires that.

The database password, the application role password, the JWT signing secret, the key that encrypts stored provider credentials, the metrics token, and the Grafana admin password are all generated for you. You never type a secret you invented, and none of them are in the repository.

Supabase Auth is optional. Set AUTH_PROVIDER=supabase only if you intentionally want that compatibility path; the default is local email/password auth.

If you would rather read the file before anything starts, run it with --no-build --no-up. It scaffolds web/.env and stops.

Step 3 — what you should see

first-run.sh
Collecting deployment values (secrets are generated automatically)…
Public hostname (e.g. voxnotch.test; :80 for a local no-TLS test): voxnotch.test
Contact email for Let's Encrypt: [email protected]
Generating secrets…
Wrote web/.env (owner-only).

Local auth is enabled. The login screen can create the first account.

Validating configuration…
Configuration is valid.
Building images (the frontend bakes in the auth provider; this can take a while)…
Starting the stack…
Waiting for the API to become ready (up to 180s)…

Stack is up and READY.
  Application: https://voxnotch.test
  Readiness:   https://voxnotch.test/api/health/ready

Next: make yourself an admin once you've signed in at least once —
  web/scripts/launch/bootstrap.sh --admin-email [email protected]

The two service-key prompts echo nothing, which is correct rather than broken. "Stack is up and READY" is the line that matters: it means the API answered /health/ready, which means Postgres and Redis are both reachable from it.

Step 4

Open it, sign in, then make yourself an admin.

Go to the application URL the script printed and create your local account. Do this before the next command: the admin bootstrap looks up that account by email.

Then run the bootstrap with that email. It sets ADMIN_USER_IDS in web/.env and restarts the API. It is additive and safe to run twice.

Until you do, nobody is an admin — including you — and /admin returns a 404 rather than a login prompt. That is deliberate: an admin plane that announces itself is a target.

shell
web/scripts/launch/bootstrap.sh --admin-email [email protected]

For optional Supabase Auth, pass --admin-user-id with the JWT sub instead.

Step 4 — what "working" looks like

status.sh
API liveness  : OK (/health)
API readiness : READY (/health/ready)
Migration head: 070_durable_model_policy.sql
Worker        : consuming the queue

Run web/scripts/launch/status.sh any time. Those four lines are the install. Liveness without readiness means the database or the queue is unreachable; a worker that is not consuming means uploads will queue forever and never transcribe.

Step 5

If something went wrong.

These are the failures that actually happen, in rough order of how often. Each one has a specific fix, because "check your logs" is not a fix.

  1. "The Docker Compose v2 plugin is required"

    You have the old standalone docker-compose binary and not the plugin. Install the plugin or upgrade Docker; the compose file uses v2 syntax the v1 binary cannot read.

  2. "web/.env already exists"

    A previous run got far enough to write the file. Re-run with --resume to keep it and just build and start, or delete it to answer the prompts again.

  3. Caddy exits immediately and the whole site is unreachable

    Almost always an empty contact email, which makes the certificate directive malformed. Set ACME_EMAIL in web/.env and start again.

    If DNS does not yet resolve to this host, certificate issuance fails too.

  4. "Stack started but did not report ready in time"

    Run status.sh. If readiness is failing on a brand new volume, the database role provisioning did not run — the log says the application role does not exist. Removing the database volume and starting again re-runs the init scripts.

    Destroying the volume destroys the data. On a fresh install there is none.

  5. The app shows a plain development login form

    The frontend was built without the Supabase values, which are baked in at build time rather than read at runtime. A restart will not fix it; rebuild with web/scripts/launch/start.sh --build.

  6. Uploads are accepted but nothing ever transcribes

    The worker is not consuming the queue. status.sh says so in as many words. Bring it back with docker compose -f docker-compose.prod.yml up -d worker, then clear anything stuck from the admin jobs view.

Before you trust it with real work

Run the real-host closeout before inviting users.

A local demo proves the software can start. A production closeout proves your hostname, TLS, auth flow, worker path, backup visibility, and restore posture are ready for people other than you.

Arm backups on day one rather than on the day you need them. scripts/backup.sh writes a dump plus a storage archive, scripts/backup-verify.sh rehearses the restore into a throwaway database, and a systemd timer ships in deploy/systemd/ — but nothing schedules it for you.

Asked at install time.

Do I need a GPU?

Not for transcription. The stack ships the base Whisper model running on CPU, and you can raise it to large-v3 if you have the patience or the hardware.

You do need a CUDA GPU for real speech synthesis, which lives behind an optional extra. Without it the stack installs a mock engine that emits a tone, so the pipeline runs and you are never fooled into thinking you have a voice model.

Can I try it without a domain?

Yes. Answer the hostname prompt with :80 and the launcher configures plain HTTP against localhost, with no certificate request.

That is a smoke test, not a deployment. Do not put real recordings behind it.

How do I upgrade?

Pull, then start with --build. Migrations are applied through a ledger that records every file with a checksum, and a migration edited after it was applied is refused rather than silently re-run.

Take a backup first. The restore rehearsal script exists so that "I have a backup" and "I have a restore" can be different sentences.

How do I get rid of it?

web/scripts/launch/stop.sh stops the stack. Add --volumes and it destroys the data too, after making you type a confirmation.

There is no VoxNotch account to close. If you used Supabase for production sign-in, that identity project is yours to delete or keep.

Checked

  1. Commands, prompts, and output lines were read from web/scripts/launch/first-run.sh, _common.sh, and status.sh on 2026-08-01. The output above is what those scripts print, not a reconstruction.
  2. The migration head shown is the newest file in web/supabase/migrations/ on that date; yours will differ as the schema moves.
  3. Failure modes come from web/docs/runbooks/incident-response.md and deployment.md.

Start with one recording.

Record something short, watch the job run, and read the transcript. That is the whole evaluation, and it takes one file.