Skip to content
BuildStackHQHosting
All docs

Docs

Hosting apps

The five minute version: a Node API with a database on your own domain, from the CLI or the dashboard.

Keel hosts four kinds of thing, all in Frankfurt (AWS eu-central-1), all deployed with the same CLI:

Kind What it is Create it with Price
Agent A run that starts on a schedule, a webhook or by hand, with every model and tool call traced keel init --template agent, keel deploy, keel run 79 EUR per agent per month, 50,000 steps included
Service or API An always-on container with a public HTTPS hostname, restarted when it crashes, rolled out replica by replica keel init --template api-node, keel deploy, keel service start 79 EUR per replica-month
Static site A built frontend served from the Keel edge on a Keel hostname or your own domain keel sites deploy ./dist included on Pro and Enterprise
Database Managed Postgres or Valkey, backed up, injected into services as a secret keel db create postgres, keel db create valkey Postgres 19 EUR, Valkey 9 EUR per month

Custom domains are included for services and sites on Pro and Enterprise: keel domains add <hostname>. Services can run more than one replica: keel services scale <slug> --replicas 2.

This page is the five minute version for a Node API with a database on your own domain. Services and APIs, the ingress notes and The CLI have the details behind each step, and examples/ has runnable starting points for Node, Python, Go and a static site.

From the browser, without the CLI

Everything below also works from the dashboard. Press New on the Agents or Services page and pick what to host:

Kind Where it goes What happens
Agent or Service /agents/new Name it, then either Connect GitHub (install the Keel GitHub App, pick a repository and branch, leave the Dockerfile path empty to have one generated for Node, Bun, Python and Go) or Upload (drag the project folder, a .zip or a .tar.gz, up to 100 MB compressed; node_modules, .git, virtual environments and .env files are skipped because the platform installs dependencies during the build and your local secrets should stay local). A service asks for the port it listens on, a health check path, the replicas and an optional workspace. Add environment variables, plain or marked secret, review the plan, and press Deploy: they are stored on the agent (Environment and secrets) before the build starts, so a plain one is editable later without a deploy and a secret one lives in the vault. You land on the agent page with the build log streaming; when the image is pushed the first revision goes live and a service shows its hostname. Later versions: Upload new version on the agent page, or push to the branch when the repository is connected.
Static site /sites/new Name it, drag the build output (dist, out, public, or an archive of it; it needs index.html at its root), choose the SPA fallback and 404 page, deploy. The public URL is shown when the deployment is live.
Database /data-stores Create a Postgres or Valkey store; the connection URL becomes a secret you declare from the agent page or keel.yaml.

The browser path and keel deploy produce the same thing: an immutable revision whose image was built by the platform. What the wizard collects (port, health check, replicas, workspace) is what keel.yaml would declare, and its environment variables are the per-agent layer that overrides the file's environment:; keel deploy on the same agent later replaces the image and takes its settings from the file.

0. Install and sign in (one minute)

npm install -g @keelhosting/cli
keel login

keel login opens the browser for a device authorisation and stores the token in the OS keychain. If you belong to more than one organization it asks for keel org use <slug>.

1. Scaffold the API (one minute)

mkdir hello-api && cd hello-api
keel init --template api-node

Without --template, keel init in a terminal shows a picker:

What do you want to host?
  1. agent       A Node agent that runs on a trigger and writes a result, with every model and tool call traced
  2. bot         A Telegram bot in Python that stays up as a service and keeps its state in a workspace
  3. api-node    A Fastify API in TypeScript served on a public hostname, with an optional Postgres database
  4. api-python  A FastAPI service served on a public hostname, with an optional Postgres database
  5. api-go      A Go net/http API served on a public hostname, with an optional Postgres database
  6. static      A plain HTML and CSS site deployed with keel sites deploy and served on your own domain
Template [1-6, default agent]:

In a pipe or CI it does not prompt and scaffolds the node agent as before. The api-node template writes keel.yaml, a Dockerfile, package.json, src/server.ts, src/app.ts, src/db.ts and a Vitest test, the same files as examples/api-node. The important part of keel.yaml:

name: hello-api
slug: hello-api
mode: service
service:
  kind: app
ingress:
  port: 8080
egress:
  customHosts: []
secrets: []

mode: service keeps one container up; service.kind: app says it is a regular backend rather than an AI agent, which is a declaration about the workload and no longer changes the price; ingress.port is the port your server listens on. The API listens on PORT (8080 by default) and answers GET / and GET /healthz.

Run it locally first:

pnpm install
pnpm dev
curl localhost:8080/healthz

2. Deploy (two minutes, mostly the build)

keel deploy
keel service start hello-api
keel service status hello-api --follow

keel deploy builds the image, registers it, creates a revision and prints the public hostname:

Revision rev_01JA2K is live in eu-central-1
Egress: deny-all, allowlist v12, 0 custom hosts
https://hello-api-0a1b2c3d.svc.keelhosting.dev

Open the hostname. The first deploy of a service needs keel service start once; every later keel deploy is a rolling restart. Logs: keel logs hello-api --follow.

3. Add a database (thirty seconds)

keel db create postgres

This creates a managed Postgres in eu-central-1, backed up, and stores its connection string as the DATABASE_URL secret of your organization. Declare the secret in keel.yaml so the service receives it as an environment variable:

secrets:
  - DATABASE_URL

Then keel deploy. The template's /healthz now pings the database and answers 503 if it cannot reach it. keel db create valkey does the same for a Redis compatible cache and queue (VALKEY_URL). A declared secret must have a value or the container refuses to start, which is why the template ships with secrets: [].

4. Put it on your domain (thirty seconds plus DNS)

keel domains add api.example.com

The command prints the CNAME to add at your DNS provider, waits for it to resolve, issues the certificate and starts serving. The Keel hostname keeps working next to the custom one. keel domains list shows the state of every domain and its certificate.

5. Scale and iterate

keel services scale hello-api --replicas 2
keel deploy
keel runs steps <run-id>

Replicas are billed at 79 EUR per replica-month, pro-rated by the hour, whatever the service.kind. Deploys roll out replica by replica: a new replica comes up and passes its health check before the old one stops. Every outbound call your API makes is in the trace, so a 503 from a third party shows up as a step with its response, not as a line in stdout.

Decide what the trace records

Every outbound call your app makes is a step in the trace, and by default Keel records the whole request and response body of each one. That is what makes a 503 from a third party readable instead of a mystery. It is also a copy of whatever went over that wire, kept for your plan's retention window.

If your app handles other people's data, set the level before you run it:

trace:
  capture: metadata
  • full (the default) records both bodies in full, minus your own secret values.
  • structure records the shape of each body and none of its text: keys, array lengths, value types and lengths, plus the model name, the tool name and the token counts.
  • metadata records no body at all. The step still carries its timing, host, HTTP status, token counts and error class.

You can also change it on the agent page in the dashboard, which states plainly what the current level records; changing it there creates a new revision. Keel enforces whichever level you set: below full the gateway never writes the body, so there is nothing to delete afterwards and nothing to leak.

The honest cost: a metadata-only app is much harder to debug. The trace will tell you a call took 4.2 seconds and returned 503, and nothing about what came back. The level is per app and per revision, so the usual answer is full on everything except the one app that reads a customer's mailbox or ledger. the gateway notes has the full table and the reasoning.

What is different from a generic host

  • Egress is deny-all. Every host the API calls out to goes under egress.customHosts in keel.yaml; model providers on the verified list need no entry. A missed host shows up as an egress_denied step in the trace, not as a silent timeout.
  • Secrets are set once with keel secrets set NAME and injected at start. They never live in the image, in keel.yaml or in a .env on a box.
  • Per-app configuration lives on the app, not in the image: keel env set my-api LOG_LEVEL=debug, keel env set my-api STRIPE_KEY=sk_live_... --secret, or paste a .env on the agent page in the dashboard. Plain values and secrets are applied on the next deploy or keel service restart; the dashboard shows a restart banner until every replica runs with the current set. keel env pull my-api > .env gives owners an audited copy for local development (Environment and secrets).
  • Inspected hosts are terminated by the Keel gateway. The Dockerfile from keel init points SSL_CERT_FILE, NODE_EXTRA_CA_CERTS and friends at the Keel CA bundle, so Node, Python, Go, curl and the AWS SDKs need nothing else.
  • What the trace records is a per-app setting, not a platform decision. See "Decide what the trace records" above.
  • Everything, including logs, traces, backups and build artifacts, stays in eu-central-1.

Migrating from a generic host walks through moving an existing app from Railway, Render, Fly or Heroku.