Version: closed alpha

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.

FieldTypeDefaultMeaning
namestringrequiredThe 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_retentiondurationplatform defaultHow long Atomic invocation logs are kept (e.g. 72h, 7d).
backup_retentiondurationplatform defaultHow 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.

FieldTypeDefaultMeaning
function_memorysize (MB/GB)platform defaultMax memory per invocation. Exceed it and the invocation is killed with an out-of-memory error.
function_timeoutduration (s/m/h)platform defaultMax wall-clock time per invocation before it's killed.
rate_limitrate (N/s, N/min, N/h)platform defaultMax invocations across the whole slice per window. Excess requests get 429.
deploy_historyintegerplatform defaultHow many past deploys are kept per function for rollback (drift atomic rollback). Higher means more restore points, more storage.
functions[]string | map (optional)auto-discoveredUsually 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.

FieldTypeDefaultMeaning
nosql_storagesize (KB/MB/GB)platform defaultTotal NoSQL document storage. Part of the slice's storage pool, billed per GiB.
blob_storagesizeplatform defaultTotal blob/object storage the slice reserves — the billing driver for blobs, pooled with the other storage and charged per GiB.
blob_max_sizesizeplatform defaultMaximum size of a single blob (file). A free safety quota, not a price driver.
blob_max_countintegerplatform defaultMaximum number of blobs the slice can store. A free safety quota, not a price driver.
queue_max_depthintegerplatform defaultMaximum messages in a single queue before producers are throttled.
sql_storagesize (KB/MB/GB)platform defaultPer-database storage cap for SQL databases. A write past the cap is rejected with 413.
secret_max_sizesizeplatform defaultMaximum size of a single secret value (e.g. 4KB).
locksintegerplatform defaultMaximum number of distributed locks held at once across the slice (the drift.Lock primitive).
realtime_connectionsinteger0 (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.

FieldTypeDefaultMeaning
namestringrequiredDatabase identifier and SDK lookup key — drift.SQL("name").
schemapathSQL file of idempotent DDL, applied on every deploy.
seedpathSQL 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:

  1. Hardcoded — a literal value written in the Driftfile. Absolute.
  2. Environment — a variable exported in your shell session.
  3. Override flags--secret KEY=value (repeatable), and --env <name> (same as the positional environment; also sets ENV). An override yields to a variable already set in the environment; it beats the env files.
  4. .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.
  5. .env file — the base KEY=value file next to the Driftfile, sourced automatically (lowest precedence; fills only what nothing above provided). Pass --no-env-file to 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).

FieldTypeDefaultMeaning
canvas_sizesizeplatform defaultTotal storage across all Canvas sites in the slice.
sites[]path | mapA 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

Read the Canvas guide →

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 }

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.

Ready to ship one? The Getting Started guide walks a Driftfile from zero to a live app, and the CLI reference covers every deploy command.