Lunar
A lightweight, self-hosted Function-as-a-Service platform written in Go with Lua scripting.
Install / Use
/learn @dimiro1/LunarREADME
Lunar (formerly Faas-Go)
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.
Features
- 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
- API Documentation - Swagger UI available at
/docs - Lightweight - Single binary, no external dependencies
Screenshots
Dashboard

Function Editor

Environment Variables

Testing Functions

Execution History

Version Management

Version Comparison

Command Palette

Error Messages

AI Request Logs

Email Request Logs

Quick Start
Building from Source
git clone https://github.com/dimiro1/lunar.git
cd lunar
make build
Running
./build/lunar
Another option:
Run the following command once to install all required tools:
make install-tools
Then start the development environment with:
make dev
The application will be available at http://localhost:3000.
First-Time Setup
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.
Writing Functions
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
Available APIs
- 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
LLM-Assisted Development
Lunar provides an llms.txt file at /llms.txt with the complete Lua API reference, including function signatures, parameters, and code examples. You can use this with any LLM-powered coding assistant to get accurate help when writing Lunar functions.
Example: Counter Function
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
Example: Send Email
-- Requires RESEND_API_KEY environment variable
function handler(ctx, event)
local data = json.decode(event.body)
local result, err = email.send({
from = "noreply@yourdomain.com",
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 })
}
end
Calling Functions
curl -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
Deployment
Docker
# Run the latest release from Docker Hub
docker run -p 3000:3000 -v $(pwd)/data:/data dimiro1/lunar:latest
# 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 -d
Railway
Lunar 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
Configuration
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)
Authentication
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/functions
Note: Function execution endpoints (/fn/{id}) do not require authentication.
Testing
Go Tests
Run the Go unit tests:
make test
Frontend Tests (Jasmine)
The frontend uses Jasmine for unit testing, running directly in the browser without Node.js dependencies.
make test-frontend
This 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, ...)
E2E Tests (chromedp)
End-to-end tests use chromedp to run a headless Chrome browser:
make test-e2e
E2E tests cover:
- Login flow
- Page navigation
- Functions list rendering
Run All Tests
make test-all
This runs Go unit tests and E2E tests. Run make test-frontend separately to open the browser-based Jasmine tests.
Architecture
- 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
Frontend Dependencies
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-js
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
Author
Claudemiro Alves Feitosa Neto
License
MIT License - see LICENSE file for details.
