How to Self-Host a WhatsApp API with Docker in 5 Minutes
A practical, copy-paste tutorial to self-host a WhatsApp API with Docker — clone, set secrets, docker compose up, and send your first message in minutes.
How to Self-Host a WhatsApp API with Docker in 5 Minutes
Most WhatsApp API providers want you on their cloud, paying per conversation, with your message data sitting on their servers. You don't have to accept that. WaSphere is a self-hosted WhatsApp automation platform you run on your own box with a single Docker Compose stack — four services, one .env, and a browser tab.
This is the fast path: clone, set your secrets, bring the stack up, and send a message. No reverse proxy and no Redis to wrangle — the default stack is deliberately small.
What you're deploying
The stack is four containers defined in one root docker-compose.yml:
- postgres — stores accounts, sessions, message history, API keys, webhooks, and the audit log.
- wa-server (port 3001) — manages WhatsApp connections via Baileys. Each session is its own connection.
- dashboard-api (port 3000) — the REST API your apps call. Runs database migrations automatically on startup.
- dashboard-ui (port 3004) — the web dashboard you open in a browser.
There is no bundled Traefik and no Redis. In production you front it with your own nginx, Caddy, or Traefik for TLS — but for the first run, localhost is all you need.
Requirements
- Docker Engine 24+ and Docker Compose v2.20+
- 1 vCPU and 1 GB RAM is enough to start; budget ~100–150 MB extra per active WhatsApp session
Verify Docker is ready:
docker compose version
# Docker Compose version v2.27.0
Step 1 — Clone the repository
git clone https://github.com/wasphere/wasphere.git
cd wasphere
Step 2 — Generate your secrets
Copy the example env file:
cp .env.example .env
Every secret should be unique and random. Generate them all at once:
for k in JWT_SECRET ENCRYPTION_KEY WA_TOKEN WEBHOOK_SIGNING_SECRET INTERNAL_WEBHOOK_SECRET POSTGRES_PASSWORD; do
echo "$k=$(openssl rand -hex 32)"
done
Paste the output into .env, then set the remaining values:
POSTGRES_USER=wasphere
POSTGRES_DB=wasphere
DASHBOARD_UI_URL=http://localhost:3004
A quick reference for what each secret does:
| Variable | Purpose |
|---|---|
JWT_SECRET | Signs dashboard auth tokens |
ENCRYPTION_KEY | Encrypts stored credentials at rest |
WA_TOKEN | Authenticates Dashboard API → WA Server calls |
WEBHOOK_SIGNING_SECRET | Default HMAC-SHA256 webhook signing key |
INTERNAL_WEBHOOK_SECRET | WA Server → Dashboard API internal events |
Never reuse one value for two secrets — each one isolates a different failure mode.
Step 3 — Bring the stack up
docker compose up -d
The first run pulls images and builds (1–3 minutes). Migrations run automatically when the Dashboard API starts — there's no manual Prisma step. Watch it come up:
docker compose logs -f
Confirm all four services are running:
docker compose ps
Step 4 — Register the admin account
Open http://localhost:3004. On first launch the dashboard shows a registration screen — the first account you create becomes the admin. Sign in, then:
- Go to Sessions → New Session and give it a name.
- Scan the QR code from WhatsApp → Linked Devices → Link a Device.
- The status flips to
connected.
Use a dedicated number for automation, not your personal account.
Step 5 — Create a scoped API key and send a message
Under API Keys, create a key with the messages:send permission. You can scope it to a single session for tighter isolation. Keys are prefixed wsk_live_... and shown only once.
Now send your first message. The session lives in the URL; the body just carries the recipient and text:
curl -X POST \
"https://api.your-domain.com/workspaces/{workspaceId}/proxy/api/sessions/{sessionId}/messages/text" \
-H "Authorization: Bearer wsk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"to": "447700900123",
"text": "Hello from my self-hosted WhatsApp API!"
}'
The to field is the recipient in international format without the +. Locally you can call the API directly on http://localhost:3000; once you put a proxy in front, use your public api.your-domain.com host.
Going to production
For a public deployment, terminate TLS with your own proxy and forward to ports 3004 (UI) and 3000 (API). A minimal Caddy config:
app.your-domain.com {
reverse_proxy localhost:3004
}
api.your-domain.com {
reverse_proxy localhost:3000
}
Updating later is two commands — docker compose pull && docker compose up -d — and migrations apply themselves on restart.
Why self-host
You own the database. There are no per-message fees. Webhooks are HMAC-SHA256 signed, API keys carry 12 scoped permissions plus optional session scoping, and every action lands in an audit log. The whole thing is MIT-licensed, so you can read, fork, and run it however you like.
Ready to connect a session and send for real? Follow the Quick Start guide.