Keelson Deploy Spec
Spec version: 2026-09-02 / 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”A completed build alone does not make a Keelson deployment successful. A deployment succeeds only when all of the following conditions are met:
- The app build has completed.
- The app process has started.
- The app has passed its health check.
- An app URL (
https://<slug>.keelson.run) has been issued. - The app is reachable at that URL.
If the build succeeds but the app fails to start or pass its health check, the deployment is not successful.
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"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.
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. A region can be listed but temporarily unavailable for new
applications during rollout; an explicit unavailable selection is rejected.
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”Custom Dockerfiles are not supported. 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 | No; not a supported write location | No | Source code and dependencies |
/tmp | Yes, temporarily | No | Temporary files |
| Other paths | No | — | — |
- Every write to the local file system is ephemeral and is lost after a restart or scale-to-zero event.
- Use Managed SQLite (
db.mode: libsql) for persistent relational data. A file-based SQLite database under/datais not persistent. - For durable files, use the Files SDK for private app data or the Media SDK for content served to app members. See Files and media.
- 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 background work.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. Do not install them in command; use command only to start the app.
# 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 | Static files plus a backend API |
Reserved URL paths
Section titled “Reserved URL paths”Keelson reserves only one URL namespace on an app’s host: /__keelson/*. Every other path belongs to the app. The platform does not take over common paths such as /assets, /files, /static, /uploads, or /api.
/__keelson/*is for platform internals. Keelson uses it for platform-served assets, file downloads, and internal endpoints. Do not define app routes under this path.- 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. - No other path is reserved. Framework defaults such as
/assets/*, Vite’s default output path, can be served without configuration changes. - 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.
Framework static-path collision matrix (reference)
Section titled “Framework static-path collision matrix (reference)”The following table lists the default static paths of common frameworks. None conflicts with /__keelson/*. This is reference information: “Verified” means the behavior has been checked in this repository; “Needs verification” is a knowledge-based estimate and must be checked before it is used as evidence.
| 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.
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 | A general request timeout applies |
| Scheduled job (cron) | 1–600 seconds; default 300 seconds, with the same maximum on every plan |
| Build | Limited; practical duration depends on the amount of dependencies |
Outbound connections
Section titled “Outbound connections”- Apps can connect to external APIs and services by default.
Common failure patterns
Section titled “Common failure patterns”| Symptom | Cause | Resolution |
|---|---|---|
| Build succeeds, but the app exits immediately after starting | 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 |
Build fails with reserved_path_conflict | A __keelson directory exists at the root of the served build output | Rename the directory; only /__keelson/* is reserved |
| 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 | The work was deferred until after the response was returned, so it never ran | Finish it before responding — or write it to a queue table in the database, drain it from a crons entry, and tell the user the wait is up to one interval |
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 — Check startup, the health check, and URL access, not only build completion.
- 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.