Title: Keelson Deploy Spec Spec-Version: 2026-08-17 Last-Updated: 2026-08-17 Canonical-URL: https://keelson.dev/docs/reference/deploy-spec/ Raw-URL: https://keelson.dev/docs/reference/deploy-spec.txt Language: en --- **Spec version:** 2026-09-02 / **Raw text (for AI agents):** [`/docs/reference/deploy-spec.txt`](/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](/docs/deploy/deploy/). --- ## 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: 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://.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. --- ## Required file Every deployment requires a `keelson.yaml` file in the project root directory. Minimal configuration: ```yaml slug: my-app runtime: python-slim command: "python app.py" ``` Framework-based apps such as Flask require a production server. See [Production hardening](#production-hardening). See the [keelson.yaml reference](/docs/reference/keelson-yaml-reference/) for details about every field. --- ## 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 `, 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 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 - **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 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 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 Keelson provides fixed build and runtime environments. The app must be able to build and start in those environments. ### Operating system and architecture - **OS:** Linux - **CPU:** x86_64 (amd64) ### Root privileges Apps run as a non-root user. They cannot use `sudo`, run `apt-get install`, or make system-level changes. ### 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 | 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 `/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](/docs/building-apps/files-and-media/). ### Port - 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. ### System packages 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. ### Process model - 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. --- ## 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 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. ```yaml # Python command: "python app.py" # Node.js command: "npm start" # Go (Keelson builds `./app` during deployment) command: "./app" ``` ### Packages with native dependencies 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. ### 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 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 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) 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 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](/docs/deploy/environment-variables/) for details. ### 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 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 **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: ```yaml 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: ```yaml 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: ```yaml 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: ```yaml command: "npm run start" ``` Set the package script to: ```json "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: ```yaml 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 Defaults must be safe for production. In Python, default `DEBUG` to false: ```python DEBUG = os.environ.get("DEBUG", "false").lower() == "true" ``` Do not default it to true: ```python DEBUG = os.environ.get("DEBUG", "true").lower() == "true" ``` Debug pages can expose environment variables, including database authentication tokens. ### Detecting Keelson at runtime Use `KEELSON_MODE`, which Keelson sets to `keelson`, to detect the platform: ```python 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 | 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 - Apps can connect to external APIs and services by default. --- ## 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 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`. --- ## Expected behavior for AI agents When deploying an app to Keelson, an AI agent should make decisions in the following order. ### Decision flow 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. ### 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 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. --- ## Related documentation - Deploy an app: https://keelson.dev/docs/deploy/deploy/ Deployment steps and lifecycle - Environment variables and secrets: https://keelson.dev/docs/deploy/environment-variables/ How to manage API keys and configuration values - App URL: https://keelson.dev/docs/deploy/public-url/ The URL issued after deployment - keelson.yaml reference: https://keelson.dev/docs/reference/keelson-yaml-reference/ Definitions of every configuration field - Build and runtime: https://keelson.dev/docs/building-apps/supported-app-types/ Details of the build process and runtime environment