Open-source game backend. Write it in Lua. Hot-reload without restart. Apache-2.
Docs • Live demo • Discord • Issues
Edit a Lua file. Save. Live match updates. No restart. Try it.
A batteries-included multiplayer game backend you write in Lua, packaged as a single Docker image. You get auth, matchmaking, rooms, leaderboards, economy, chat, tournaments, voting, parties, phases and seasons, reconnection, and WebSocket + REST out of the box — and SDKs for Godot, Defold, Unity, Unreal, JS/TS, and Flutter.
No Erlang knowledge required. Your match logic is a .lua file. Save it and
connected clients see the change — no restart, no redeploy.
-- lua/match.lua
match_size = 2
function init(config)
return { players = {}, tick_count = 0 }
end
function join(player_id, state)
state.players[player_id] = { x = 400, y = 300, hp = 100 }
return state
end
function handle_input(player_id, input, state)
local p = state.players[player_id]
if input.right then p.x = p.x + 5 end
if input.left then p.x = p.x - 5 end
if input.shoot then
game.broadcast("shot", { by = player_id, at = input.aim })
end
return state
end
function tick(state)
state.tick_count = state.tick_count + 1
return state
endThat's a full playable room. Save the file, the match reloads in place.
1. Create the project layout
mkdir my_game && cd my_game
mkdir -p lua
# paste the match.lua above into lua/match.lua2. Bring up Postgres + asobi_lua
# docker-compose.yml
services:
postgres:
image: postgres:17
environment: { POSTGRES_USER: postgres, POSTGRES_PASSWORD: postgres, POSTGRES_DB: my_game }
healthcheck: { test: ["CMD-SHELL", "pg_isready -U postgres"], interval: 5s }
asobi:
image: ghcr.io/widgrensit/asobi_lua:latest
depends_on: { postgres: { condition: service_healthy } }
ports: ["8080:8080"]
volumes: ["./lua:/app/game:ro"]
environment: { ASOBI_DB_HOST: postgres, ASOBI_DB_NAME: my_game }docker compose up -d3. Register a player and join a match
# Register
curl -s localhost:8080/api/v1/auth/register \
-H 'content-type: application/json' \
-d '{"username":"alice","password":"hunter2"}'
# → { "token": "eyJ...", "player_id": "01HX..." }
# Queue for matchmaking
curl -s localhost:8080/api/v1/matchmaker/tickets \
-H 'authorization: Bearer eyJ...' \
-d '{"mode":"default"}'
# → { "ticket_id": "...", "status": "searching" }Connect the WebSocket from any SDK below and the client is live. Edit
lua/match.lua, save, and the running match picks up the change — players
stay connected, state is preserved.
- ⚡ Hot-reload Lua — push a fix at 11pm, your live match keeps playing. No restart, no dropped sockets.
- 🎮 Every engine — first-class SDKs for Godot, Defold, Unity, Unreal, plus JS/TS and Flutter.
- 🧠 Batteries included — matchmaker (fill + skill), rooms, economy, inventory, leaderboards, tournaments, chat, social, notifications, IAP, voting with 4 methods (plurality, ranked, approval, weighted), phases, seasons, reconnection.
- 🗺️ Large-world ready — spatial zones, lazy zone loading, terrain chunks, adaptive tick rates. Single-node by design; shard at the app level.
- 🚀 83,000 msg/sec sustained at 3,500 concurrent WebSockets, 4.4ms p50 RTT — see benchmarks.
- 🛡️ Apache-2, self-host — use commercially, fork it, run it yourself. We will never relicense. Exit guaranteed →
- 🇪🇺 Made in the EU — GDPR-ready, NIS2-aware, no US cloud lock-in.
- 🔒 OTP fault tolerance — one match crashing never touches any other match. No GC pauses during gameplay.
| Engine | Install | Docs | Sample |
|---|---|---|---|
| Godot 4.x | widgrensit/asobi-godot | guide | asobi-godot-demo |
| Defold | widgrensit/asobi-defold | guide | asobi-defold-demo |
| Unity 2021.3+ | com.asobi.sdk (UPM via git URL) |
guide | asobi-unity-demo |
| Unreal 5 | widgrensit/asobi-unreal | guide | — |
| JS / TS | widgrensit/asobi-js | guide | — |
| Flutter | dart pub add asobi |
guide | asobi-flame-demo |
| Flame (Flutter) | widgrensit/flame_asobi | guide | asobi-flame-demo |
your Lua scripts (mounted at /app/game)
│
▼
asobi_lua ── Luerl VM + bridge modules + bot runtime
│
▼
asobi (library) ── OTP supervision, pg groups, rate limits, sessions
│
▼
Nova + Kura + PostgreSQL
Every match and world runs as its own BEAM process under a supervisor. Luerl executes your Lua inside the BEAM — no sub-process, no sandbox escape, no GC pauses. Hot reload swaps the Luerl module while match state stays in the process heap.
Note
asobi_lua is pre-1.0. The API is stabilising; expect minor breaking changes until 1.0. We ship in lockstep with the asobi library (Hex.pm) and version SDKs against server tags.
Run the image wherever you like — Hetzner, Scaleway, Fly, Clever, a Raspberry Pi, your laptop. The image is ~120MB, cold starts in <3s, and holds thousands of WebSockets on a single vCPU. Full deployment guide at asobi.dev/docs/deploy.
A managed cloud at asobi.dev is opening later in 2026 — same binary, flat per-container pricing, never CCU-based. Learn more →.
- from Hathora — rooms → matches, serverless processes → container. Hathora shuts down 2026-05-05.
- from PlayFab — Titles, CloudScript, Virtual Currency mapped.
- from Nakama self-host — keep your Lua runtime, lose CockroachDB.
- Lua Scripting Guide — callbacks, state, modules, voting, world mode
- Bot AI Guide — write bots that fill matches
- asobi engine docs — architecture, REST API, WebSocket protocol, benchmarks
- 💬 Discord — chat with the team and other devs
- 🗣️ GitHub Discussions — Q&A, show-and-tell, RFCs
- 🐛 Issues — bug reports and feature requests
- 📦 Releases — changelog and release notes
If you're already writing Erlang/OTP and want Lua scripting as a dep:
%% rebar.config
{deps, [
{asobi_lua, {git, "https://github.com/widgrensit/asobi_lua.git", {tag, "v0.1.0"}}}
]}.Configure game modes in your sys.config — see guides/lua-scripting.md.
Game-server authors writing Erlang directly should depend on the core library at widgrensit/asobi instead.
Apache-2.0. See LICENSE.
