Docs / Self-host

Self-host Asobi

Asobi is fully open-source and designed to be run on your own infrastructure. This guide walks through three deployment targets: Docker Compose (dev), a single server (production), and Kubernetes (scale).

Prefer managed? Asobi Cloud is coming — fully managed game servers, EU-sovereign hosting, per-environment scaling. Join the waitlist at asobi.dev/cloud.

Deployment targets

Docker Compose

The quickest way to run a real Asobi stack. Save this as docker-compose.yml:

services:
  postgres:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: asobi
    volumes: [pgdata:/var/lib/postgresql/data]
    ports: ["5432:5432"]

  asobi:
    image: ghcr.io/widgrensit/asobi_lua:latest
    depends_on: [postgres]
    environment:
      ASOBI_DB_HOST: postgres
      ASOBI_DB_NAME: asobi
      ASOBI_DB_USER: postgres
      ASOBI_DB_PASSWORD: postgres
      ERLANG_COOKIE: ${ERLANG_COOKIE}
    ports: ["8080:8080"]
    volumes:
      - ./game:/app/game:ro

volumes:
  pgdata:

Generate a cookie, then boot the stack:

export ERLANG_COOKIE=$(openssl rand -hex 32)
docker compose up -d

Asobi is now running on http://localhost:8080. Deploy your game with asobi deploy ./game and you're live.

Single VPS

For production on a single machine — a Hetzner AX41 (€40/mo) comfortably handles hundreds of concurrent players. Build a release from the Asobi source:

git clone https://github.com/widgrensit/asobi
cd asobi
rebar3 as prod release

# The release is at _build/prod/rel/asobi/
# Copy it to the server, run with:
bin/asobi daemon

Configure the release via config/prod_sys.config.src — environment variables, database credentials, and TLS settings are all there. See the config directory for full examples.

Systemd unit

# /etc/systemd/system/asobi.service
[Unit]
Description=Asobi game backend
After=network.target postgresql.service

[Service]
Type=notify
User=asobi
Environment=ERLANG_COOKIE=generate-a-real-one
Environment=HOME=/var/lib/asobi
ExecStart=/opt/asobi/bin/asobi foreground
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Reverse proxy (Caddy)

game.example.com {
    reverse_proxy localhost:8080
}

Caddy auto-issues TLS certs and handles WebSocket upgrades without config. Nginx works equally well — make sure proxy_set_header Upgrade $http_upgrade is set.

Kubernetes

For multi-tenant or multi-game deployments, run one pod per game-env. Our reference infra repo (widgrensit/asobi-infra) ships a Helm stack for Scaleway Kapsule: cert-manager, Prometheus, Loki, Velero, and CloudNativePG.

A minimal Deployment looks like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: asobi-my-game-live
  namespace: tenant-widgrensit
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: engine
          image: ghcr.io/widgrensit/asobi_lua:latest
          env:
            - name: ASOBI_DB_HOST
              valueFrom:
                secretKeyRef: { name: pg-credentials, key: host }
            - name: ERLANG_COOKIE
              valueFrom:
                secretKeyRef: { name: erlang-cookie, key: cookie }
          ports:
            - containerPort: 8080
          resources:
            requests: { cpu: 100m, memory: 256Mi }
            limits:   { cpu: 1000m, memory: 1Gi }

See the asobi-infra repo for the full cluster setup, NetworkPolicies, and tenant isolation patterns.

Backups

  • Postgres: everything Asobi persists lives in Postgres. Back up continuously (WAL shipping to S3-compatible storage) or at least nightly.
  • Lua bundles: your game source lives in git. That's the backup.
  • Zone snapshots: persistent worlds snapshot their state to Postgres every tick interval. They're restored automatically on startup.

Rate limits

Asobi ships with three Seki limiter groups out of the box. Defaults are tuned for a single internet-facing node:

 Path prefix       | Limiter             | Default (req/sec/IP)
-------------------|---------------------|-----------------------
 /api/v1/auth/*    | asobi_auth_limiter  | 5
 /api/v1/iap/*     | asobi_iap_limiter   | 10
 everything else   | asobi_api_limiter   | 300

Override per environment via the asobi, rate_limits env — see the Configuration page.

Operator hygiene

Recommended deploy posture beyond the obvious "rotate the cookie, terminate TLS at the proxy":

  • Mount the game dir read-only. The runtime supports hot reload via filesystem polls; it does not need write access to /app/game. docker run -v ./game:/app/game:ro --read-only --tmpfs /tmp.
  • Bind distributed Erlang to localhost. For single-node deploys, uncomment the -kernel inet_dist_use_interface line in vm.args.src so EPMD and the dist port range are unreachable from the public internet.
  • Use TLS for distribution if you cluster. Set an explicit dist port range plus -proto_dist inet_tls; the cookie alone is not sufficient on a public network.
  • Ship the Apple root CA in a known location. Default is priv/apple_root_ca.pem inside the asobi app; override via apple_root_ca_path if you mount it elsewhere.
  • Run the engine as non-root. The shipped Dockerfile already does this; bare metal should too.
  • Enable Postgres SSL for cross-host connections. Required for any deploy that does not co-locate Postgres on the same machine.
  • Treat deploy keys like passwords. Rotate on employee offboarding; never commit them to your game repo.

For the full threat model and the audit findings these recommendations come from, see the Security section.

Upgrades

Asobi releases are hot-code-loadable on the BEAM. For minor version bumps, deploy the new release and existing matches finish on the old code; new matches use the new code. No downtime. For major version bumps (breaking schema changes), run migrations via rebar3 kura migrate in a maintenance window.

Where next?