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

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.


A completed build alone does not make a Keelson deployment successful. A deployment succeeds only when all of the following conditions are met:

  1. The app build has completed.
  2. The app process has started.
  3. The app has passed its health check.
  4. An app URL (https://<slug>.keelson.run) has been issued.
  5. 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.


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

Minimal configuration:

slug: my-app
runtime: python-slim
command: "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.


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.


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.

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.

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 directoryNo; not a supported write locationNoSource code and dependencies
/tmpYes, temporarilyNoTemporary files
Other pathsNo
  • 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 /data is 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 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 background work. 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. Do not install them in command; use command only to start the app.

# 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 requiredStatic files plus a backend API

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 __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.
  • 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.endpoints path cannot start with /__keelson; it must start with /api/external/ or /api/webhooks/. Reserved words also cannot be used as a slug.

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.

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.


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 requestA general request timeout applies
Scheduled job (cron)1–600 seconds; default 300 seconds, with the same maximum on every plan
BuildLimited; practical duration depends on the amount of dependencies

  • Apps can connect to external APIs and services by default.

SymptomCauseResolution
Build succeeds, but the app exits immediately after startingServer 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
Build fails with reserved_path_conflictA __keelson directory exists at the root of the served build outputRename the directory; only /__keelson/* is reserved
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 arrivesThe work was deferred until after the response was returned, so it never ranFinish 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
  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 — Check startup, the health check, and URL access, not only build completion.
  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.