bun installCopy .env.example if you want a local reference for the standard environment variables:
cp .env.example .envStart the server and client together:
bun run devRun them separately:
bun run dev:server
bun run dev:clientDefault local URLs:
- Client:
http://localhost:3001 - Server:
http://localhost:3000 - WebSocket:
ws://localhost:3000/ws
Use native Bun for the client and server, with Docker only for Postgres. Each worktree should have
its own .env, Compose project name, ports, containers, and database volume.
Create a worktree:
git worktree add ../tilezo-wt/room-state -b feat/room-state main
cd ../tilezo-wt/room-state
bun installGenerate a worktree-local .env:
bun run worktree:setupThe generated .env is ignored by Git, read automatically by Bun and Docker Compose, and includes:
COMPOSE_PROJECT_NAMEfor Docker Compose isolation.SERVER_PORTandCLIENT_PORTfor browser-facing ports.DB_PORTfor the host Postgres port.PORT,DATABASE_URL,PUBLIC_API_URL, andPUBLIC_WS_URLfor native Bun development.
Start only Postgres:
bun run db:upRun migrations against that worktree database:
bun run db:migrateStart the native app:
bun run devThe setup command prints the exact client, server, and database ports for the worktree. If you
already copied .env.example, or you need to regenerate the worktree values, run:
bun run worktree:setup -- --forceStop the worktree database without deleting data:
bun run db:downDelete that worktree database volume when you need a clean database:
bun run db:resetUse the full Docker Compose stack when you want container parity for the client, server, and Postgres:
docker compose up --buildCompose starts:
deps: a one-shot Bun install that keeps the container-owned dependency volume in sync withbun.lock.migrate: a one-shot Drizzle migration run after Postgres is healthy and before the server starts.client: Bun's browser dev server onhttp://localhost:3001.server: Bun's WebSocket/API server onhttp://localhost:3000.db: Postgres 17 with data stored in thepostgres_dataDocker volume.
The server uses DATABASE_URL=postgres://postgres:postgres@db:5432/tilezo inside Compose. From the host, the same database is available at postgres://postgres:postgres@localhost:5432/tilezo.
Compose runs migrations automatically before starting the server.
For concurrent worktrees, set or generate unique values for COMPOSE_PROJECT_NAME, SERVER_PORT,
CLIENT_PORT, and DB_PORT before running Compose. The bun run worktree:setup command writes
those values to .env, which Docker Compose reads automatically.
Run project checks inside the container:
docker compose exec server bun run typecheck
docker compose exec server bun run lint
docker compose exec server bun testStop the stack without deleting database data:
docker compose downDelete the local Docker database volume when you need a clean database:
docker compose down -vbun run typecheck
bun run lint
bun testUse repository scripts for formatting and linting so Biome configuration stays consistent:
bun run format
bun run lintThe server uses Drizzle for Postgres schema and migrations. DATABASE_URL is required for account creation and login; when it is present, the server also loads or seeds the default room.
bun run --cwd apps/server db:generate
bun run --cwd apps/server db:migrateServer:
HOST=0.0.0.0
PORT=3000
DATABASE_URL=postgres://postgres:postgres@localhost:5432/tilezo
AUTH_SECRET=change-me-in-production
NODE_ENV=developmentClient:
PUBLIC_API_URL=http://localhost:3000
PUBLIC_WS_URL=ws://localhost:3000/wsIf PUBLIC_API_URL or PUBLIC_WS_URL is omitted, the client uses the local server defaults. The
client uses Bun's static env inlining for literal process.env.PUBLIC_* references.
The Dockerfile includes separate targets for later deployment:
docker build --target server -t tilezo-server .
docker build --target client -t tilezo-client .The server target runs the Bun WebSocket server on PORT with HOST=0.0.0.0. The client target builds static browser assets and serves them with Caddy.
For a production deployment, use a managed Postgres database when possible, set DATABASE_URL and AUTH_SECRET on the server service, and route /auth/*, /ws, and /health to the server. Route the browser app to the client service. Use https://... for PUBLIC_API_URL and wss://.../ws for PUBLIC_WS_URL when serving over HTTPS.
PUBLIC_API_URL and PUBLIC_WS_URL are baked into the static client build. If the same client image
needs to move between environments, add a runtime client config file or config endpoint before
promoting one image across staging and production.
Use Bun's test runner for deterministic logic and server room behavior.
Current test coverage includes:
- Isometric projection.
- Screen-to-tile conversion.
- Grid walkability.
- Pathfinding.
- Protocol parsing.
- Room join, leave, and authoritative movement.
Rendering tests should stay light until the client UI becomes more complex.
Direct messages are friend-gated and block-aware, but message bodies are stored as server-readable plaintext for now so moderation, account export, and deletion workflows can operate. They are private from other players, not end-to-end encrypted from server operators or anyone with database access. Do not represent Tilezo DMs as E2EE unless a future encrypted storage design is implemented and reviewed.