Driftfile
Your whole project declared in one file. Functions, data, secrets, and sites — all in one place, deployed with one command.
A Driftfile is the single source of truth for a Drift project. It declares the
Atomic functions,
Backbone data, and
Canvas sites your app needs, plus the resource limits it runs
within. You run drift project deploy from the project root, and the platform reads the
Driftfile, provisions everything, and wires it together.
The file
The file is named Driftfile — capital D, no extension, just like a
Makefile or Dockerfile. It lives at the project root, next to your
atomic/, backbone/, and canvas/ folders. The format inside is
YAML. The CLI looks for ./Driftfile where you run the deploy — there is no second
config file and no lockfile; multiple environments live in this one file under
environments:, not a forest of per-environment variants.
my-project/
├── Driftfile
├── atomic/
│ ├── submit/
│ └── validator/
├── backbone/
│ └── permit-types.jsonl
└── canvas/
├── public/
└── reviewer/
Anatomy
The Driftfile is the project. The top level carries the project name, the three
service sections — atomic, backbone, canvas — each
optional and independent (a static site needs only name and canvas; a data
pipeline needs only atomic and backbone), and two optional siblings:
environments (per-environment config overrides) and hooks (local
pre/post-deploy commands). Within a section, the envelope limits come first, then the resources that
run inside them.
name: my-app # required — the project / slice name
atomic: # optional — function limits + functions
function_memory: 128MB
function_timeout: 30s
rate_limit: 1000/min
functions: [ ... ]
backbone: # optional — data limits + resources
nosql_storage: 500MB
nosql: [ ... ]
sql: [ ... ]
queues: [ ... ]
secrets: { ... }
canvas: # optional — site limit + site(s)
canvas_size: 50MB
sites: [ ... ]
environments: # optional — per-environment config overrides
prod: {}
staging: { atomic: { rate_limit: 100/min } }
hooks: # optional — local pre/post-deploy commands
pre_deploy: [npm run build]
post_deploy: [./smoke.sh]
A complete example
This is the Driftfile from the intake-review template — a municipal permit
intake app with a public submission portal and a reviewer dashboard. It exercises all three services
and deploys as-is with drift project deploy. It deliberately spans several languages to
show the six-language promise holds — a proof, not the everyday shape. Most backends are one
language in flat files (one element), where the functions list
below is auto-discovered and can be dropped entirely.
name: intakereview
atomic:
function_memory: 32MB
function_timeout: 60s
rate_limit: 1000/min
functions:
- submit
- permit-types-list
- status-get
- reviewer-login
- reviewer-queue
- reviewer-submission
- reviewer-mailbox
- reviewer-blob
- decision-apply
- validator
- notifier
backbone:
nosql_storage: 500MB
blob_max_size: 10MB
blob_max_count: 200
queue_max_depth: 500
nosql:
- submissions
- audit-log
- mailbox
- name: reviewers
seed: ./backbone/reviewers.jsonl
- name: permit-types
seed: ./backbone/permit-types.jsonl
queues:
- validate
- notify
secrets:
MUNICIPALITY_NAME: "Amsterdam"
SUBMITTER_BASE_URL: "/status.html"
canvas:
canvas_size: 50MB
sites:
- ./canvas/public
- dir: ./canvas/reviewer
route: /reviewer
Project identity
The top of the file carries the project's identity. The Driftfile never declares a tier or a price — the cost is computed from the limits and resource counts you declare, per environment.
| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The project name; also the slice name. 1–32 lowercase letters, numbers, or hyphens. With environments, prod deploys under this bare name and others under name-<env>. |
log_retention | duration | platform default | How long Atomic invocation logs are kept (e.g. 72h, 7d). |
backup_retention | duration | platform default | How long Backbone backup snapshots are kept. |
Atomic — functions
The atomic section sets the function runtime limits. You usually don't list
functions at all: drop your handlers into atomic/ as flat source files and the CLI
auto-discovers every @atomic function (that flat set is the default
element — one language, one backend). A function's URL, method, and
authentication come from a one-line directive at the top of its source, never from here:
# @atomic http=post:submit auth=none # an HTTP endpoint
# @atomic http=get:status/:token auth=none # with a path parameter
# @atomic queue=validate auth=none # triggered by a queue message
The limits below apply to every function in the slice — there is no per-function override.
| Field | Type | Default | Meaning |
|---|---|---|---|
function_memory | size (MB/GB) | platform default | Max memory per invocation. Exceed it and the invocation is killed with an out-of-memory error. |
function_timeout | duration (s/m/h) | platform default | Max wall-clock time per invocation before it's killed. |
rate_limit | rate (N/s, N/min, N/h) | platform default | Max invocations across the whole slice per window. Excess requests get 429. |
deploy_history | integer | platform default | How many past deploys are kept per function for rollback (drift atomic rollback). Higher means more restore points, more storage. |
functions[] | string | map (optional) | auto-discovered | Usually omitted — functions are discovered from your flat atomic/* source. List bare names only to pin the legacy folder-per-function layout (./atomic/<name>/); long form { name, dir } overrides the directory. |
Read the Atomic guide → for how to write a function.
Backbone — data & state
The backbone section sets the data-layer limits and declares the resources to create:
document collections, queues, secrets, and cache entries.
| Field | Type | Default | Meaning |
|---|---|---|---|
nosql_storage | size (KB/MB/GB) | platform default | Total NoSQL document storage. Part of the slice's storage pool, billed per GiB. |
blob_storage | size | platform default | Total blob/object storage the slice reserves — the billing driver for blobs, pooled with the other storage and charged per GiB. |
blob_max_size | size | platform default | Maximum size of a single blob (file). A free safety quota, not a price driver. |
blob_max_count | integer | platform default | Maximum number of blobs the slice can store. A free safety quota, not a price driver. |
queue_max_depth | integer | platform default | Maximum messages in a single queue before producers are throttled. |
sql_storage | size (KB/MB/GB) | platform default | Per-database storage cap for SQL databases. A write past the cap is rejected with 413. |
secret_max_size | size | platform default | Maximum size of a single secret value (e.g. 4KB). |
locks | integer | platform default | Maximum number of distributed locks held at once across the slice (the drift.Lock primitive). |
realtime_connections | integer | 0 (off) | Maximum simultaneous live Realtime WebSocket connections across the slice. Omit or set 0 to disable Realtime. |
NoSQL collections
nosql is a list. A bare string creates an empty collection. The long form adds a
seed — a JSONL file (one JSON document per line) that the deploy upserts by each
document's _id.
nosql:
- submissions # empty collection
- name: permit-types
seed: ./backbone/permit-types.jsonl # seeded from JSONL
# permit-types.jsonl — one JSON document per line, each with an _id
{"_id": "residential-extension", "name": "Residential extension"}
{"_id": "new-build-residential", "name": "New-build residential"}
Seeding is idempotent (re-deploying re-upserts changed rows) and
non-destructive (documents your app added at runtime are left alone, because their
_ids aren't in the seed file). Every line must be valid JSON with a non-empty
_id.
SQL databases
sql is a list of per-slice SQLite databases, addressed by name. Each entry
materialises as one .db file and is reached from the SDK as drift.SQL("name")
for relational queries and transactions. A bare string creates an empty database; the long form adds a
schema and/or a seed.
sql:
- name: app
schema: ./backbone/app.sql # idempotent DDL — runs on every deploy
seed: ./backbone/app-seed.sql # runs only on the first deploy
schema is applied on every deploy, so keep it idempotent
(CREATE TABLE IF NOT EXISTS …). seed runs only when the database
has no user tables yet — the first deploy — so later deploys never re-run it, even
if you change the file. To re-run a seed, start from an empty database again: drop it with
drift backbone sql drop <name> and redeploy — the deploy recreates the database
and re-applies the schema and seed. A database name is 1–64 characters
([a-z0-9], then [a-z0-9_-], then [a-z0-9]) and becomes both the
.db filename and the SDK lookup key.
| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | required | Database identifier and SDK lookup key — drift.SQL("name"). |
schema | path | — | SQL file of idempotent DDL, applied on every deploy. |
seed | path | — | SQL file run only on the first deploy (when the database has no user tables yet). |
Read the Backbone guide → for querying SQL and using transactions from your functions.
Queues
A list of names. Queues can't be seeded — seeding messages on every deploy would re-fire work.
queues:
- validate
- notify
Secrets
A map of KEY: value. A value of $NAME (unquoted) is a reference resolved when
drift project deploy runs — the literal never touches the Driftfile or the wire, only
the resolved string travels. Quote a value to force a literal that starts with $.
secrets:
MUNICIPALITY_NAME: "Amsterdam" # hardcoded literal
RESEND_API_KEY: $RESEND_API_KEY # resolved at deploy time
A $NAME reference (and any ${VAR} placeholder elsewhere in the Driftfile)
resolves through a variable origin hierarchy, highest precedence first:
- Hardcoded — a literal value written in the Driftfile. Absolute.
- Environment — a variable exported in your shell session.
- Override flags —
--secret KEY=value(repeatable), and--env <name>(same as the positional environment; also setsENV). An override yields to a variable already set in the environment; it beats the env files. .env.<env>file — when an environment is selected, the per-environment secrets file (e.g..env.staging) next to the Driftfile. Out-ranks the base.env..envfile — the baseKEY=valuefile next to the Driftfile, sourced automatically (lowest precedence; fills only what nothing above provided). Pass--no-env-fileto skip the env files.
So an exported DB_PASSWORD beats --secret DB_PASSWORD=…, which beats a
DB_PASSWORD line in .env.staging, which beats one in .env. A
deploy prints what it loaded from which env file and what it ignored because the environment already
had it — the layering is never silent. Config dials that differ per environment belong in
environments:; only secrets belong in .env.<env>.
Cache
A map from key to a value to pre-warm at deploy. The short form is a file path whose
contents become the cached value. The long form takes an inline value (or a
file) plus an optional ttl in seconds — after which the entry expires
and a Cache.Get returns empty until your code re-populates it. Omit ttl (or
set 0) for an entry that never expires.
cache:
menu: ./backbone/menu.json # short form — file contents become the value
banner: # long form — inline value with a TTL
value: "Closed for maintenance"
ttl: 3600 # seconds; 0 / omitted = never expires
pricelist: # long form — file + TTL
file: ./backbone/prices.json
ttl: 86400
At runtime the cache is read/write from your functions — these entries are just the initial warm set. Read the Backbone guide →
Canvas — sites
The canvas section sets the total static-asset budget and lists your sites. A bare path
mounts a directory at /; the long form sets an explicit route for a sub-path
(handy for a public site plus an admin or reviewer area).
| Field | Type | Default | Meaning |
|---|---|---|---|
canvas_size | size | platform default | Total storage across all Canvas sites in the slice. |
sites[] | path | map | — | A bare path mounts at /. Long form { dir, route } mounts dir at route. |
canvas:
canvas_size: 50MB
sites:
- ./canvas/public # mounted at /
- dir: ./canvas/reviewer
route: /reviewer # mounted at /reviewer
Environments — per-environment deploys
Ship the same project to several places — production, a staging copy, a throwaway dev slice —
that share one shape but differ in a few config dials. Each environments entry is a partial
override that deep-merges onto the base; drift project deploy <env> selects one and
deploys it as its own slice.
atomic:
rate_limit: 5000/min # the base = production defaults
backbone:
log_retention: 30d
environments:
prod: {} # inherits the base unchanged
staging:
atomic: { rate_limit: 200/min }
backbone: { log_retention: 3d }
dev:
atomic: { rate_limit: 20/min }
- Scalar knobs (
rate_limit, retention, memory, counts) replace the base; the resource set (functions, collections, sites) is shared. - The slice name is derived:
nameforprod/production,name-<env>otherwise. Each environment is its own separately-priced slice with its own URL. - Config lives here; secrets that differ per environment live in
.env.<env>.${ENV}binds to the selected environment. - Short form:
environments: [prod, staging]— each inherits the base unchanged.
This is not a CI pipeline: there is no “deploy staging, test, promote prod” orchestration in the Driftfile. Drift gives the verb; your CI chains it.
Hooks — pre/post-deploy commands
Fold the build-and-verify ceremony into the deploy so it stays one command. Commands run locally, in order, through the shell from the project root, with the deploy environment exported; a non-zero exit aborts.
hooks:
pre_deploy: # before anything ships — typically a build
- npm --prefix web run build
post_deploy: # after the slice is live — typically a smoke test
- ./scripts/smoke.sh
Hooks are lifecycle commands, not a pipeline engine — no test stages, matrices, parallelism, or
remote execution. A failed post_deploy leaves the slice live but exits non-zero so you and
CI see it.
The smallest Driftfile
Every limit has a sensible default, so the minimum that deploys something is a name and a site:
name: hello
canvas: ./canvas
Deploy & validation
The CLI validates the whole Driftfile before any network call — bad sizes, missing
seed files, malformed JSONL, or an unset secret env var are all reported at once, with line numbers,
so you fix everything in one pass. On a valid file, drift project deploy shows the diff,
tells you whether the slice is free or its monthly cost, and asks to apply.
Deploys only create or grow a slice — drift project deploy never
shrinks one. To reduce limits or remove resources, use drift slice resize.