drift Docs
Start
What is Drift?
The tour, if you are new here.
Use cases
Whether Drift does your thing.
Getting started
Nothing to deployed, in one command.
Architecture
How a slice is put together.
What it costs
The free grant, four unit prices, two rules.
Build
Canvas
Static sites, same origin as your API.
Tools
Operate
Auth
Accounts, tokens and scopes.
Security
Boundaries, sandboxing and hardening.
Troubleshooting
Error codes
What went wrong, and what to do about it.
Legal
Acceptable use
What a slice may not be used for.
Data processing
The DPA, and every sub-processor.

Getting started

We'll build a small guestbook: a page with a form, an API that stores and lists the messages, and a database behind it. Along the way you'll touch all three Drift services: Atomic for the API, Backbone for the data, and Canvas for the site, and never leave your terminal.

Everything you deploy lands in a slice: your project's own private corner of Drift, with its own functions, data, site and URL, isolated from everyone else's. You make one in step 7, and deploy into it in step 8.

1. Install the CLI

Everything on Drift happens through the drift command-line tool. Every release publishes two install paths. Homebrew is the shortest, because it fetches a prebuilt binary, so there's no toolchain to install and nothing to compile:

brew install ondrift/tap/drift

Already have Go? This builds the same CLI from source:

go install github.com/ondrift/cloud/cli/cmd/drift@latest

Either way, confirm it's there. Two numbers come back and they move independently: the version of this binary, and the version of the Driftfile schema it implements.

drift --version

2. Get an account

Drift is in closed alpha, so signing up needs an invite code. A code is minted for one specific email address and works once. Redeem it and choose your own username and password:

drift account create --invite-code <code>

The CLI asks for a username (2–32 lowercase letters and digits, with no hyphens or underscores), your email address, and a password of at least eight characters. The email has to be the one the code was issued for; anything else is refused. The invite code stands in for the emailed verification code, so there's no second round trip to wait for.

In CI, keep the password out of shell history.

Pipe it in rather than passing it as an argument, so it never appears in ps: echo "$PASS" | drift account create -u alice -e alice@example.com --password-stdin --invite-code <code>

Creating the account logs you in. On another machine, sign in with the credentials you chose:

drift account login

The session lands in ~/.drift/session.json, bound to a per-machine device ID, so this is a one-time step on each machine.

3. Lay out the project

A Drift project is folders and a Driftfile. Atomic functions are flat source files directly under atomic/, with no per-function folders and no list to maintain; your site lives under canvas/. Here's the shape we're about to build:

  • guestbook/the project root
    • atomic/every function, discovered flat
      • sign.goPOST, save a message
      • entries.goGET, list messages
    • canvas/the site, served at /
      • index.htmlthe page visitors see
    • Driftfilewhat the slice is made of

4. Write the API

An Atomic function is a normal function in a normal source file. Nothing in the code marks it as special — no decorator, no framework, no base class — because how it is reached is declared in your Driftfile in a moment. Here's the endpoint that saves a message, in Go (atomic/sign.go):

atomic/sign.go
package main

import drift "github.com/ondrift/cloud/sdk/go"

func PostSign(body map[string]any, _ drift.Request) (int, string, any, map[string]string) {
    id, _ := drift.Backbone.NoSQL.Collection("guestbook").Insert(body)
    return 201, "Created", map[string]any{"id": id}, nil
}

And the one that lists messages back (atomic/entries.go):

atomic/entries.go
package main

import drift "github.com/ondrift/cloud/sdk/go"

func GetEntries(_ drift.Request) (int, string, any, map[string]string) {
    all, _ := drift.Backbone.NoSQL.Collection("guestbook").List(nil)
    return 200, "OK", all, nil
}

Every function returns the HTTP status, a short status message, and the response body (the SDK serialises objects to JSON for you). Go adds a fourth value, a response-headers map, nil when you don't need one; Python, Node, Ruby, PHP and Rust return the three values only. There is no router to wire up and no main() to write — Drift generates the entry point around your handler.

A handler has to be reachable from outside its own file.

Capitalised in Go, pub fn in Rust, a named function rather than an arrow assigned to a const in Node. The generated entry point imports it by name, so a lowercase handler cannot be bound — and the CLI says so before it builds anything, naming the callables it did find.

There is no database to set up.

No connection string, no ORM, no migrations. drift.Backbone.NoSQL.Collection("guestbook") is the data layer, and the SDK talks to Backbone for you. The collection does have to be declared before you write to it — step 6 below is where guestbook gets declared, and a write to a collection the slice does not have is refused with 400 rather than creating one.

5. Add the page

Canvas hosts your static site. Its best trick: a Canvas page can call its own functions on the same origin, so the browser just fetches /api/sign and /api/entries, with no CORS to configure and no separate API domain. Save this as canvas/index.html:

canvas/index.html
<h1>Guestbook</h1>
<form id="form">
  <input name="name" placeholder="Your name" required>
  <input name="message" placeholder="Your message" required>
  <button>Sign</button>
</form>
<ul id="entries"></ul>
<script>
  fetch("/api/entries").then(r => r.json()).then(rows => {
    rows.forEach(e => entries.innerHTML += `<li><b>${e.name}</b>: ${e.message}</li>`);
  });
  form.onsubmit = async (ev) => {
    ev.preventDefault();
    await fetch("/api/sign", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(Object.fromEntries(new FormData(form)))
    });
    location.reload();
  };
</script>

6. Declare it in a Driftfile

The Driftfile ties the project together: it names the slice this project deploys into, and says which of your source files fill which of its slots. It does not say how big anything is — that is the slice's shape, and you choose it in step 7. Save this as Driftfile at the project root:

Driftfile
slice: guestbook          # the slice this deploys into
atomic:
  functions:
    - route: entries
      method: get
      handler: GetEntries
    - route: sign
      method: post
      handler: PostSign
backbone:
  nosql:
    - slot: guestbook     # the collection this project uses
canvas:
  sites:
    - ./canvas

This list is the whole declaration. name is what the function answers on — post:sign means "POST, reachable at /api/sign" — handler is which callable in atomic/ serves it, and memory is what it books. A function the manifest does not name is not deployed at all, which is deliberate: one that reached production without appearing here would be one the platform could neither size nor price.

Anything else in your source is a helper. It costs nothing, is never routed, and can sit in the same file as a handler without changing either fact.

Both of these do very little: read or write one collection and return, so they book near their floor and cost pennies. That floor is 16MB here because these are Go: a compiled function carries its own language runtime per invocation, where an interpreted one shares a language server and can book as little as 8MB. If you don't know what to write, deploy once and run drift file benchmark — the slice measures every invocation and will tell you what each function actually needed.

NoSQL collections, SQL databases and blob buckets each need their own size.

Each entry names a slot the slice already holds and says what to put in it: a collection takes a slot and an optional seed or ttl, queues take a name and nothing else, cache entries take a file or value plus a TTL, and secrets are a plain KEY: value map. How big any of it is belongs to drift slice resize.

7. Make the slice

A slice is a thing you buy, so you choose its shape and see its price before it exists. That happens in a form the CLI draws in your terminal:

drift slice create guestbook

The form carries every dial: how many functions and the memory each books, collections, databases, queues, blobs, secrets, retention, and the billing period. The itemised monthly total recomputes as you change a value, and the same code prices the form and the invoice, so the number you agree to is the number you pay. Submit it and the slice exists and becomes your active one.

For this guestbook you need two functions and one collection. Two functions at 16MB each is 32 MiB of function memory, at €0.03 per MiB per month, plus a few cents of storage — call it €0.97/month, and the form will show you the exact figure.

Or take the free one and skip the form.

drift slice create guestbook --free provisions the whole Hacker grant — five function slots, two collections, a site — at €0, one per account, with nothing to answer. It covers this guestbook comfortably. Its slots arrive under placeholder names and book 8 MB each, so run drift slice resize once to make them the two 16 MB Go slots this project needs — that stays free, and the free grant explains why.

The function itself is free. You pay for the memory it books and the disk its deployed code occupies, and nothing for its existence — so splitting one function into six costs what the one cost, as long as the memory adds up the same. See what it costs for the full price list and the free grant, and memory and concurrency for what that booking buys you at run time.

8. Deploy everything

Now one command reads the Driftfile, seeds the collection, compiles and ships both functions, and publishes the site into the slice you just made:

drift file apply

To see what it would do without doing it, add --plan: it prints the functions, sites and resources the file declares, and applies nothing.

Deploying never changes the slice's shape.

drift file apply fills the slots you bought; it does not create a slice, grow one or shrink one. Applied against a slice that does not exist it stops and names drift slice create rather than provisioning something you never priced. Changing the shape — in either direction — is drift slice resize, back in the same form.

9. See it live

Your slice answers at <username>-guestbook.ondrift.eu, with TLS handled for you. Open that URL to use the guestbook in the browser, or hit the API directly:

Shell
# sign the guestbook
curl -X POST https://<username>-guestbook.ondrift.eu/api/sign \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "message": "Hello from Drift!"}'

# read it back
curl https://<username>-guestbook.ondrift.eu/api/entries

10. Inspect it from the terminal

You can read and poke at your slice's data without leaving the shell:

Shell
# list what's in the guestbook collection
drift backbone nosql list --collection guestbook

# store a secret (read fresh on every invocation of a function that declares it)
drift backbone secret set API_KEY=sk-12345

# push a message onto a queue
drift backbone queue push notify '{"to": "alice", "text": "welcome!"}'

# cache a value for an hour
drift backbone cache set greeting "hello world" --ttl 3600

# check the slice's health and usage
drift backbone status

A function only receives the secrets it names.

They go on its own Driftfile entry, under secrets:. The runner fetches each named secret on every call and passes it in as DRIFT_SECRET_API_KEY, so changing a value takes effect on the next request with no redeploy. Adding a name to the list is the part that needs one.

Bare drift opens the same data in a full-screen terminal dashboard, with slices down the side, and Atomic / Backbone / Canvas panes for browsing functions, documents and sites.

The three ways to make a slice

Step 7 used the first. The other two exist for cases the form does not suit:

CommandWhat it does
drift slice create <name>Draws the shape in the terminal: every dial, with the price recomputed as you change a value.
drift slice create <name> --freeTakes the whole free Hacker grant with no form, so it works in CI and over SSH.

All three set the new slice as the active one, so the commands above know where to go. Switch between slices later with drift slice use <name>, and change a shape with drift slice resize, which opens the same form on what the slice already is.

The free slice's limits are fixed; its shape is yours.

Nothing you send can raise a free slice's limits, and you get one per account. Within them the slice is shaped in the form like any other: it arrives declaring five slots named slot-1slot-5, and you rename and resize them to the routes you actually serve — up to five slots in any sizes adding to 40 MB — without leaving the free tier. Do it before you deploy, because a route the slice does not declare is refused. What decides whether a slice is free is whether its shape fits inside the grant, not what the shape prices at.

Write in your language

Functions can be written in Go, Python, Node.js, Ruby, PHP, or Rust, and the CLI detects the language from your source files. The same "sign" endpoint looks like this in Python…

Python
import drift

def post_sign(body, req):
    entry_id = drift.backbone.nosql.collection("guestbook").insert(body)
    return 201, "Created", {"id": entry_id}

…and in Node.js:

Node.js
const drift = require("@ondrift/sdk");

async function postSign(body, req) {
    const id = await drift.backbone.nosql.collection("guestbook").insert(body);
    return [201, "Created", { id }];
}

module.exports = { postSign };

The handler's name is yours to choose — post_sign here, but it could be sign_guestbook as long as the Driftfile's handler: says so.

drift file apply handles every language the same way: it reads your Driftfile, finds each declared handler in its element's folder, and ships them together. drift atomic deploy <folder> is the narrower path — it ships only the functions your Driftfile declares in that one folder, leaving the rest of the project alone.

Rust takes one function per folder.

Go and the interpreted languages can carry several in one flat folder; Rust can't yet, and the CLI will say so if you don't.

Take your data and go

Drift has no lock-in. At any point you can snapshot a slice and download the whole thing: source code, database contents, secrets, and static sites. Your source comes back as you wrote it, with every Drift-generated wrapper stripped out, and the only Drift files in the archive are two manifests sitting beside the tree:

Shell
drift slice snapshot create --name my-backup
drift slice snapshot download <snapshot-id>

Optional: use your own domain

Your slice answers at <username>-guestbook.ondrift.eu, and you can point your own hostname at it, and Drift verifies ownership and issues the TLS certificate for you. Add a domain to your active slice:

drift slice domain add guestbook.example.com

The CLI prints two DNS records to create at your registrar:

# 1. prove you own the domain
_drift-challenge.guestbook.example.com.  TXT    "drift-verify=<token>"

# 2. route the hostname to your slice
#    (or an A record to the platform's IP at a zone apex, where CNAMEs aren't allowed)
guestbook.example.com.                   CNAME  ingress.ondrift.eu.

Once DNS has propagated, verify. Drift checks the TXT record and starts issuing the certificate:

drift slice domain verify guestbook.example.com

Watch it go live (and see every domain on the slice) with:

drift slice domain list

When the status reads live, your guestbook is served on your own domain, HTTPS and all.