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

Keelson Deploy Spec

Spec version: 2026-10-06 / Raw text (for AI agents): /docs/reference/deploy-spec.txt

This document is the canonical definition of the supported runtimes, constraints, and requirements for deploying an app to Keelson. Use it when deciding whether an app can be deployed.

For a quickstart and step-by-step instructions, see Deploy an app.


Verify deployment completion according to the app type, as well as the build result.

App typeConditions to verify
Web app or hybridThe build is complete, the web app process has started, and the health check has passed. An app URL has been issued and is reachable. A hybrid app must also serve its static files
Static site or SPAFiles have been uploaded and published and can be served at the app URL. No app process startup or web health check is required
Scheduled jobs onlyThe build is complete, and the schedules and job execution environment settings have been applied. Web process startup, health checks, and URL access do not apply

For web apps, a successful build is insufficient if startup or the health check fails. The app URL is https://<workspace-slug>--<app-slug>.keelson.run.

Scheduled jobs run on their configured schedules after deployment. A successful deployment does not establish that individual jobs have completed successfully; check their execution history and logs.


Every deployment requires a keelson.yaml file in the project root directory.

Minimal configuration:

slug: my-app
runtime: python-slim
command: "python app.py"
db:
mode: none

db and db.mode (libsql or none) are required.

Framework-based apps such as Flask require a production server. See Production hardening.

See the keelson.yaml reference for details about every field.


Each application is placed in one logical region when it is created. Defined region keys are jp-tokyo (Japan) and us-oregon (US West); Keelson shows these display names instead of cloud-provider region names. Only jp-tokyo is currently open for new applications; us-oregon is defined but not yet offered.

For a new application, select a region with keelson deploy --new --region <region>, or set the top-level region field in keelson.yaml. The CLI option takes priority over the file, and the file takes priority over the workspace’s default region. An explicit selection of a region that is not yet offered (currently us-oregon) is rejected rather than redirected. Without a selection the app is placed in the workspace’s default region (Japan), so do not set region unless the user asks for it.

The region is fixed after application creation. Deploying with another region does not move an existing application and is rejected; create a new application to use another region.


Keelson can run apps only on the following runtimes.

RuntimeLanguageIntended use
python-slimPythonLightweight APIs, text processing, automation, and similar workloads
python-mediaPythonMedia processing; includes image and video libraries
node-slimNode.jsLightweight web apps, APIs, and similar workloads
node-mediaNode.jsMedia processing; includes image-processing libraries
go-slimGoLightweight Go apps
go-mediaGoGo apps with media-processing dependencies

Select a runtime with the runtime field in keelson.yaml. If you are unsure, start with a -slim runtime and switch to -media when the app needs media-processing libraries.

  • slim — Contains the language runtime and standard libraries only. It builds faster and has a smaller image.
  • media — Adds common system libraries required for image processing, such as Pillow and sharp, and for video processing.

Keelson does not depend on a specific framework. An app can run if its command starts it and it accepts requests as an HTTP server.

Examples include FastAPI, Flask, Express, Next.js, Hono, and Gin.


The following languages and runtimes are not supported:

  • Ruby
  • Java / Kotlin / Scala
  • PHP
  • Rust
  • .NET / C#
  • Elixir / Erlang
  • Swift

Apps that require an unsupported runtime cannot be deployed to Keelson, even after configuration changes that do not replace that runtime.


Keelson provides fixed build and runtime environments. The app must be able to build and start in those environments.

  • OS: Linux
  • CPU: x86_64 (amd64)

Apps run as a non-root user. They cannot use sudo, run apt-get install, or make system-level changes.

A Dockerfile in the repository is ignored; it is not a reason to refuse a deploy. Keelson selects a runtime and starts the app with command. Specify the runtime and start command in keelson.yaml instead of a Dockerfile.

PathWritablePersistentPurpose
/dataNo; it cannot be created by your appNoKeelson does not provide it. Apps run as UID 1000 and cannot create directories under the root-owned /
App directory (/workspace)Cannot create entries directly in this directory; existing files and subdirectories may be writable depending on permissionsNoSource code and dependencies
/tmpYes, temporarilyNoTemporary files
Home directory (/home/appuser)Yes in Python and Node.js runtimesNoRuntime and tool caches and configuration
Other pathsDepends on ownership and permissionsNoUse /tmp for temporary files
  • Use /tmp for temporary files. Local writes are not persistent and are lost when the instance restarts or stops.
  • Keelson provides three forms of persistent storage: Managed SQLite (db.mode: libsql), the Files SDK for private app files, and the Media SDK for images and attachments served to users. None is available as a local file path. Apps may also connect to external databases or storage.
  • File-based SQLite is not persistent. File SQLite clients are detected during deployment and rejected unless declared for ephemeral use with db.local_sqlite.
  • A web app must listen for HTTP requests on the port specified by the PORT environment variable.
  • It must listen on 0.0.0.0. Requests cannot reach a server bound to 127.0.0.1 or localhost.
  • Keelson terminates HTTPS. The app itself must listen over HTTP.

Keelson does not provide an environment where arbitrary OS packages can be added.

  • -slim runtimes contain only a minimal set of system libraries.
  • -media runtimes contain common libraries required for image and video processing.
  • An app may not work if it needs a system library that is not present in its runtime.
  • Packages cannot be added with apt-get or similar tools because the app runs as a non-root user.
  • The normal model is one process started by command.
  • systemd and daemon managers are not available.
  • Use crons for work that runs at set times, and tasks for work that continues after a request (Background tasks). workers has been removed, and declaring it causes the deployment to be rejected.

Even when the language runtime is supported, dependencies and system requirements can prevent an app from being deployed.

Dependencies installed by language package managers

Section titled “Dependencies installed by language package managers”

Pure language packages managed by the following package managers can be installed normally:

  • Python: pip (requirements.txt)
  • Node.js: npm (package.json)
  • Go: go mod (go.mod)

Keelson installs dependencies automatically while building the image by detecting requirements.txt, pyproject.toml, package.json, or go.mod. Use command only to start the app. If it includes pip install, npm install, or go build, the CLI preflight check stops deployment with command_installs_dependencies.

Build constraints: package-lock.json is the only supported Node lockfile. Only public registries are supported; private registries and authenticated installs are not. Go uses CGO_ENABLED=0. The build target is linux/amd64. Secrets are unavailable during builds. The time limit is 600 seconds.

# Python
command: "python app.py"
# Node.js
command: "npm start"
# Go (Keelson builds `./app` during deployment)
command: "./app"

Some packages require C libraries or other system-level dependencies.

  • Commonly supported by -media runtimes: General media-processing libraries such as Pillow, opencv-python, sharp, and ffmpeg-related packages.
  • Potentially unsupported: Packages that depend on a system library not included in the runtime.
PatternReason
Requires apt-get installPackages cannot be added without root privileges
Depends on a specialized C libraryThe library may not be included in the runtime
Requires a GPU inference libraryGPU instances are not provided
Runs a database server such as PostgreSQL, MySQL, or RedisThe app can connect to an external service, but cannot run that server on Keelson
Requires systemd or a background daemonThe process model is different

A deployment mode is not chosen directly. It is derived automatically from the presence of command and assets. CLI and API output exposes the raw deploy_mode label; use the following table to interpret it.

NameRaw deploy_modecommandassetsDescription
Web appcontainerPresentAbsentStandard app deployment
Static siteedge-staticAbsentPresent, without fallbackStatic files only
SPAedge-spaAbsentPresent, with fallbackSingle-page app with fallback routing
HybridhybridPresentPresent; fallback required when type: web is explicitStatic files plus a backend API
Scheduled jobs onlycontainerAbsentAbsentRequires crons; runs scheduled jobs without starting a web server

Apps that run only scheduled jobs also use deploy_mode: container, but their startup and completion checks differ from web apps.

Static sites and SPAs require type: web and cannot use crons.


Some paths on the app’s host are used internally by Keelson or blocked from external access. Do not use the following as public app endpoints.

  • /__keelson and all paths beneath it are reserved for platform internals for every HTTP method. Keelson uses them for platform-served assets, file downloads, and internal endpoints. Do not define app routes at these paths.
  • Do not emit a __keelson directory at the root of the served build output. A build containing this reserved path is rejected with the error code reserved_path_conflict. Rename the directory and deploy again.
  • The platform responds to GET /health and GET /_health through the gateway. These GET requests do not reach the app even if it defines matching routes.
  • /api/webhooks/email and /api/webhooks/email-events are blocked from external access at the edge. Use different paths for app-defined webhooks.
  • /api/mcp and paths beginning with /api/mcp/ are reserved for MCP tool calls. In apps that run server-side code (including the server side of a hybrid app), requests to these paths that do not come through MCP return 404, even if keelson.yaml has no mcp: block. Do not put any route other than tool handlers (POST /api/mcp/<tool-name>) here. Statically served parts are not affected.
  • In apps with mcp.enabled: true, Keelson responds to /mcp, /.well-known/oauth-protected-resource, and /.well-known/oauth-protected-resource/mcp. These requests do not reach the app. See Use your app from AI clients (MCP).
  • Related rules: an auth.endpoints path cannot start with /__keelson; it must start with /api/external/ or /api/webhooks/. Reserved words also cannot be used as a slug.

Paths such as /assets/* (Vite’s default output), /files/*, /static/*, and /uploads/* are available to apps. /api/* is also available subject to the exceptions above and the authentication configuration rules.

Framework static-path collision matrix (reference)

Section titled “Framework static-path collision matrix (reference)”

The following table lists common frameworks’ default static paths. “Verified” means delivery has been checked in this repository; “Needs verification” means the result is inferred from default paths. For unverified frameworks, check the actual build output and delivery. This table is only about path collisions with /__keelson; it does not indicate whether a framework is supported.

FrameworkDefault static pathConflicts with /__keelsonStatus
Vite (Vue / Svelte / React / Solid / Preact)/assets/*NoVerified
Remix v2 / VitePress/assets/*Expected not toNeeds verification
Angular/assets/*Expected not toNeeds verification
Next.js/_next/static/*Expected not toNeeds verification
Nuxt / SvelteKit / Astro/_nuxt/* · /_app/* · /_astro/*Expected not toNeeds verification
CRA / Django / Flask/static/*Expected not toNeeds verification

Configure API keys, tokens, and connection details required at startup as environment variables or secrets instead of embedding them in source code.

  • env in keelson.yaml — Values that are safe to include in version control.
  • Console secrets — API keys, tokens, and other values that should not be committed to source code.

The app may fail to start correctly if a required value is missing.

See Environment variables and secrets for details.

VariableDescription
PORTPort the app must listen on. Read it; do not set it
TZWorkspace time zone, detected automatically from the browser when the workspace is created
KEELSON_MODEMarker indicating that the app is running on Keelson (keelson)
KEELSON_APP_IDInternal app ID
KEELSON_WORKSPACE_IDInternal workspace ID
KEELSON_TENANT_IDCompatibility alias for KEELSON_WORKSPACE_ID (same value)
KEELSON_DEPLOY_IDInternal ID of the current deploy

The former tenant-named variable remains accepted as a compatibility alias. No removal date is set.

With db.mode: libsql, Keelson also injects KEELSON_DB_URL / KEELSON_DB_AUTH_TOKEN (aliases: TURSO_DATABASE_URL / TURSO_AUTH_TOKEN). Names beginning with KEELSON_ are reserved and cannot be used in env or secrets.


Keelson apps run on Cloud Run and may start and stop during normal operation. On shutdown, the process receives SIGTERM and has 10 seconds before SIGKILL. Use a production server and handle graceful shutdown within that window.

Flask: app.run() starts the Werkzeug development server. It may remain behind a local __main__ entry point, but it must not serve a deployment, even with debug=False. Add gunicorn to requirements.txt and use:

command: "gunicorn --bind 0.0.0.0:$PORT --workers 1 --threads 8 --timeout 0 --graceful-timeout 9 app:app"

Keelson provides 1 vCPU, so use one worker. A graceful timeout of 9 seconds fits within the 10-second SIGTERM window. Replace app:app with the module and application object for the project.

Django: Start Django with gunicorn:

command: "gunicorn --bind 0.0.0.0:$PORT --workers 1 --threads 8 --timeout 0 --graceful-timeout 9 config.wsgi:application"
  • Keep DEBUG off by default.
  • With DEBUG=False, Django does not serve static files itself. Add whitenoise to MIDDLEWARE, configure STATIC_ROOT, and ensure the deployed artifacts include the output of collectstatic.
  • Keep ALLOWED_HOSTS = ["*"]; restricting it can make health checks return HTTP 400.
  • Replace config.wsgi:application with the WSGI module for the project.

FastAPI / uvicorn: Bind to 0.0.0.0:$PORT and leave the worker count at its default of one:

command: "uvicorn main:app --host 0.0.0.0 --port $PORT"

Do not use --reload. File watching consumes memory and can start the app twice. Replace main:app with the module and application object for the project.

Next.js: Start the built application, not the development server:

command: "npm run start"

Set the package script to:

"start": "next start -p $PORT"

If package.json contains "start": "next dev", fix that script rather than working around it. next dev is not a production server.

Node.js: Keelson does not set NODE_ENV automatically. Declare production mode in keelson.yaml; without it, frameworks such as Express may return stack traces:

env:
NODE_ENV: "production"

Go: Start the built binary with command: "./app". Do not leave ListenAndServe without shutdown handling: use signal.NotifyContext and server.Shutdown to stop accepting new requests and drain in-flight requests within the 10-second window.

Defaults must be safe for production. In Python, default DEBUG to false:

DEBUG = os.environ.get("DEBUG", "false").lower() == "true"

Do not default it to true:

DEBUG = os.environ.get("DEBUG", "true").lower() == "true"

Debug pages can expose environment variables, including database authentication tokens.

Use KEELSON_MODE, which Keelson sets to keelson, to detect the platform:

ON_KEELSON = os.environ.get("KEELSON_MODE") == "keelson"

Apply fail-fast checks for missing production configuration only when ON_KEELSON is true. Do not use DEBUG as the platform check, because that can prevent the app from starting locally.


TargetLimit
HTTP requestThe response must start within 120 seconds or the request returns 504. Once streaming starts, the limits are 120 seconds without data and 300 seconds total
Scheduled job (cron)1–600 seconds; default 300 seconds, reduced to the plan limit if it is lower
Build600 seconds

  • Apps can connect to external APIs and services by default.
  • The only exception is the SMTP ports (TCP 25, 465, 587, and 2525), which are always blocked. Send email through an email delivery service’s HTTPS API instead.

SymptomCauseResolution
App is running but unreachable, or health checks failServer is not listening on 0.0.0.0Set host="0.0.0.0" explicitly
Cannot connect to the portPort number is hard-codedRead the port from the PORT environment variable
Module not foundDependency is missing from the dependency manifest or failed to install during the image buildCheck the build logs and declare the dependency in requirements.txt or package.json
Native module fails to buildRequired system library is missingSwitch to a -media runtime or replace the dependency
Start command not foundEntrypoint path is incorrectCheck the file name and path
Preflight stops with command_installs_dependenciescommand includes pip install / npm installRemove installation steps and keep only the start command
Validation stops because db is missingNo db.mode is declaredDeclare libsql or none
Build fails with reserved_path_conflictA __keelson directory exists at the root of the served build outputRename the directory. See Reserved URL paths for other restrictions
A scheduled job never fires, and no error is loggedThe schedule lives inside the app process (an in-process scheduler). The app has scaled to zero by the time it is dueDeclare it as a crons entry instead
The UI reports success, but the email or notification never arrivesWork deferred until after the response did not runFinish it before responding, or enqueue it as a background task. Without background tasks, store the work in the database and process it periodically with crons; tell users it waits until the next scheduled run and may take longer depending on execution conditions
  1. Check the build logs — Confirm that dependencies were installed successfully.
  2. Check the runtime logs — Look for startup and runtime errors.
  3. Check keelson.yaml — Verify runtime, command, and env.

When deploying an app to Keelson, an AI agent should make decisions in the following order.

  1. Check the runtime — Confirm that the app’s language appears in this specification’s supported runtimes.
  2. Check dependencies — Confirm that native dependencies and system-library requirements fit within the constraints.
  3. Check keelson.yaml — Confirm that the file exists and its configuration is valid. Create it if it is missing.
  4. Check environment variables — Identify any missing environment variables or secrets the app requires.
  5. Deploy — Use the CLI or Skill to run the deployment.
  6. Verify completion — Follow the definition of a successful deployment for the app type. Check startup, health checks, and URL access for web apps; delivery at the URL for static sites and SPAs; and applied configuration for apps that run only scheduled jobs.
  7. Handle failure — Inspect the logs and make changes where possible. If a constraint is violated, state the reason and stop.
  • Do not attempt a deployment when the runtime is unsupported. State the reason and stop.
  • When required configuration is missing, explain what is needed.
  • Do not treat build success alone as deployment completion.
  • Always inspect the logs after a startup failure.
  1. The latest Keelson Deploy Spec on the web (this document)
  2. The latest CLI version information on the web
  3. The copy of the specification bundled with a Skill
  4. General knowledge and assumptions

If the web canonical specification conflicts with information bundled with a Skill, follow the web canonical specification.