Framework notes
Keelson is not limited to a particular framework. Any HTTP server that listens on PORT can run. However, deploying a development server can make the app slow, expose internal information on error pages, or drop requests during shutdown. This page lists production startup methods by framework.
If you ask an AI agent to deploy the app, it applies these settings automatically because the Skill contains the same information.
All frameworks
Section titled “All frameworks”- Bind to
0.0.0.0onPORT. Requests cannot reach a server bound to127.0.0.1 - Do not put dependencies in
command. They are installed at build time fromrequirements.txt,package.json, orgo.mod - Shutdown is SIGTERM, then SIGKILL after 10 seconds. Finish in-flight requests within 10 seconds
- 1 vCPU. Use one worker
- Debug mode is off by default. Debug pages may expose environment variables, including database authentication tokens
- Use
KEELSON_MODEto detect Keelson. The value iskeelsonwhen running on the platform. UsingDEBUGfor this purpose prevents the app from starting locally
ON_KEELSON = os.environ.get("KEELSON_MODE") == "keelson"DEBUG = os.environ.get("DEBUG", "false").lower() == "true" # Defaults to falsePython
Section titled “Python”FastAPI / uvicorn
Section titled “FastAPI / uvicorn”command: "uvicorn main:app --host 0.0.0.0 --port $PORT"env: PYTHONUNBUFFERED: "1"Do not add --reload; file watching consumes memory and may start the app twice. Keep the default of one worker.
app.run() is a development server. Add gunicorn to requirements.txt and use it to start the app.
command: "gunicorn --bind 0.0.0.0:$PORT --workers 1 --threads 8 --timeout 0 --graceful-timeout 9 app:app"env: PYTHONUNBUFFERED: "1"Replace app:app with the module name and app object name. --graceful-timeout 9 keeps shutdown within the 10-second SIGTERM grace period.
Django
Section titled “Django”Django can only run with an external database (db.mode: none plus connection details such as PostgreSQL passed as secrets). This is because Django’s ORM has no backend for Managed SQLite (libSQL).
command: "gunicorn --bind 0.0.0.0:$PORT --workers 1 --threads 8 --timeout 0 --graceful-timeout 9 config.wsgi:application"db: mode: noneenv: PYTHONUNBUFFERED: "1"- Default to
DEBUG = False. PassSECRET_KEYas a secret - With
DEBUG=False, Django does not serve static files. Add whitenoise toMIDDLEWARE, configureSTATIC_ROOT, and include the output ofcollectstatic - Keep
ALLOWED_HOSTS = ["*"]. Restricting it may cause health checks to return 400 - Construct
CSRF_TRUSTED_ORIGINSfromKEELSON_APP_URLbecause the request’sHostis an internal hostname
SQLAlchemy / SQLModel / Flask-SQLAlchemy
Section titled “SQLAlchemy / SQLModel / Flask-SQLAlchemy”Replace the sqlite:///app.db engine configuration with sqlalchemy-libsql-native. Models and queries do not change. This path is experimental, and an AI agent asks for confirmation before applying it.
Streamlit / Gradio
Section titled “Streamlit / Gradio”Streamlit does not currently work because it requires WebSockets. Gradio 4 and later are under verification.
Node.js
Section titled “Node.js”Common settings
Section titled “Common settings”NODE_ENV is not set automatically. Declare it in keelson.yaml. Without it, Express and similar frameworks may include stack traces in responses.
env: NODE_ENV: "production"Only package-lock.json is used as a lockfile. pnpm and yarn lockfiles are ignored, so generate it with npm install --package-lock-only.
Express
Section titled “Express”command: "npm start" # package.json: "start": "node server.js"- Do not return stack traces from error handlers
- Use
app.set("trust proxy", 1)to receive the client IP and protocol from the edge - Pass session and JWT secrets through
secrets
Next.js
Section titled “Next.js”Build the app and start it with next start. next dev is not for production.
command: "npm run start" # package.json: "start": "next start -p $PORT"npm run buildruns automatically during the build. Confirm that it produces.next/- If the script is set to
"start": "next dev", fix it instead of adding a workaround - Read
KEELSON_DB_URLon the server. Putting it inNEXT_PUBLIC_*sends it to the browser
Prisma
Section titled “Prisma”provider = "sqlite" does not work as-is. Use @prisma/adapter-libsql pinned to the same major version as @prisma/client (verified with 6.x). An AI agent asks for confirmation before applying this change.
Keelson builds the app with go build -o /workspace/app . and starts it with ./app.
runtime: go-slimcommand: "./app"- The app is built with
CGO_ENABLED=0. You cannot use cgo SQLite drivers such asmattn/go-sqlite3. Use a pure Go libSQL client with Managed SQLite - Do not leave
ListenAndServerunning without shutdown handling. Usesignal.NotifyContextandserver.Shutdownto drain in-flight requests within 10 seconds - If you use GORM, replace it with
database/sqlplus libSQL
Static sites / SPAs
Section titled “Static sites / SPAs”Set assets.dir to the build output from Vite, a Next.js static export, Astro, or a similar tool. The build runs automatically as npm run build. Even if dist/ is in .gitignore, declaring it in assets.dir includes it in the archive.
Related pages
Section titled “Related pages”- Supported app types and constraints
- Database (Managed SQLite)
- Keelson Deploy Spec — source of truth for AI agents