The pocketbase driver no longer embeds PocketBase: Warp keeps its data as PocketBase collections on a server of its own and reaches it only through the REST API, the way the postgres driver reaches PostgreSQL. database.connection_string is the server's URL, and Warp signs in as the superuser in WARP_PB_SUPERUSER_EMAIL / WARP_PB_SUPERUSER_PASSWORD (or a WARP_PB_SUPERUSER_FILE of the two). Every SQL table is a superuser-only collection of the same name and columns, created by warp migrate. Reads go through PocketBase's SQL endpoint with the SQL drivers' queries, via a read-only database/sql driver that inlines arguments, moves long ones into warp_params and pages past 1000 rows. Writes are record operations in batch requests under one write lock in the Warp process. Media is a protected file field in warp_media, local or in the S3 bucket Warp writes into PocketBase's settings. Search matches word prefixes with LIKE. The storage tests run against an in-process PocketBase and pass as on SQLite. Docker: the PocketBase stacks get a pocketbase service running tajniak81/pocketbase-docker-root (WARP_PB_IMAGE), with a pb-data volume and its admin UI on loopback; the AIO images run that PocketBase binary as a process beside the homeserver, making up a superuser when none is set. The .env examples, READMEs, docs and warp.yaml.example follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.1 KiB
Docker: one container per component
The homeserver (warp), the admin panel (warp-admin) and the web client
(warp-web) as separate images, built from Dockerfile,
apps/admin/Dockerfile and apps/web/Dockerfile.
| File | Database | Images |
|---|---|---|
docker-compose.sqlite.yml |
SQLite, in the warp-data volume |
built here |
docker-compose.pocketbase.yml |
PocketBase, a container of its own, in pb-data |
built here |
docker-compose.postgresql.yml |
PostgreSQL 17, its own container | built here |
docker-compose.pocketbase.s3.yml |
PocketBase, media in an existing S3 bucket elsewhere | built here |
docker-compose.pocketbase.seaweedfs.yml |
PocketBase, media in SeaweedFS, one container | built here |
docker-compose.pocketbase.seaweedfs.split.yml |
PocketBase, media in SeaweedFS, one container per role, with its admin UI | built here |
docker-compose.*.prod.yml |
as above | pulled, never built |
cd Docker
cp .env.sqlite.example .env # then edit it
docker compose -f docker-compose.sqlite.yml up -d --build
docker compose -f docker-compose.sqlite.yml exec warp warp create-account --username admin --admin
Homeserver on :8008, admin panel on :8009, web client on :8080
(WARP_PORT, WARP_ADMIN_PORT, WARP_WEB_PORT in .env move them). Put a
TLS-terminating reverse proxy in front before exposing them; see
docs/deployment.md.
The PocketBase files run PocketBase as a container of its own, from
tajniak81/pocketbase-docker-root (WARP_PB_IMAGE picks another; any
PocketBase 0.40 or later will do). Its data is in the pb-data volume, and
the homeserver reaches it at http://pocketbase:8070, signed in as the
superuser in .env. The image creates that superuser on its first start
and leaves it alone after, so a new password is set in the admin UI first,
at http://localhost:8070/_/ on loopback, and in .env after.
Media in S3
The three pocketbase.s3 and pocketbase.seaweedfs setups keep the
PocketBase database in pb-data and put media in an S3 bucket
(WARP_PB_S3_BUCKET, default warp). Media already stored on the
filesystem is not moved into the bucket when S3 is turned on, so start
these on a fresh pb-data or copy the files across first. It is the
PocketBase container that talks to the bucket.
docker-compose.pocketbase.s3.yml: a bucket that already exists elsewhere (AWS, MinIO, a SeaweedFS on another host). SetWARP_PB_S3_ENDPOINT,WARP_PB_S3_ACCESS_KEYandWARP_PB_S3_SECRET. Path style is on by default; setWARP_PB_S3_FORCE_PATH_STYLE=falsefor AWS S3.http://host.docker.internal:8333reaches a gateway on the Docker host. The homeserver waits up to 30 seconds for the endpoint to answer before it starts.docker-compose.pocketbase.seaweedfs.yml: SeaweedFS as oneweed server -s3container, plus a one-shotseaweedfs-initthat creates the bucket. The gateway's one identity is the homeserver's key pair,WARP_PB_S3_ACCESS_KEYandWARP_PB_S3_SECRET.docker-compose.pocketbase.seaweedfs.split.yml: master, volume, filer, S3 gateway and admin UI (port 23646) as separate containers.seaweedfs-initwrites the homeserver's identity into the filer's IAM store on every start, the store the admin UI writes too, so keys added under Object Store → Users never displace it; the gateway does not start until that identity is there. SetSEAWEED_ADMIN_PASSWORDas well.
The S3 gateway and the admin UI are published on 127.0.0.1 only
(SEAWEED_S3_BIND, SEAWEED_ADMIN_BIND); master, volume and filer are not
published, since the volume server serves content by id with no
authentication. Both SeaweedFS files share a project and the
seaweed-data layout, so switching between them needs no migration.
SEAWEED_IMAGE pins the SeaweedFS release (4.45).
Prod files
The .prod.yml files pull $WARP_REGISTRY/warp, warp-admin and
warp-web at tag $WARP_VERSION (defaults 10.2.1.10:5500/admin and
latest). Push the images there first:
docker compose -f docker-compose.sqlite.yml build
for i in warp warp-admin warp-web; do
docker tag $i:latest $WARP_REGISTRY/$i:$WARP_VERSION
docker push $WARP_REGISTRY/$i:$WARP_VERSION
done
docker-compose.postgresql.prod.yml refuses to start without
WARP_POSTGRES_PASSWORD.
Settings
Every compose file has its own settings file, named after it:
docker-compose.<name>.yml takes .env.<name>.example, so
docker-compose.pocketbase.seaweedfs.prod.yml takes
.env.pocketbase.seaweedfs.prod.example. Each lists only the settings
its compose file uses, with their defaults, and names the ones that are
required; an empty value means the default. Copy the one you need to
.env, which compose reads for the ports, the registry and the passwords,
and passes to the homeserver, the admin panel and the web client. .env
is not committed. The prod examples start from a public server name
rather than localhost.
The compose files set only what makes each one what it is: the database driver and connection string, and the address the admin panel and web client reach the homeserver on.