Two authoritative sources:
state-server/docs/DEPLOYMENT_GUIDE_FDB.md — the canonical deployment / migration guide. Describes a VM-based topology with systemd, Nginx, and an FDB cluster. This is the documented topology; this wiki page summarises it.gitlab.avvyland.com/avvy/argocd — the actual production topology. State-server runs on Kubernetes (Hetzner Cloud) via ArgoCD-managed Kustomizations, not on bare-VM systemd. See Deployed Services.The two are not interchangeable: the guide reads as a portable handbook (useful for spinning up a parallel environment), while ArgoCD is the live source of truth for what's running today. Where they disagree, ArgoCD wins.
Ubuntu 22.04 LTS or Debian 11+ (64-bit). Go 1.21+ for builds. FoundationDB 7.1.0+ (7.3.x recommended). Ansible 2.14+ for automation.
Single node. State-server + FDB on one host. Suitable for development, staging, and small deployments (< ~1k concurrent users). Single point of failure.
3-node cluster (recommended). Three FDB nodes in cluster configuration; two or more state-server instances behind a load balancer. Suitable for production with 1k–10k users. HA with automatic failover.
Multi-region. Five-to-nine FDB nodes across regions; multiple state-server instances per region; global LB with geo-routing. For large or globally distributed deployments.
Clients (HTTPS / WSS)
│
▼
Nginx (TLS termination, WS upgrade, load balance)
│
▼ HTTP to private network
state-server pool (Go binary, systemd unit)
│
▼ FDB client protocol (port 4500)
FoundationDB cluster (coordinators + storage nodes)
Worker tier (avvy-worker) is separate; it reads from Redis and POSTs HMAC-signed callbacks back to state-server.
| Port | Protocol | Purpose | Public |
|---|---|---|---|
| 8443 | HTTPS / WSS | REST + WebSocket | yes |
| 4500 | FDB | FoundationDB client | no — internal only |
| 4501–4510 | FDB | FDB cluster comms | no — internal only |
Firewall to allow 8443/tcp publicly and 4500–4510/tcp only from your private subnet. Restrict SSH (22/tcp) to bastions.
cd state-server
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o state-server
tar czf state-server-$(date +%Y%m%d).tar.gz state-server
Copy the tarball to /opt/state-server/ on each production host.
/etc/systemd/system/state-server.service:
[Unit]
Description=State Server
After=network.target foundationdb.service
Requires=foundationdb.service
[Service]
Type=simple
User=stateserver
Group=stateserver
WorkingDirectory=/opt/state-server
EnvironmentFile=/opt/state-server/.env
ExecStart=/opt/state-server/state-server
Restart=always
RestartSec=5s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/state-server/logs
LimitNOFILE=65536
LimitNPROC=4096
[Install]
WantedBy=multi-user.target
Bootstrap:
sudo useradd -r -s /bin/false stateserver
sudo mkdir -p /opt/state-server/{logs,certs}
sudo chown -R stateserver:stateserver /opt/state-server
sudo systemctl daemon-reload
sudo systemctl enable --now state-server
Production. Let's Encrypt via certbot --standalone. Auto-renew with a cron entry that runs certbot renew --post-hook "systemctl restart state-server".
Local development. A self-signed certificate is acceptable; clients must trust the local CA (or use NODE_EXTRA_CA_CERTS for Node SDK users).
The .env references CERT_FILE and KEY_FILE. Certificates live under /opt/state-server/certs/, owned by the stateserver user.
Production places Nginx in front of state-server for TLS termination, WebSocket upgrade, and load balancing. A reference nginx.conf lives in the state-server repo and the deployment guide; key points:
upstream block with least_conn, health-check, and keepalive 32.map ($http_upgrade → $connection_upgrade).proxy_read_timeout on the /ws location for WebSocket sessions.The state-server reads its config from .env (loaded by the systemd EnvironmentFile= directive). Required keys include — at minimum — the JWT secret, persistence target (FDB cluster file path or MongoDB URI), TLS paths, and the worker callback secret.
Do NOT mirror specific environment values into the wiki. Production secrets live in your deployment's secret manager (Vault, sealed-secrets, ansible-vault, or EnvironmentFile= with 0640 perms). The state-server repo's .env.example lists every required key.
NoNewPrivileges, PrivateTmp, ProtectSystem=strict, ProtectHome).callback_secret rotated together on state-server and worker fleets.listen-address set to private IP, not 0.0.0.0).Available, Healthy on every node./health returns 200 from each instance./api/ and /ws correctly; WebSocket upgrade works end-to-end.Persistence layer for MongoDB / FDB setup detail.
Redis task queue for asynq config.
Deployed services for the actual production topology (Kubernetes via ArgoCD).
gitlab.avvyland.com/avvy/state-server — docs/DEPLOYMENT_GUIDE_FDB.md, ansible/, external/mongo/docker-compose.yaml.