Installation
Quick Start
Download the binary for your platform from GitHub Releases and run:
./waxum --token mysecrettoken
That's it. No database configured — Waxum drops a waxum.db SQLite file
next to the binary and stores everything (sessions, webhooks, DLQ) there.
Zero config.
Scale-out or need Postgres / MySQL? Pass a DATABASE_URL:
./waxum --token mysecrettoken --db postgres://user:pass@localhost:5432/waxum
./waxum --token mysecrettoken --db mysql://user:pass@localhost:3306/waxum
Schema migrations run automatically at startup on every backend.
Docker Compose
git clone https://github.com/imtaqin/waxum.git
cd waxum
Create a .env file:
# Database — choose one:
DATABASE_URL=mysql://user:password@host:3306/wars
# DATABASE_URL=postgres://user:password@localhost:5432/wagateway
# Authentication
SUPERADMIN_TOKEN=your-secret-token
# Optional
JWT_SECRET=your-jwt-signing-secret
WHATSAPP_STORAGE_PATH=/app/whatsapp_sessions
RUST_LOG=waxum=info,tower_http=info
Then run:
docker compose up -d
This starts NATS (message queue) and the Waxum API. To also run a bundled PostgreSQL:
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -d
To run without NATS, remove or comment out the NATS_URL line in .env.
What is in the image
Since v0.13.5 the release ships a distroless image
(gcr.io/distroless/cc-debian13 plus the waxum binary, about 65 MB).
There is no shell, package manager, curl or gosu inside it, which
removes the CVEs those carried. What they did is built into the binary:
- Non-root. The container starts as root, chowns the mounted data
directories (
WHATSAPP_STORAGE_PATHand the SQLite directory), then drops to the uid:gid inWAXUM_RUN_AS(1000:1000) before serving. Volumes written by an older root-running image keep working. Running the container with--user 1000:1000works too; nothing is chowned then. - Healthcheck. The image declares
HEALTHCHECK CMD ["/app/waxum", "--healthcheck"], which requests/healthon127.0.0.1:$PORTand exits0only on a200. - Stop.
docker stopends the process immediately onSIGTERM.
If your own compose file or orchestrator defines a healthcheck with
curl or wget, switch it, because those binaries no longer exist in
the image:
healthcheck:
test: ["CMD", "/app/waxum", "--healthcheck"]
interval: 30s
timeout: 5s
retries: 3
docker exec ... sh is gone for the same reason. Use
docker logs and the /status diagnostics instead.
Build from Source
Linux (Ubuntu/Debian)
# Install dependencies
sudo apt-get update
sudo apt-get install -y \
build-essential \
pkg-config \
libssl-dev \
libsqlite3-dev
# Install Rust nightly
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustup default nightly
# Clone and build
git clone https://github.com/imtaqin/waxum.git
cd waxum
cargo build --release
# Run
./target/release/waxum --token mysecrettoken --db mysql://user:pass@localhost/wars
Windows
The prebuilt release binary is compiled with x86_64-pc-windows-gnu
(MinGW runtime is statically linked) so it runs on a fresh Windows
install without needing the Microsoft Visual C++ Redistributable
(VCRUNTIME140.dll). Just download the .zip, extract, and run.
To build from source:
# 1. Install Rust from https://rustup.rs
# 2. Open a new terminal after installation
# Install nightly toolchain + the GNU target
rustup default nightly
rustup target add x86_64-pc-windows-gnu
# MinGW for the C toolchain
choco install mingw -y
# Clone and build
git clone https://github.com/imtaqin/waxum.git
cd waxum
cargo build --release --target x86_64-pc-windows-gnu
# Run
.\target\x86_64-pc-windows-gnu\release\waxum.exe --token mysecrettoken --db mysql://user:pass@localhost/wars
If you build with the default MSVC target instead
(cargo build --release without --target), the resulting binary
depends on VCRUNTIME140.dll and will fail with a "DLL not found"
error on machines without the VC++ Redistributable installed.
macOS
# Install Xcode command line tools
xcode-select --install
# Install Rust nightly
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustup default nightly
# Clone and build
git clone https://github.com/imtaqin/waxum.git
cd waxum
cargo build --release
# Run
./target/release/waxum --token mysecrettoken --db postgres://user:pass@localhost/wagateway
CLI Arguments
Usage: waxum [OPTIONS]
Options:
-t, --token <TOKEN> Set superadmin token
-d, --db <URL> Set database URL (postgres/mysql)
-p, --port <PORT> Set server port (default: 3451)
-h, --help Show help
Examples:
# MySQL with custom port
./waxum --token mytoken --db mysql://user:pass@localhost:3306/wars --port 8080
# PostgreSQL
./waxum -t mytoken -d postgres://user:pass@localhost:5432/wagateway
CLI arguments override .env values.
Database
Waxum runs against three backends. Pick one, or pick none — the default is fine.
SQLite (default, zero-config)
Leave DATABASE_URL empty and Waxum drops a local SQLite file next to
the binary at ./waxum.db. Schema is auto-migrated on boot. Good for
development and single-node production.
Override the path if you want:
./waxum --token mytoken --db sqlite://./data/waxum.db
# or
SQLITE_PATH=./data/waxum.db ./waxum --token mytoken
PostgreSQL
For scale-out, HA, and shared metadata across multiple Waxum nodes:
sudo -u postgres createdb waxum
./waxum --token mytoken --db postgres://postgres:password@localhost:5432/waxum
MySQL
Same story, MySQL flavour:
mysql -u root -p -e "CREATE DATABASE waxum;"
./waxum --token mytoken --db mysql://root:password@localhost:3306/waxum
Every backend runs the same idempotent CREATE TABLE IF NOT EXISTS +
ALTER TABLE ADD COLUMN IF NOT EXISTS sequence at startup — no manual
migrations, no docker compose up bootstrapping, no schema drift.
:::info Legacy Postgres env vars
If DATABASE_URL is unset and you set POSTGRES_* or MYSQL_* env
vars, Waxum picks those up as fallback. They still work; prefer the
DATABASE_URL form for new deployments.
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your-password
POSTGRES_DB=waxum
:::
Environment Variables
Database
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | (none — falls back to sqlite://./waxum.db) | Database connection URL (postgres://, mysql://, or sqlite://) |
Authentication
| Variable | Default | Description |
|---|---|---|
SUPERADMIN_TOKEN | (auto-generated JWT) | API access token (any string) |
JWT_SECRET | (random) | JWT signing secret (for auto-generated tokens) |
Server
| Variable | Default | Description |
|---|---|---|
PORT | 3451 | Server port |
WHATSAPP_STORAGE_PATH | ./whatsapp_sessions | WhatsApp session storage path (SQLite files) |
RUST_LOG | info | Log level (debug, info, warn, error) |
STALE_SOCKET_SECS | 150 | Rebuild a logged-in session whose socket has received nothing for this many seconds (a half-open connection, e.g. after the host's IP changed). 0 disables the check. |
LINK_PREVIEW_PAGE_TIMEOUT_MS | 5000 | How long waxum waits for a page when building a link preview, in milliseconds (1000 to 30000). An invalid value is ignored with a warning. |
WAXUM_RUN_AS | (unset; 1000:1000 in the Docker image) | uid:gid to drop to when started as root. Unset means waxum keeps the user it was started as. |
NATS JetStream (Optional)
| Variable | Default | Description |
|---|---|---|
NATS_URL | (none) | NATS server URL — required to enable NATS |
NATS_EVENTS_STREAM | WA_EVENTS | Stream name for incoming events |
NATS_SEND_STREAM | WA_SEND | Stream name for outbound commands |
NATS_EVENTS_MAX_AGE_DAYS | 7 | Max age for event messages (days) |
NATS_SEND_MAX_AGE_DAYS | 1 | Max age for outbound commands (days) |
NATS_TOKEN | (none) | Authentication token |
NATS_CREDS_FILE | (none) | Path to credentials file |
Sample .env Files
MySQL (Recommended)
DATABASE_URL=mysql://user:password@localhost:3306/wars
SUPERADMIN_TOKEN=mysecrettoken
RUST_LOG=waxum=info,tower_http=info
PostgreSQL with NATS
DATABASE_URL=postgres://user:password@localhost:5432/wagateway
SUPERADMIN_TOKEN=mysecrettoken
JWT_SECRET=your-jwt-signing-secret
WHATSAPP_STORAGE_PATH=./whatsapp_sessions
NATS_URL=nats://localhost:4222
RUST_LOG=waxum=info,tower_http=info
Verify Installation
# Health check
curl http://localhost:3451/health
# Should return: OK
# Authenticated request
curl -H "Authorization: Bearer YOUR_TOKEN" \
http://localhost:3451/api/v1/sessions
# Open Swagger UI in browser
open http://localhost:3451/swagger-ui