Bare Metal
This guide provides complete instructions for deploying Hook0 on bare metal servers.
This configuration is intended for development and testing purposes only, not for production use.
This setup does not address production concerns such as:
- Scalability - handling increased load and traffic
- Resilience - fault tolerance and recovery
- High availability - minimizing downtime
- Observability - monitoring, logging, and alerting
- 24/7 operations - on-call support and incident management
- Security - the ready-made deployment files turn off webhook target address checking, and the Docker Compose file loosens two more guards
The Docker Compose file, the Kubernetes deployments.yaml and the Helm chart all set
DISABLE_TARGET_IP_CHECK to true. That switches off the check
which refuses a webhook target whose address is not globally reachable, and it is what lets a local
stack deliver to localhost. It also lets a webhook from these deployments reach anything else the
worker can reach on your network. On any deployment that accepts subscriptions you do not control,
leave the flag at its default of false.
The Docker Compose file sets two more values that belong to a development stack alone:
REVERSE_PROXY_IPSholds172.16.0.1/32, the address of this stack's own bridge gateway. A request that reaches a published port from the host arrives as that gateway rather than as the caller, and this setting tells the API to read the caller's address out of a proxy header instead. It is held to one address on purpose, since the gateway is the only address on the network that no container can be given, and anything wider would cover every sibling container and whatever an operator adds to the network next. Set it to the address of your own reverse proxy, and to nothing wider.API_RATE_LIMITING_EMAIL_BURST_SIZEis raised from 5 to 20, the headroom the browser-driven end-to-end tests need. That quota is what bounds an address-enumeration sweep against the endpoints that send an email to an address the caller names, so a deployment that keeps the raised value hands an attacker four times the sweep for free.
The two interact, and the first one decides how much the second is worth. A quota keyed on the caller's IP bounds a sweep only while the caller is stuck with one address, and anyone the trusted range covers can pick a fresh one per request. Here that is every process on the host, which is what a development stack is for. Widen the range in a real deployment and you take the per-IP bound off the mail-sending endpoints altogether, whatever the burst size says. The per-account cooldown and daily allowance held in the database survive either way, but they bound what a single mailbox receives and see nothing of a sweep across many addresses. See Security for what each limiter covers.
For production workloads, use Hook0 Cloud which provides a fully managed, production-ready infrastructure.
Requirements
- A PostgreSQL 18+ database
- Node.js LTS
- Rust stable toolchain
Installation Steps
Repository Setup
Clone the repository from GitLab and navigate to the project directory:
git clone https://gitlab.com/hook0/hook0.git
cd hook0
UI Building
The frontend is built by Vite, which passes an environment variable through to the bundle only when
its name starts with VITE_. frontend/vite.config.ts sets no envPrefix, so that prefix is the
whole of what reaches the code. The API's base URL is VITE_API_ENDPOINT:
cd frontend
export VITE_API_ENDPOINT=https://your-api-url.com/api/v1
npm install
npm run build
The value is compiled into the bundle, so changing it means building again rather than restarting anything.
| Variable | What it sets | Default |
|---|---|---|
VITE_API_ENDPOINT | Base URL of the API the dashboard calls | empty, which leaves every request relative to wherever the dashboard is served from |
VITE_API_TIMEOUT | Milliseconds before a call to the API is abandoned | 3000 |
VITE_ALLOWED_API_ORIGINS | Comma-separated origins the API_ENDPOINT override below may point at, on top of the origin of VITE_API_ENDPOINT | empty |
VITE_PLAY_ENDPOINT | Base URL of the webhook testing tool offered when a subscription is created | https://play.hook0.com |
VITE_CRISP_WEBSITE_ID | Crisp website ID, which turns on the support chat widget | empty, and the widget is never loaded |
API_ENDPOINT without the prefix is something elseIt is a query-string override read from the dashboard's own URL, meant for debugging, and it is
accepted only when it points at an origin already on the allowlist above. Exporting it in a shell
before npm run build does nothing at all: Vite drops every variable that is not prefixed, and the
build succeeds with an empty base URL, so the dashboard loads and every call it makes goes nowhere.
API Compilation
Using Rust's cargo tool with SQLX_OFFLINE=true:
cd api
SQLX_OFFLINE=true cargo build --release
The build generates an executable that serves as a web server, binding to 127.0.0.1:8080 by default.
Configuration Notes
The API supports numerous configuration options via CLI parameters or environment variables, viewable through the help command:
./target/release/hook0-api --help
For HTTPS validation against the OS certificate store, use:
cargo build --release --no-default-features --features reqwest-rustls-tls-native-roots
Logging Setup
Configure logging before running the API:
export RUST_LOG=info,sqlx=warn,actix_governor=warn
./target/release/hook0-api
Output Worker
A separate worker component handles webhook delivery, compiled similarly to the API:
cd output-worker
SQLX_OFFLINE=true cargo build --release
./target/release/hook0-output-worker
Multiple workers can run concurrently to distribute processing load.
Deployment Notes
Hook0 UI can be served by the API server or deployed separately as a static application.