Skip to content

Deployment

This guide covers running the x402 Facilitator in production - from Docker Compose on a single VM to a container-orchestrated cloud environment.


Quick Docker Compose

The repository ships with a docker-compose.yml that starts the facilitator alongside PostgreSQL and Redis.

bash
# 1. Clone
git clone https://github.com/vitwit/facilitator-server.git
cd facilitator-server

# 2. Configure
cp config.example.yaml config.yaml
# Edit config.yaml - set secrets, RPC URLs, private keys

# 3. Build and run
docker-compose up -d

# 4. Check logs
docker-compose logs -f facilitator

The server listens on http://localhost:8080 by default.


Building the Docker Image

bash
docker build -t x402-facilitator:latest .

Running the container

bash
docker run -d \
  --name x402-facilitator \
  -p 8080:8080 \
  -v /path/to/config.yaml:/app/config.yaml:ro \
  x402-facilitator:latest

Mount config.yaml as read-only. Never bake secrets into the image.


Production Checklist

Security

  • [ ] Set all JWT secrets to ≥ 32 random bytes (openssl rand -hex 32).
  • [ ] Set a strong security.client_secret.
  • [ ] Set a strong, unique admin.token and store it in a secrets manager.
  • [ ] Use ssl_mode: "verify-full" for PostgreSQL in production.
  • [ ] Never commit config.yaml to version control - add it to .gitignore.
  • [ ] Set x402.allow_missing_client_auth: false.
  • [ ] Set server.environment: "production" (disables Gin debug output).

Networking

  • [ ] Put the facilitator behind a reverse proxy (Nginx, Caddy, or a cloud load balancer).
  • [ ] Enforce HTTPS - terminate TLS at the proxy.
  • [ ] Restrict /admin routes to internal networks only (via proxy ACLs or firewall rules).
  • [ ] Enable rate limiting (rate_limit.enabled: true).

Database

  • [ ] Use a managed PostgreSQL service (e.g. AWS RDS, GCP Cloud SQL, Supabase) for production.
  • [ ] Enable automated backups.
  • [ ] Run migrations before deploying a new version (see Migrations).

Observability

  • [ ] Configure structured JSON logging (logging.format: "json").
  • [ ] Ship logs to a centralised log aggregator (Loki, Datadog, CloudWatch).
  • [ ] Set up uptime monitoring on GET /health.

Nginx Reverse Proxy

nginx
server {
    listen 443 ssl http2;
    server_name facilitator.yourdomain.com;

    ssl_certificate     /etc/ssl/certs/facilitator.crt;
    ssl_certificate_key /etc/ssl/private/facilitator.key;

    # Rate limiting
    limit_req_zone $binary_remote_addr zone=facilitator:10m rate=10r/s;

    location / {
        limit_req zone=facilitator burst=20 nodelay;

        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;
        proxy_read_timeout 60s;
    }

    # Block admin from the public internet
    location /admin {
        deny all;
    }
}

Database Migrations

Migrations are stored in internal/database/migrations/ and run automatically on startup via golang-migrate. If you prefer to manage migrations manually:

bash
# Install migrate CLI
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

# Run migrations
migrate \
  -path internal/database/migrations \
  -database "postgres://user:pass@host:5432/dbname?sslmode=disable" \
  up

To roll back:

bash
migrate \
  -path internal/database/migrations \
  -database "postgres://user:pass@host:5432/dbname?sslmode=disable" \
  down 1

Kubernetes

A minimal Kubernetes deployment:

yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: x402-facilitator
spec:
  replicas: 2
  selector:
    matchLabels:
      app: x402-facilitator
  template:
    metadata:
      labels:
        app: x402-facilitator
    spec:
      containers:
        - name: facilitator
          image: x402-facilitator:latest
          ports:
            - containerPort: 8080
          volumeMounts:
            - name: config
              mountPath: /app/config.yaml
              subPath: config.yaml
              readOnly: true
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
      volumes:
        - name: config
          secret:
            secretName: x402-facilitator-config
---
apiVersion: v1
kind: Service
metadata:
  name: x402-facilitator
spec:
  selector:
    app: x402-facilitator
  ports:
    - port: 80
      targetPort: 8080

Store config.yaml as a Kubernetes Secret:

bash
kubectl create secret generic x402-facilitator-config \
  --from-file=config.yaml=./config.yaml

Horizontal Scaling

The facilitator is stateless beyond the database and Redis. To scale horizontally:

  1. Run multiple instances behind a load balancer.
  2. Point all instances at the same PostgreSQL database.
  3. Point all instances at the same Redis instance (for distributed rate limiting - when implemented).
  4. Ensure all instances share the same JWT secrets so tokens issued by one instance are accepted by another.

Monitoring & Alerts

Health endpoint

Poll GET /health every 30 seconds. Alert if status is not "healthy" or the endpoint is unreachable.

Log-based alerts

Key log patterns to alert on:

PatternSeverityMeaning
SETTLEMENT_FAILEDCriticalOn-chain settlement is failing.
VERIFICATION_FAILEDWarningVerification errors (may be malformed client requests).
database: downCriticalPostgreSQL connectivity lost.
HTTP 5xx spikeWarningUnexpected server errors.

Database size

Monitor the x402_metrics and metrics tables - they grow with every transaction. Set up archival or partitioning if you expect high volume.


Upgrading

  1. Pull the new image / build from the updated source.
  2. Run database migrations first (see above).
  3. Perform a rolling restart to avoid downtime.
bash
# Docker Compose rolling update
docker-compose pull facilitator
docker-compose up -d --no-deps facilitator

Released under the MIT License.