drift Docs
Start
What is Drift?
The tour, if you are new here.
Why Drift?
The case for a smaller cloud.
Getting started
Nothing to deployed, in one command.
Architecture
How a slice is put together.
What it costs
The free grant, five 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.

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. The deploy in step 7 creates one for you.

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:

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

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.

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:

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:

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 source file with a one-line directive above the function it decorates. The directive, not a config file, declares how the function is reached: the HTTP method, the route, and whether it needs authentication. 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"

// @atomic http=post:sign auth=none
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"

// @atomic http=get:entries auth=none
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. The directive does the routing, where http=post:sign means "POST, reachable at sign", so there's no router to wire up.

The directive must sit directly above a named function.

Capitalised in Go, pub fn in Rust, a named function rather than an arrow in Node. Blank lines in between are fine, other code is not. An @atomic line floating at the top of the file above the imports decorates nothing, and the CLI refuses the deploy rather than silently shipping zero functions.

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, and the collection springs into existence the first time you write to it.

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: the resources your slice needs and how big they are, all at the top level (the file is the project). Save this as Driftfile at the project root:

Driftfile
name: guestbook
backbone:
  nosql:
    - { name: guestbook, size: 50MB }
canvas:
  sites:
    - ./canvas

The functions aren't listed. With flat files under atomic/ the CLI discovers every @atomic function for you, so there's no list to keep in sync. You only reach for atomic.functions to pin the folder-per-function layout or to attach per-function options.

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

That number is both the billing driver and the enforced quota (a write that would push the item past it is rejected with a 413) so Drift won't guess it for you. Queues take a name and nothing else (their bound is slice-wide, backbone.queue_max_depth), cache entries take a file or value plus a TTL, and secrets are a plain KEY: value map.

7. Deploy everything

One command reads the Driftfile, creates the slice, provisions the collection, compiles and ships both functions, and publishes the site:

Before it changes anything, the CLI prints exactly what it will create and what that costs, then waits for a y:

Slice "guestbook" does not exist. Will create:
    atomic.functions: 2
    backbone.nosql_collections: 1
    backbone.nosql.guestbook: 50MB

    Atomic functions         2 x €0.05 = €0.10
    Function memory (MiB)    32 x €0.03 = €0.96
    Storage (per GiB)        0.0488 x €0.25 = €0.01

  Cost: €1.07/month

  Apply? [y/N]

The memory line appears even though the Driftfile never mentions memory. atomic.function_memory is optional, but a slice that runs functions can't run them in zero memory, so an omitted value resolves to 32MB, and the resolved shape is what gets priced. Declare it to buy more; anything from 32MB to 256MB is accepted, at €0.03 per MiB per month. See what it costs for the full price list and the free grant.

Deploys only ever create or grow a slice. A Driftfile that declares less than the live slice is refused rather than applied, because shrinking deletes data the manifest can't know about. To apply a shrink deliberately, use drift slice resize --from Driftfile --allow-destructive. To see the plan without deploying anything, add --plan.

8. 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

9. 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 in its own directive: // @atomic http=post:sign auth=none secrets=API_KEY. 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 secrets= 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.

Shaping the slice yourself

Step 7 created the slice as a side effect of deploying. To create one up front instead, pick the path that fits:

CommandWhat it does
drift slice create <name>Opens the create form in the terminal dashboard: every dial, with the price recomputed as you change a value.
drift slice create <name> --freeTakes the fixed free Hacker preset with no form, so it works in CI and over SSH.
drift slice create --from DriftfileBorn at the shape the manifest declares, the same shape a first project deploy would have produced. Add -y to skip the cost prompt.

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>.

The free Hacker preset is fixed.

The platform discards any config sent with it, and you get one per account. Deploying a Driftfile into a free slice is normal, because the preset's spare capacity is slots you never asked for, so a smaller manifest is not treated as a shrink. Declaring a NoSQL collection with a size still grows the slice, because the grant hands out collection slots but no per-collection storage quota. What decides whether it stays free is the price of the resulting shape, and the prompt shows that as free → €N/mo before anything happens.

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

# @atomic http=post:sign auth=none
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");

// @atomic http=post:sign auth=none
async function postSign(body, req) {
    const id = await drift.backbone.nosql.collection("guestbook").insert(body);
    return [201, "Created", { id }];
}

module.exports = { postSign };

drift project deploy handles all of them the same way: it walks atomic/, finds every decorated function whatever the language, and ships them together. drift atomic deploy <folder> is the single-function path instead. It takes one folder and deploys the first @atomic annotation it finds in it, so point it at a folder holding one function, not at a folder holding several.

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, with no Drift-specific files in the archive:

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:

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:

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

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