Skip to content
Console →
Website →
Asking an AI? Paste this URL https://keelson.dev/llms.txt

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.

  • Bind to 0.0.0.0 on PORT. Requests cannot reach a server bound to 127.0.0.1
  • Do not put dependencies in command. They are installed at build time from requirements.txt, package.json, or go.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_MODE to detect Keelson. The value is keelson when running on the platform. Using DEBUG for 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 false
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 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: none
env:
PYTHONUNBUFFERED: "1"
  • Default to DEBUG = False. Pass SECRET_KEY as a secret
  • With DEBUG=False, Django does not serve static files. Add whitenoise to MIDDLEWARE, configure STATIC_ROOT, and include the output of collectstatic
  • Keep ALLOWED_HOSTS = ["*"]. Restricting it may cause health checks to return 400
  • Construct CSRF_TRUSTED_ORIGINS from KEELSON_APP_URL because the request’s Host is an internal hostname

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 does not currently work because it requires WebSockets. Gradio 4 and later are under verification.

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.

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

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 build runs 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_URL on the server. Putting it in NEXT_PUBLIC_* sends it to the browser

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-slim
command: "./app"
  • The app is built with CGO_ENABLED=0. You cannot use cgo SQLite drivers such as mattn/go-sqlite3. Use a pure Go libSQL client with Managed SQLite
  • Do not leave ListenAndServe running without shutdown handling. Use signal.NotifyContext and server.Shutdown to drain in-flight requests within 10 seconds
  • If you use GORM, replace it with database/sql plus libSQL

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.