A lightweight, self-hosted Function-as-a-Service platform written in Go with Lua scripting.
Beta Phase Notice: This project is currently in beta. New features and changes are actively being developed, but I promise to maintain backward compatibility for all Lua APIs.
- Simple Lua Functions - Write serverless functions in Lua
- Code Editor - Monaco Editor with autocomplete and inline documentation
- HTTP Triggers - Execute functions via HTTP requests
- Built-in APIs - HTTP client, KV store, environment variables, logging, and more
- AI Integration - Chat completions with OpenAI and Anthropic, with request/response logging
- Email Integration - Send emails via Resend with scheduling support
- Version Control - Track and manage function versions
- Execution History - Monitor function executions and logs
- Beautiful Error Messages - Human-friendly error messages with code context, line numbers, and actionable suggestions
- Web Dashboard - Manage functions through a clean web interface
- Lightweight - Single binary, no external dependencies
git clone https://github.com/dimiro1/lunar.git
cd lunar
make build./build/lunarAnother option:
Run the following command once to install all required tools:
make install-toolsThen start the development environment with:
make devThe API will be available at http://localhost:3000 and the dashboard at http://localhost:3000/.
On first run, Lunar will automatically generate an API key and save it to data/api_key.txt. The key will be printed in the server logs:
INFO Generated new API key key=cf31cb0cdc7811ca9cec6a3c77579b3ea28c1e4e10d6fc1061ae71788834c21b file=data/api_key.txt
When you access the dashboard, you'll be prompted to enter this API key to login. The key is also available in the data/api_key.txt file.
Functions are written in Lua and must export a handler function:
function handler(ctx, event)
-- ctx contains execution context (executionId, functionId, etc.)
-- event contains HTTP request data (method, path, query, body, headers)
log.info("Function started")
return {
statusCode = 200,
headers = { ["Content-Type"] = "application/json" },
body = json.encode({ message = "Hello, World!" })
}
end- log - Logging utilities (info, debug, warn, error)
- kv - Key-value storage (get, set, delete)
- env - Environment variables (get)
- http - HTTP client (get, post, put, delete)
- json - JSON encoding/decoding
- crypto - Cryptographic functions (md5, sha256, hmac, uuid)
- time - Time utilities (now, format, sleep)
- url - URL utilities (parse, encode, decode)
- strings - String manipulation
- random - Random generators
- base64 - Base64 encoding/decoding
- ai - AI chat completions (OpenAI, Anthropic)
- email - Send emails via Resend
function handler(ctx, event)
-- Get current count from KV store
local count = kv.get("counter") or "0"
local newCount = tonumber(count) + 1
-- Save updated count
kv.set("counter", tostring(newCount))
log.info("Counter incremented to: " .. newCount)
return {
statusCode = 200,
headers = { ["Content-Type"] = "application/json" },
body = json.encode({ count = newCount })
}
end-- Requires RESEND_API_KEY environment variable
function handler(ctx, event)
local data = json.decode(event.body)
local result, err = email.send({
from = "[email protected]",
to = data.email,
subject = "Welcome!",
html = "<h1>Hello, " .. data.name .. "!</h1>",
scheduled_at = time.now() + 3600 -- Optional: send in 1 hour
})
if err then
return {
statusCode = 500,
body = json.encode({ error = err })
}
end
return {
statusCode = 200,
headers = { ["Content-Type"] = "application/json" },
body = json.encode({ email_id = result.id })
}
endcurl -X GET http://localhost:3000/fn/{function-id}
curl -X POST http://localhost:3000/fn/{function-id} -d '{"key":"value"}'
curl -X GET http://localhost:3000/fn/{function-id}?name=John# Build and run with Docker
docker build -t lunar .
docker run -p 3000:3000 -v lunar-data:/app/data lunar
# Or use Docker Compose
docker compose up -dLunar is ready to deploy on Railway:
- Connect Repository - Link your GitHub repository to Railway
- Add Volume - Create a volume and mount it to
/data - Set Environment Variables:
BASE_URL- Your Railway public URL (e.g.,https://yourapp.up.railway.app)API_KEY- (Optional) Set a custom API key, or let it auto-generate
- Deploy - Railway will automatically detect the Dockerfile and deploy
The Dockerfile is Railway-compatible and will:
- Use Railway's automatic
PORTenvironment variable - Bind to
0.0.0.0:$PORTfor public networking - Persist data to the mounted volume at
/data
Lunar can be configured via environment variables:
PORT=3000 # HTTP server port (default: 3000)
DATA_DIR=./data # Data directory for SQLite database (default: ./data)
EXECUTION_TIMEOUT=300 # Function execution timeout in seconds (default: 300)
API_KEY=your-key-here # API key for authentication (auto-generated if not set)
BASE_URL=http://localhost:3000 # Base URL for the deployment (auto-detected if not set)The dashboard requires authentication via API key. You can:
- Auto-generate (recommended) - Let Lunar generate a secure key on first run
- Set manually - Provide your own key via the
API_KEYenvironment variable
API calls can authenticate using either:
- Cookie - Automatically handled by the dashboard after login
- Bearer token - Include
Authorization: Bearer YOUR_API_KEYheader
Example API call with Bearer token:
curl -H "Authorization: Bearer YOUR_API_KEY" http://localhost:3000/api/functionsNote: Function execution endpoints (/fn/{id}) do not require authentication.
Run the Go unit tests:
make testThe frontend uses Jasmine for unit testing, running directly in the browser without Node.js dependencies.
make test-frontendThis starts a local Go server and opens the test runner at http://localhost:8888/test/SpecRunner.html. Tests cover:
- Route URL generators
- UI components (Button, Badge, Table, Pagination, ...)
End-to-end tests use chromedp to run a headless Chrome browser:
make test-e2eE2E tests cover:
- Login flow
- Page navigation
- Functions list rendering
make test-allThis runs Go unit tests and E2E tests. Run make test-frontend separately to open the browser-based Jasmine tests.
- Backend - Go with standard library HTTP server, SQLite database
- Frontend - Mithril.js SPA with Monaco Editor
- Runtime - GopherLua for Lua script execution
- Storage - SQLite for functions, versions, executions, KV store, and environment variables
JavaScript dependencies are vendored in frontend/vendor/ (no npm required). Versions are managed in the Makefile:
| Library | Purpose |
|---|---|
| Mithril.js | SPA framework |
| Monaco Editor | Code editor |
| Highlight.js | Syntax highlighting |
| Jasmine | Frontend testing |
To update dependencies, edit the version variables in the Makefile and run:
make vendor-jsContributions are welcome! Please feel free to submit issues or pull requests.
Claudemiro Alves Feitosa Neto
MIT License - see LICENSE file for details.











