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.
Definition of a successful deployment
Section titled “Definition of a successful deployment”Verify deployment completion according to the app type, as well as the build result.
| App type | Conditions to verify |
|---|---|
| Web app or hybrid | The 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 SPA | Files 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 only | The 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.
Required file
Section titled “Required file”Every deployment requires a keelson.yaml file in the project root directory.
Minimal configuration:
slug: my-appruntime: python-slimcommand: "python app.py"db: mode: nonedb 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.
Placement region
Section titled “Placement region”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.
Supported runtimes
Section titled “Supported runtimes”Keelson can run apps only on the following runtimes.
| Runtime | Language | Intended use |
|---|---|---|
python-slim | Python | Lightweight APIs, text processing, automation, and similar workloads |
python-media | Python | Media processing; includes image and video libraries |
node-slim | Node.js | Lightweight web apps, APIs, and similar workloads |
node-media | Node.js | Media processing; includes image-processing libraries |
go-slim | Go | Lightweight Go apps |
go-media | Go | Go 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.
Difference between slim and media
Section titled “Difference between slim and media”- 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.
Supported frameworks
Section titled “Supported frameworks”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.
Unsupported runtimes
Section titled “Unsupported runtimes”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.
Build and runtime constraints
Section titled “Build and runtime constraints”Keelson provides fixed build and runtime environments. The app must be able to build and start in those environments.
Operating system and architecture
Section titled “Operating system and architecture”- OS: Linux
- CPU: x86_64 (amd64)
Root privileges
Section titled “Root privileges”Apps run as a non-root user. They cannot use sudo, run apt-get install, or make system-level changes.
Dockerfile
Section titled “Dockerfile”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.
File system
Section titled “File system”| Path | Writable | Persistent | Purpose |
|---|---|---|---|
/data | No; it cannot be created by your app | No | Keelson 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 permissions | No | Source code and dependencies |
/tmp | Yes, temporarily | No | Temporary files |
Home directory (/home/appuser) | Yes in Python and Node.js runtimes | No | Runtime and tool caches and configuration |
| Other paths | Depends on ownership and permissions | No | Use /tmp for temporary files |
- Use
/tmpfor 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
PORTenvironment variable. - It must listen on
0.0.0.0. Requests cannot reach a server bound to127.0.0.1orlocalhost. - Keelson terminates HTTPS. The app itself must listen over HTTP.
System packages
Section titled “System packages”Keelson does not provide an environment where arbitrary OS packages can be added.
-slimruntimes contain only a minimal set of system libraries.-mediaruntimes 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-getor similar tools because the app runs as a non-root user.
Process model
Section titled “Process model”- The normal model is one process started by
command. - systemd and daemon managers are not available.
- Use
cronsfor work that runs at set times, andtasksfor work that continues after a request (Background tasks).workershas been removed, and declaring it causes the deployment to be rejected.
Dependency constraints
Section titled “Dependency constraints”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.
# Pythoncommand: "python app.py"
# Node.jscommand: "npm start"
# Go (Keelson builds `./app` during deployment)command: "./app"Packages with native dependencies
Section titled “Packages with native dependencies”Some packages require C libraries or other system-level dependencies.
- Commonly supported by
-mediaruntimes: 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.
Common unsupported patterns
Section titled “Common unsupported patterns”| Pattern | Reason |
|---|---|
Requires apt-get install | Packages cannot be added without root privileges |
| Depends on a specialized C library | The library may not be included in the runtime |
| Requires a GPU inference library | GPU instances are not provided |
| Runs a database server such as PostgreSQL, MySQL, or Redis | The app can connect to an external service, but cannot run that server on Keelson |
| Requires systemd or a background daemon | The process model is different |
Deployment modes
Section titled “Deployment modes”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.
| Name | Raw deploy_mode | command | assets | Description |
|---|---|---|---|---|
| Web app | container | Present | Absent | Standard app deployment |
| Static site | edge-static | Absent | Present, without fallback | Static files only |
| SPA | edge-spa | Absent | Present, with fallback | Single-page app with fallback routing |
| Hybrid | hybrid | Present | Present; fallback required when type: web is explicit | Static files plus a backend API |
| Scheduled jobs only | container | Absent | Absent | Requires 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.
Reserved URL paths
Section titled “Reserved URL paths”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.
/__keelsonand 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
__keelsondirectory at the root of the served build output. A build containing this reserved path is rejected with the error codereserved_path_conflict. Rename the directory and deploy again. - The platform responds to
GET /healthandGET /_healththrough the gateway. These GET requests do not reach the app even if it defines matching routes. /api/webhooks/emailand/api/webhooks/email-eventsare blocked from external access at the edge. Use different paths for app-defined webhooks./api/mcpand 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 ifkeelson.yamlhas nomcp: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.endpointspath cannot start with/__keelson; it must start with/api/external/or/api/webhooks/. Reserved words also cannot be used as aslug.
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.
| Framework | Default static path | Conflicts with /__keelson | Status |
|---|---|---|---|
| Vite (Vue / Svelte / React / Solid / Preact) | /assets/* | No | Verified |
| Remix v2 / VitePress | /assets/* | Expected not to | Needs verification |
| Angular | /assets/* | Expected not to | Needs verification |
| Next.js | /_next/static/* | Expected not to | Needs verification |
| Nuxt / SvelteKit / Astro | /_nuxt/* · /_app/* · /_astro/* | Expected not to | Needs verification |
| CRA / Django / Flask | /static/* | Expected not to | Needs verification |
Environment variables and secrets
Section titled “Environment variables and secrets”Configure API keys, tokens, and connection details required at startup as environment variables or secrets instead of embedding them in source code.
envinkeelson.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.
Environment variables set by Keelson
Section titled “Environment variables set by Keelson”| Variable | Description |
|---|---|
PORT | Port the app must listen on. Read it; do not set it |
TZ | Workspace time zone, detected automatically from the browser when the workspace is created |
KEELSON_MODE | Marker indicating that the app is running on Keelson (keelson) |
KEELSON_APP_ID | Internal app ID |
KEELSON_WORKSPACE_ID | Internal workspace ID |
KEELSON_TENANT_ID | Compatibility alias for KEELSON_WORKSPACE_ID (same value) |
KEELSON_DEPLOY_ID | Internal 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.
Production hardening
Section titled “Production hardening”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.
Production server by framework
Section titled “Production server by framework”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
DEBUGoff by default. - With
DEBUG=False, Django does not serve static files itself. Add whitenoise toMIDDLEWARE, configureSTATIC_ROOT, and ensure the deployed artifacts include the output ofcollectstatic. - Keep
ALLOWED_HOSTS = ["*"]; restricting it can make health checks return HTTP 400. - Replace
config.wsgi:applicationwith 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.
Debug and development flags
Section titled “Debug and development flags”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.
Detecting Keelson at runtime
Section titled “Detecting Keelson at runtime”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.
Timeouts
Section titled “Timeouts”| Target | Limit |
|---|---|
| HTTP request | The 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 |
| Build | 600 seconds |
Outbound connections
Section titled “Outbound connections”- 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.
Common failure patterns
Section titled “Common failure patterns”| Symptom | Cause | Resolution |
|---|---|---|
| App is running but unreachable, or health checks fail | Server is not listening on 0.0.0.0 | Set host="0.0.0.0" explicitly |
| Cannot connect to the port | Port number is hard-coded | Read the port from the PORT environment variable |
| Module not found | Dependency is missing from the dependency manifest or failed to install during the image build | Check the build logs and declare the dependency in requirements.txt or package.json |
| Native module fails to build | Required system library is missing | Switch to a -media runtime or replace the dependency |
| Start command not found | Entrypoint path is incorrect | Check the file name and path |
Preflight stops with command_installs_dependencies | command includes pip install / npm install | Remove installation steps and keep only the start command |
Validation stops because db is missing | No db.mode is declared | Declare libsql or none |
Build fails with reserved_path_conflict | A __keelson directory exists at the root of the served build output | Rename the directory. See Reserved URL paths for other restrictions |
| A scheduled job never fires, and no error is logged | The schedule lives inside the app process (an in-process scheduler). The app has scaled to zero by the time it is due | Declare it as a crons entry instead |
| The UI reports success, but the email or notification never arrives | Work deferred until after the response did not run | Finish 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 |
Troubleshooting steps
Section titled “Troubleshooting steps”- Check the build logs — Confirm that dependencies were installed successfully.
- Check the runtime logs — Look for startup and runtime errors.
- Check
keelson.yaml— Verifyruntime,command, andenv.
Expected behavior for AI agents
Section titled “Expected behavior for AI agents”When deploying an app to Keelson, an AI agent should make decisions in the following order.
Decision flow
Section titled “Decision flow”- Check the runtime — Confirm that the app’s language appears in this specification’s supported runtimes.
- Check dependencies — Confirm that native dependencies and system-library requirements fit within the constraints.
- Check
keelson.yaml— Confirm that the file exists and its configuration is valid. Create it if it is missing. - Check environment variables — Identify any missing environment variables or secrets the app requires.
- Deploy — Use the CLI or Skill to run the deployment.
- 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.
- Handle failure — Inspect the logs and make changes where possible. If a constraint is violated, state the reason and stop.
Mandatory rules
Section titled “Mandatory rules”- 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.
Specification precedence
Section titled “Specification precedence”- The latest Keelson Deploy Spec on the web (this document)
- The latest CLI version information on the web
- The copy of the specification bundled with a Skill
- General knowledge and assumptions
If the web canonical specification conflicts with information bundled with a Skill, follow the web canonical specification.