Self-Hosting
This guide walks you through running the Zirzir server on your own infrastructure. The server gives you a dashboard, webhook infrastructure, multi-project support, and a unified API your SDKs talk to.
Quick Start (Docker)
Section titled “Quick Start (Docker)”The fastest path to a running server:
docker run -d \ --name zirzir \ -p 8080:8080 \ -v zirzir-data:/app/data \ -e ZIRZIR_SECURITY_ADMIN_API_KEY=$(openssl rand -hex 32) \ ghcr.io/recite-labs/zirzir/server:latestOpen http://localhost:8080 to access the dashboard.
Docker Compose
Section titled “Docker Compose”For a more manageable setup, create a docker-compose.yml:
services: zirzir: image: ghcr.io/recite-labs/zirzir/server:latest ports: - "8080:8080" volumes: - zirzir-data:/app/data - ./plugins:/app/plugins:ro environment: ZIRZIR_LOG_LEVEL: info ZIRZIR_SECURITY_ADMIN_API_KEY: ${ZIRZIR_ADMIN_API_KEY} ZIRZIR_SECURITY_ENCRYPTION_KEY: ${ZIRZIR_ENCRYPTION_KEY} ZIRZIR_SERVER_ALLOW_ORIGINS: "https://yoursite.com" restart: unless-stopped healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"] interval: 30s timeout: 5s retries: 3
volumes: zirzir-data:# Generate secretsecho "ZIRZIR_ADMIN_API_KEY=$(openssl rand -hex 32)" >> .envecho "ZIRZIR_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env
# Startdocker compose up -dBuild from Source
Section titled “Build from Source”Requirements: Go 1.24+, Node.js 20+
git clone https://github.com/recite-labs/zirzir.gitcd zirzir/server
# Install dependencies and build (includes React dashboard)make build
# Run./bin/zirzirFor development with hot reload:
# Install air (hot reload tool)go install github.com/air-verse/air@latest
# Run in dev modemake devConfiguration
Section titled “Configuration”Zirzir loads configuration from three sources (in order of priority):
- Environment variables — prefixed with
ZIRZIR_(e.g.ZIRZIR_SERVER_PORT) - Config file —
config.yamlin the working directory,/etc/zirzir/, or~/.zirzir/ - Defaults
Config File
Section titled “Config File”Copy the example and edit:
cp config.example.yaml config.yamlserver: port: 8080 host: 0.0.0.0 allow_origins: - "https://yoursite.com"
database: path: zirzir.db
log: level: info # debug, info, warn, error
plugins: directory: plugins
security: encryption_key: "" # openssl rand -hex 32 admin_api_key: "" # openssl rand -hex 32Environment Variables
Section titled “Environment Variables”All config values can be set via environment variables using the ZIRZIR_ prefix with underscores replacing dots:
| Variable | Config equivalent | Default | Description |
|---|---|---|---|
ZIRZIR_SERVER_PORT | server.port | 8080 | HTTP port |
ZIRZIR_SERVER_HOST | server.host | 0.0.0.0 | Bind address |
ZIRZIR_SERVER_ALLOW_ORIGINS | server.allow_origins | * | CORS origins |
ZIRZIR_DATABASE_PATH | database.path | zirzir.db | SQLite file path |
ZIRZIR_LOG_LEVEL | log.level | info | Log verbosity |
ZIRZIR_PLUGINS_DIRECTORY | plugins.directory | plugins | Plugin binary directory |
ZIRZIR_SECURITY_ENCRYPTION_KEY | security.encryption_key | — | AES key for credential encryption |
ZIRZIR_SECURITY_ADMIN_API_KEY | security.admin_api_key | — | Key for /admin endpoints |
Database
Section titled “Database”Zirzir uses SQLite — no external database server needed. Migrations run automatically on startup.
- Default path:
zirzir.db(current directory) - Docker path:
/app/data/zirzir.db(inside the named volume)
To run migrations manually:
# Requires goose: go install github.com/pressly/goose/v3/cmd/goose@latestgoose -dir migrations sqlite3 zirzir.db upBackups
Section titled “Backups”SQLite databases are single files. Back up by copying:
# While the server is running (SQLite handles concurrent reads)sqlite3 zirzir.db ".backup /backups/zirzir-$(date +%Y%m%d).db"First-Time Setup
Section titled “First-Time Setup”After the server starts:
1. Open the Dashboard
Section titled “1. Open the Dashboard”Navigate to http://localhost:8080. You’ll be prompted to create an admin account.
2. Create a Project
Section titled “2. Create a Project”A project is an isolated workspace with its own API keys and provider configurations. Create one from the dashboard or via the admin API:
ADMIN_KEY="your-admin-api-key"
# Create a merchantcurl -X POST http://localhost:8080/admin/merchants \ -H "Authorization: Bearer $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "My App", "webhook_url": "https://yoursite.com/webhooks/zirzir"}'3. Configure a Provider
Section titled “3. Configure a Provider”Add Chapa credentials to your project — from the dashboard or via API:
MERCHANT_ID="merchant-id-from-step-2"
curl -X POST http://localhost:8080/admin/merchants/$MERCHANT_ID/providers \ -H "Authorization: Bearer $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "chapa", "environment": "test", "config": { "secret_key": "CHASECK_TEST-..." } }'4. Generate an API Key
Section titled “4. Generate an API Key”curl -X POST http://localhost:8080/admin/merchants/$MERCHANT_ID/api-keys \ -H "Authorization: Bearer $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{"environment": "test", "label": "Backend"}'The response includes your zz_test_... API key. Use it in your SDK:
const zirzir = new Zirzir({ baseUrl: 'http://localhost:8080', apiKey: 'zz_test_...',})Plugins
Section titled “Plugins”External payment providers are loaded as plugins from the plugins/ directory.
# Build the example M-Pesa pluginmake plugin-example
# The binary is placed in plugins/ls plugins/# mpesaPlugins are discovered automatically on startup. See Adding Providers for how to build your own.
Production Checklist
Section titled “Production Checklist”Before going live:
- Set
admin_api_key— generate withopenssl rand -hex 32 - Set
encryption_key— encrypts stored provider credentials - Restrict CORS — set
allow_originsto your actual domains - Use a volume for the SQLite database (Docker: named volume, bare metal: persistent disk)
- Set up backups — scheduled SQLite backup to object storage
- Put behind a reverse proxy — Nginx or Caddy for TLS termination
- Set
log.leveltowarnin production (reduce noise) - Monitor
/health— wire it to your uptime checker
Reverse Proxy (Caddy)
Section titled “Reverse Proxy (Caddy)”pay.yourcompany.com { reverse_proxy localhost:8080}Reverse Proxy (Nginx)
Section titled “Reverse Proxy (Nginx)”server { listen 443 ssl; server_name pay.yourcompany.com;
ssl_certificate /etc/letsencrypt/live/pay.yourcompany.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/pay.yourcompany.com/privkey.pem;
location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}Next Steps
Section titled “Next Steps”- Server API Reference — all endpoints, request/response schemas
- Adding Providers — build a plugin for a new payment gateway
- Configuration — SDK configuration for server mode