Questions? Email [email protected] · All prices are one-time unless stated otherwise
D
Datapad Projects
Self-hosting

Running the server

Run driftlessd with Docker or directly, where its data lives, and the available flags.

One binary, two surfaces

The server is a single program, driftlessd. It serves the client sync API under /v1 and the web admin under /admin from the same address. There is no database server or cache to run alongside it.

Run with Docker

The recommended way to run Driftless is the bundled Compose file:

docker compose up --build

This builds the image and starts the container with port 8080 published and a named volume mounted at /data. The image is built locally from the repository; it is not yet published to a registry.

Run directly

You can also run the server without Docker. Build the embedded admin UI once, then start the server:

npm --prefix web install
npm --prefix web run build
go run ./cmd/driftlessd

By default it listens on :8080 and writes data to ./data.

Flags and environment

Each flag falls back to an environment variable when it is not given on the command line, so the container can be configured entirely through the environment:

  • -data DIR / DRIFTLESS_DATA — directory for the database and chunk store. Default ./data (the container uses /data).
  • -addr ADDRESS / DRIFTLESS_ADDR — listen address. Default :8080.
  • -token-ttl DURATION / DRIFTLESS_TOKEN_TTL — sign a device out after this long without syncing. Default 180d; 0 (or never) means tokens never expire.
  • -session-ttl DURATION / DRIFTLESS_SESSION_TTL — sign an administrator out of the web admin after this long without activity. Default 7d; 0 never expires.
  • -login-burst N / DRIFTLESS_LOGIN_BURST — how many sign-in attempts one client address may make back to back before it has to wait. Default 10, refilling one attempt every 10 seconds; 0 turns the throttle off.
  • -trust-proxy / DRIFTLESS_TRUST_PROXY — a reverse proxy sits in front, so the real client address comes from X-Forwarded-For. Off by default; only turn it on when a proxy you control is the sole way in.
  • -max-chunk-size SIZE / DRIFTLESS_MAX_CHUNK_SIZE — largest accepted chunk upload. Default 16MiB.
  • -max-manifest-body SIZE / DRIFTLESS_MAX_MANIFEST_BODY — largest accepted manifest write, which is what bounds how many chunks a single very large file may list. Default 32MiB.

Durations are either a Go duration (720h, 30m) or a whole number of days (180d). Sizes take a binary unit suffix (16MiB, 512KiB) or a plain byte count. A flag given explicitly on the command line wins over the environment variable, and the server refuses to start on a value it cannot parse rather than quietly falling back to a default.

How the sign-out limits behave

Both lifetimes measure idleness, not age: a device that keeps syncing keeps its token indefinitely, and an administrator clicking around the admin is never logged out mid-task. Only a credential that goes unused past the limit stops working, and the housekeeping pass deletes it within the hour.

Because the limit is applied when a credential is used rather than baked into it, changing the flag takes effect immediately for credentials that already exist — shortening it signs idle devices out on the next restart, and lengthening it (or setting 0) brings a lapsed device back without a new login, as long as its token has not yet been swept. To sign a device out right now, revoke it from the admin's Users page or run driftless logout on the device itself; see Security and remote access.

Health checks

The server answers GET /healthz without authentication. It returns 200 with the body ok when the database is reachable and 503 otherwise, so it can back a Docker HEALTHCHECK or an orchestrator's liveness and readiness probes.

What is stored

Inside the data directory you will find a SQLite database (users, tokens, file versions, and the chunk index) and a content-addressed blob store holding the file chunks, one folder per user (blobs/u1, blobs/u2, …). Because every user's data sits in their own folder, a quick du -sh blobs/* shows who is using what — the same numbers the admin's Users page reports.

Backing up

A backup is a copy of the whole data directory — under Docker, the driftless-data volume — plus every extra storage location you have added (see below). Two things are worth understanding before you design one:

  • The database and the chunks belong together. The blob folders hold content-addressed pieces, not browsable copies of your files; only the database knows which pieces make up which version of which file. Either half alone restores nothing.
  • The clean way to copy them is with the server stopped. Nothing is being written then, so the database and the chunks are guaranteed to agree.

Under Docker that is three commands — stop, archive the volume, start:

docker compose stop
docker run --rm -v driftless-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/driftless-backup.tar.gz -C /data .
docker compose start

Running directly, stop the process and copy (or rsync) the data directory somewhere safe.

If you would rather not stop the server, snapshot the database with SQLite's own backup command — sqlite3 /data/driftless.db ".backup /backup/driftless.db", which is safe against a live writer — and copy the blob folders afterwards. Chunks are immutable, so a later blob copy holds everything the earlier snapshot referenced, with one exception: version pruning and the hourly cleanup can remove a chunk in between, which would leave an old version of a file unrestorable. For a backup you can fully rely on, stop the server.

Restoring

Restoring is a file copy, not an import: there is nothing to re-index.

  1. Stop the server.
  2. Put the data directory back where it was, and every extra storage location back at the same path it had — the database records locations by path.
  3. Start the server. It runs any pending schema migrations and serves as before.

Your devices keep their own copies throughout, so nothing on them is lost. One thing to do afterwards: a device that synced after the backup was taken has looked further ahead than the restored server has, and will sit quiet until the server catches up. Give each device a fresh start by deleting its state.json (see Desktop client for where that lives) and syncing again. It re-examines every file: identical content is recognised and skipped, and anything that genuinely differs is kept both ways rather than overwritten, so the newer copies on your devices come back to the server as conflict files you can review.

More disks: extra storage locations

When the data directory's disk fills up, you can add more storage locations from the admin UI (Server ▸ Storage locations) without moving anything: new chunks are written to whichever location has the most free space, and existing chunks stay where they are. Under Docker, mount the extra disk into the container first (for example -v /mnt/disk2/driftless:/data2) and then add /data2 in the admin. A location can be removed again only while it holds no chunks, and remember that a backup now spans every storage location, not just the data directory.

Replacing a drive

To upgrade or retire a disk, use move data off on its storage location (Server ▸ Storage locations):

  1. Add the new disk as a storage location (mount it into the container first under Docker).
  2. Click move data off on the old location. Every chunk is copied to your other locations — the emptiest disk first, so the new drive naturally fills — verified against its hash in transit, and only then removed at the source. Files stay fully available while the move runs, and progress shows in the admin.
  3. When the location reports empty, remove it, then unmount the old disk.

The move is safe to interrupt: chunks are copied before they are deleted, so a server restart mid-move loses nothing — run it again and it picks up where it left off. If a chunk fails verification the move stops with the chunk named, leaving everything in place.

Adding users from the command line

Besides the admin UI, you can create a sync user with the adduser subcommand. The password is read from standard input:

driftlessd adduser -data /data alice

Stopping and restarting

On SIGINT or SIGTERM the server stops accepting new connections and lets in-flight requests finish before exiting; a live event stream is closed immediately so shutdown does not stall. docker compose stop and docker compose restart trigger this clean shutdown, and a second signal exits at once.

Upgrades

To upgrade, rebuild or pull the new image and restart. The database schema is versioned and migrations run automatically on startup, so pointing a newer server at an existing data directory is expected to work.

One upgrade is worth a note: the release that introduced per-user storage folders reorganizes the blob store on its first start. Existing chunks are moved into their owner's folder — almost entirely instant renames — and a chunk that several users happened to share is duplicated so each user gets their own copy, so leave a little disk headroom. The move is safe to interrupt (restarting resumes it) and the server only begins serving once it is done; a large store may take a moment on that first start.