Installation & deployment
moth is a single static binary with a single data directory. A deployment is: the binary, a config file (optional), and something that gives it TLS.
Get the binary
Section titled “Get the binary”Coming in v1.0: signed release binaries for darwin/linux/windows (amd64/arm64), a Homebrew tap, and a ~15 MB scratch-based Docker image.
Today, build from source with Go 1.25+:
git clone https://github.com/aloisdeniel/moth.gitcd mothmake build # → bin/moth (dev build)make build VERSION=… # release-style buildThe binary is fully self-contained — the admin SPA, migrations, email
templates, and the Flutter SDK tarball are embedded. Copy bin/moth to
the target machine and you’re done installing.
Configuration
Section titled “Configuration”Resolution order, highest first: command-line flags → MOTH_*
environment variables → config file (moth.toml, loaded from the
working directory when present, or --config <path> / MOTH_CONFIG) →
built-in defaults.
| Setting | Flag | Env | moth.toml |
Default |
|---|---|---|---|---|
| Listen address | --addr |
MOTH_ADDR |
addr |
:8080 |
| Data directory | --data-dir |
MOTH_DATA_DIR |
data_dir |
./data |
| Public base URL | --base-url |
MOTH_BASE_URL |
base_url |
http://localhost:8080 |
| SMTP host | — | MOTH_SMTP_HOST |
[smtp] host |
(empty → console transport) |
| SMTP port | — | MOTH_SMTP_PORT |
[smtp] port |
587 |
| SMTP username | — | MOTH_SMTP_USERNAME |
[smtp] username |
— |
| SMTP password | — | MOTH_SMTP_PASSWORD |
[smtp] password |
— |
| SMTP from | — | MOTH_SMTP_FROM |
[smtp] from |
— |
Log format (text/json) |
--log-format |
MOTH_LOG_FORMAT |
log_format |
text |
| gRPC reflection (release builds) | --reflection |
MOTH_REFLECTION |
reflection |
false (dev builds: on) |
| Scheduled backup dir | --backup-dir |
MOTH_BACKUP_DIR |
backup_dir |
(empty → disabled) |
| Scheduled backup interval | --backup-interval |
MOTH_BACKUP_INTERVAL |
backup_interval |
24h |
| Trusted proxy CIDRs/IPs | --trusted-proxies |
MOTH_TRUSTED_PROXIES |
trusted_proxies |
(none) |
| ACME hostname(s) | --acme-domain |
MOTH_ACME_DOMAINS |
acme_domains |
(empty → disabled) |
| Rate limit — per IP / account / project per minute | — | MOTH_RATELIMIT_IP_PER_MINUTE, …_ACCOUNT_…, …_PROJECT_… |
[ratelimit] |
60 / 10 / 600 |
base_url matters: it is baked into email links, the token iss claim,
JWKS URLs, and OAuth redirect URIs, and it decides whether admin session
cookies are marked Secure. Set it to the exact public URL
(https://auth.example.com) before creating projects.
A production moth.toml:
addr = ":8080"data_dir = "/var/lib/moth/data"base_url = "https://auth.example.com"
[smtp]host = "smtp.eu.example.com"port = 587username = "moth"password = "…"from = "auth@example.com"SMTP can also be configured at runtime — stored in the database, taking
precedence over the file — via the admin console or
moth instance smtp set, and
tested with moth instance smtp test --to you@example.com. With no SMTP
configured anywhere, emails are printed to the server log (fine for dev,
a visible warning in the admin for production).
The data directory
Section titled “The data directory”Everything stateful lives under one directory:
data/ moth.db SQLite database (users, projects, tokens, events) keys/ master.key + nothing else you should touch uploads/ project logo assetskeys/master.keyencrypts project signing keys and provider secrets at rest. Alternatively supply it as theMOTH_MASTER_KEYenvironment variable and keep no key file on disk. Losing the master key means losing every project’s signing keys — see Backups.- Back up the whole directory, not just the database.
systemd
Section titled “systemd”[Unit]Description=moth authentication serverAfter=network-online.targetWants=network-online.target
[Service]User=mothGroup=mothExecStart=/usr/local/bin/moth serve --config /etc/moth/moth.tomlRestart=on-failureRestartSec=2
# HardeningNoNewPrivileges=trueProtectSystem=strictProtectHome=truePrivateTmp=trueReadWritePaths=/var/lib/moth
[Install]WantedBy=multi-user.targetsudo useradd --system --home /var/lib/moth --shell /usr/sbin/nologin mothsudo mkdir -p /var/lib/moth && sudo chown moth:moth /var/lib/mothsudo systemctl enable --now mothCreate the first admin on the host (it talks to the same database):
sudo -u moth moth admin create --config /etc/moth/moth.toml --email you@example.com— or just open https://auth.example.com/admin and use the first-run
setup screen.
Docker
Section titled “Docker”Coming in v1.0: an official scratch-based image. Until then a build-your-own image is a few lines:
FROM golang:1.25 AS buildWORKDIR /srcRUN git clone --depth 1 https://github.com/aloisdeniel/moth.git . \ && CGO_ENABLED=0 make build
FROM gcr.io/distroless/staticCOPY --from=build /src/bin/moth /mothVOLUME /dataEXPOSE 8080ENTRYPOINT ["/moth", "serve", "--data-dir", "/data"]docker build -t moth .docker run -d -p 8080:8080 -v moth-data:/data \ -e MOTH_BASE_URL=https://auth.example.com \ -e MOTH_MASTER_KEY=… \ mothmoth is CGO-free (pure-Go SQLite driver), so the static/distroless base
works without glibc. Mount /data — it is the entire state of the
instance.
Docker Compose
Section titled “Docker Compose”A minimal stack with a named volume for the data directory and Caddy in front for automatic TLS:
services: moth: image: ghcr.io/aloisdeniel/moth:latest # or `build: .` restart: unless-stopped environment: MOTH_BASE_URL: https://auth.example.com MOTH_MASTER_KEY: ${MOTH_MASTER_KEY} # from a .env file / secret volumes: - moth-data:/data expose: - "8080"
caddy: image: caddy:2 restart: unless-stopped ports: ["80:80", "443:443"] command: caddy reverse-proxy --from auth.example.com --to h2c://moth:8080 volumes: - caddy-data:/data
volumes: moth-data: caddy-data:caddy:2’s reverse-proxy --to h2c://… keeps the upstream on HTTP/2 so
native gRPC from the mobile SDK works end to end. Generate a master key once
(openssl rand -base64 32) and keep it in the .env — losing it means
losing every project’s signing keys.
Reverse proxy & TLS
Section titled “Reverse proxy & TLS”moth listens on plain HTTP and speaks HTTP/2 without TLS (h2c) on the same port, so a proxy can sit in front of everything. The one requirement that trips people up:
Caddy (recommended)
Section titled “Caddy (recommended)”Caddy terminates TLS with automatic Let’s Encrypt certificates and can proxy h2c upstream — the entire config:
auth.example.com { reverse_proxy h2c://127.0.0.1:8080}The h2c:// scheme is the important part: it keeps proxy → moth on
HTTP/2 so native gRPC round-trips.
Traefik
Section titled “Traefik”# dynamic configurationhttp: routers: moth: rule: Host(`auth.example.com`) service: moth tls: certResolver: letsencrypt services: moth: loadBalancer: servers: - url: h2c://127.0.0.1:8080nginx cannot proxy generic HTTP/2 upstream: proxy_pass is HTTP/1.1-only
and grpc_pass handles only gRPC framing — but every protocol moth
serves shares the same /moth.* paths, so a clean path-based split is
not possible. proxy_pass http://127.0.0.1:8080 keeps everything working
except native gRPC from the mobile SDK. A hardened, tested nginx recipe
(routing on Content-Type: application/grpc) ships with the v1.0
deployment guide; until then, prefer Caddy or Traefik in front of moth.
Built-in ACME
Section titled “Built-in ACME”moth serve --acme-domain auth.example.com obtains a Let’s Encrypt
certificate and serves HTTPS directly from the binary on a bare VPS, no
proxy at all — it listens on :443 with the http-01 challenge on :80
(pass a comma-separated list for multiple hostnames). Behind a proxy
instead, set --trusted-proxies to the proxy’s CIDRs/IPs so per-IP rate
limits read the real client address from X-Forwarded-For.
Health & diagnostics
Section titled “Health & diagnostics”GET /healthz— plain-HTTP liveness (also: the standard gRPC health service).moth doctor— the support checklist for “login stopped working”: base-URL/TLS sanity, health and pub endpoints, SMTP (with a real test send), and per-project JWKS + provider verification against Google’s and Apple’s live endpoints.GET /metrics— a Prometheus endpoint (requires the admin credential).- Structured logs —
--log-format jsonswitches the slog handler from human-readable text to JSON for log shippers. - An append-only audit log records every admin action; browse it in
the console or via
moth.admin.v1. - gRPC server reflection is enabled only in dev builds — release builds
don’t advertise their surface unless you pass
--reflection.