Docs / Quick start

Quick start

Write a tiny game in Lua, run the Asobi server, and connect a client. About 10 minutes. You host the server yourself, or run it on managed Asobi (cloud) - the game and the client code are identical either way.

Asobi games are written in Lua. The engine is an Erlang/OTP application underneath. If you would rather embed it directly in your own OTP app instead of writing Lua, see the Erlang API reference - the rest of this page is the Lua path.

Prerequisites: Docker and a terminal. Nothing else - you do not need Erlang or rebar3 to write and run a Lua game.

Nothing to sign up for. Self-host runs entirely on your machine - no Asobi account, no API key, no credentials. About 10 minutes to a running game. Want us to host it instead? See the cloud quickstart.

1. Write the game

Your game is Lua that the server loads from a directory. Create game/match.lua. We will build a click counter: every player who sends a click input increments a shared counter, broadcast to everyone.

-- game/match.lua
match_size = 1
max_players = 1
strategy = "fill"

function init(config)
    return { hits = 0 }
end

function join(player_id, state)
    game.send(player_id, { kind = "welcome", msg = "hi " .. player_id })
    return state
end

function leave(_player_id, state) return state end

function handle_input(_player_id, input, state)
    if input.action == "click" then
        state.hits = state.hits + 1
        game.broadcast("update", { hits = state.hits })
    end
    return state
end

function tick(state) return state end
function get_state(_player_id, state) return { hits = state.hits } end

The match_size / max_players / strategy globals configure matchmaking; the functions are the match lifecycle. Every game.* call is documented in the Lua API reference.

2. Run the server

The server is the asobi_lua runtime image plus Postgres. It loads your Lua from /app/game, so mount the game/ directory you just created there. Save this as docker-compose.yml next to game/:

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: asobi
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5

  asobi:
    image: ghcr.io/widgrensit/asobi_lua:latest
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "8084:8084"
    volumes:
      - ./game:/app/game:ro
    environment:
      ASOBI_PORT: "8084"
      ASOBI_NODE_HOST: "127.0.0.1"
      ERLANG_COOKIE: dev_cookie
      ASOBI_DB_HOST: postgres
      ASOBI_DB_NAME: asobi
      ASOBI_DB_USER: postgres
      ASOBI_DB_PASSWORD: postgres
docker compose up -d

The server is now on http://localhost:8084 - the HTTP API and the WebSocket endpoint (/ws) share the port. Prefer a ready-made server to copy from? The sdk_demo_backend repo is exactly this compose with a sample game already in ./lua.

3. Connect a client

Players authenticate with a username and password; the server returns an access / refresh token pair. There is no API key on the client. Register one over REST:

curl -sX POST http://localhost:8084/api/v1/auth/register \
  -H 'content-type: application/json' \
  -d '{"username":"player1","password":"secret123"}'
# => {"player_id":"...","access_token":"...","refresh_token":"..."}

On Windows, use PowerShell (no curl or jq install needed):

Invoke-RestMethod -Uri http://localhost:8084/api/v1/auth/register `
  -Method Post -ContentType application/json `
  -Body '{"username":"player1","password":"secret123"}'

An SDK does this for you and attaches the token to every REST and WebSocket call, refreshing it automatically. For a raw test, connect with the access_token using wscat:

npm install -g wscat
wscat -c ws://localhost:8084/ws
> {"type":"session.connect","payload":{"token":"<access_token>"}}
> {"type":"matchmaker.add","payload":{"mode":"default"}}
# server replies with match.matched { match_id: "<id>" }
> {"type":"match.join","payload":{"match_id":"<id>"}}
> {"type":"match.input","payload":{"action":"click"}}

You will see {"type":"match.state","payload":{"hits":1}} - every click increments the counter. For a real client, use an SDK: Defold, Unity, Godot, Dart/Flutter.

4. Deploy changes

Deploying is shipping new Lua to the server. How you do it is the one thing that differs between hosting your own server and running on managed Asobi.

Self-hosted

Your game is the Lua in ./game, mounted at /app/game. Edit a file and restart the container to load it:

docker compose restart asobi

For production, bake the game into your own image instead of mounting it, and run that:

FROM ghcr.io/widgrensit/asobi_lua:latest
COPY game/ /app/game

No API key is involved anywhere: a self-hosted server authenticates players directly (step 3), and there is nothing to register with. You own the database, the TLS, and the restart.

Cloud (managed Asobi)

On console.asobi.dev you get a hosted environment with an endpoint URL and hot-reload deploys through the CLI - new Lua loads with no dropped connections.

Install the CLI. On Linux and macOS:

curl -fsSL https://raw.githubusercontent.com/widgrensit/asobi-cli/main/install.sh | sh

On Windows (PowerShell), or via winget install widgrensit.asobi:

irm https://raw.githubusercontent.com/widgrensit/asobi-cli/main/install.ps1 | iex

Then, in any shell:

asobi login
asobi use <your-game>
asobi deploy prod lua

asobi login approves the CLI over a browser device-code flow; asobi use selects your game; asobi deploy ships and hot-loads your Lua. The CLI signs in for you - you never handle a key. Point your client SDK at the environment's endpoint URL instead of localhost:8084.

That's it. You have a live Asobi server running a Lua game, with a client talking to it.

Where next?