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/driftAlready have Go? This builds the same CLI from source:
go install github.com/ondrift/cloud/cli/cmd/drift@latestEither 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 --version2. 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.
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
-
atomic/every function, discovered flat
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):
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):
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.
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.
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:
<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:
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.
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 guestbookThe 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:
# 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/entries10. Inspect it from the terminal
You can read and poke at your slice's data without leaving the 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 statusA function only receives the secrets it names.
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:
| Command | What 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> --free | Takes 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.
slot-1…slot-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…
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:
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.
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:
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.comThe 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.comWatch it go live (and see every domain on the slice) with:
drift slice domain listWhen the status reads live, your guestbook is served on your own domain, HTTPS and all.