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 } endThe 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: postgresdocker compose up -dThe 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 asobiFor 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/gameNo 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 | shOn Windows (PowerShell), or via winget install widgrensit.asobi:
irm https://raw.githubusercontent.com/widgrensit/asobi-cli/main/install.ps1 | iexThen, in any shell:
asobi login
asobi use <your-game>
asobi deploy prod luaasobi 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?
- Core concepts - matches, worlds, zones, voting, phases.
- Lua API reference - every
game.*function. - Self-host - run Asobi on your own infrastructure for real.
- Cloud - environments, deploys, and billing on managed Asobi.