Connect a service to a database
A service connects to a database with a connection variable: a variable you declare on the consuming service whose value is one of the database's published connection details. You name the variable, point it at the database, and your code reads it as an ordinary environment variable.
Outside the quickstart wizard — whose database toggles declare this variable for you — nothing happens automatically. Creating a database next to a web service wires nothing: there is no attach step, and no variable appears on its own. Each consumer declares its own connection variable; that declaration is the entire model.
The variable references the database by name in the same environment. Delete a database and recreate one with the same name, and the wiring reconnects on its own.
Your first connection
This walkthrough assumes a web service and a Postgres database named db in the same environment.
- On the environment canvas, click the consuming service to open its drawer.
- Open the Variables tab.
- Click Reference.
- Name the variable
DATABASE_URL. - In the Reference picker, choose the database's URL entry under From a database — for a database named
db, that isdb · DATABASE_URL, the full connection URL. - Click Add, then press Deploy (Shift+Enter) on the changes bar — a service's variable edits are staged with the rest of its changes.
- Verify: switch the tab's view from This service to Merged. The resolved view lists
DATABASE_URLwith its source,from db · DATABASE_URL.
For a Redis database the flow is identical — name the variable REDIS_URL and pick the Redis entry.
Read it in your code
Your service reads the variable from its environment like any other. With node-postgres:
import pg from "pg";
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });const { rows } = await pool.query("SELECT now()");With node-redis:
import { createClient } from "redis";
const redis = createClient({ url: process.env.REDIS_URL });await redis.connect();Every language works the same way: read DATABASE_URL or REDIS_URL from the environment and hand it to your client library.
Wire individual keys
The full URL is one of several published keys. A consumer can wire any single key as its own variable — useful for clients that take host and password separately, for example a PGHOST variable pointing at the HOST key.
Postgres publishes:
| Key | Carries |
|---|---|
DATABASE_URL | The full connection URL: postgres://koo:<password>@<host>:5432/koo |
HOST | The internal hostname — only resolves inside Koo |
PORT | 5432 |
USER | The username (default koo) |
PASSWORD | The password |
DBNAME | The database name (default koo) |
Redis publishes:
| Key | Carries |
|---|---|
REDIS_URL | The full connection URL: redis://:<password>@<host>:6379 |
HOST | The internal hostname — only resolves inside Koo |
PORT | 6379 |
PASSWORD | The password |
A connection variable's sensitivity is derived from its key — the URL and PASSWORD keys are sensitive automatically, and you cannot set it yourself. Sensitive values are withheld from ordinary reads and revealed only through an audited action.
Share one database between services
Every consumer that needs the database declares its own connection variable: three services, three declarations, one database.
To declare it once, define the connection variable at environment scope instead. Every service in the environment then resolves it, and a service-scoped variable of the same name still wins — resolution is nearest-wins across the project → environment → service chain.
One service can hold DATABASE_URL and REDIS_URL side by side — one variable per database it uses.
Use it from the API and CLI
A connection variable is one JSON body on the Variables API:
{ "name": "DATABASE_URL", "connection": { "service": "db" } }connection.key is optional and defaults to the engine's URL key (DATABASE_URL / REDIS_URL); set it to wire an individual key instead.
A service create can carry its variables atomically: pass variables[] on the create body and the service never boots without them — see the Services API.
In scripts, create the database before its consumers. A connection variable resolves by name, so a consumer created first runs without the variable until the database exists.
The CLI sets literal variables only — declare connection variables in the console or through the API.
One database per environment
A connection variable can only point at a database in the same environment — there is no cross-environment wiring. Each environment runs its own databases, and the name-keyed wiring is what keeps environments portable; see Use databases across environments.
Troubleshoot a connection
- The variable is missing from the container. A connection that fails to resolve is dangling: it is left out of the container entirely, never delivered as an empty string. The Merged view says why in plain words — the target service no longer exists, that connection key is not published, or the alias target resolves to nothing; the API reports the same reasons as
service_not_found,key_not_published, andalias_target_not_found. - Nothing changed after you edited the drawer. A service's variable edits are staged — check the changes bar and press Deploy.
- Connections are refused. A freshly created database shows In progress before Online; until it is online, your code cannot connect even though the variable resolves.
- A hand-built URL fails to authenticate. Passwords use only URL-safe characters and are embedded verbatim in the published URL — never percent-encode or decode them, and prefer wiring the URL key over assembling your own.
koo runcan't reach the database. That is expected, twice over: sensitive values are withheld from local runs, and the database's host doesn't resolve outside Koo. Verify a connection in the Merged view or in the consumer's logs, never from your laptop.- You can't name a variable
KOO_VARIABLES_REVISION. That name is reserved: Koo changes it whenever a service's variables change, which is what rolls the service automatically to pick up new values.