Docker: PocketBase with media in S3, SeaweedFS or split SeaweedFS

Six compose files for the separate-container setup, a build and a prod
file for each, modelled on DriverVault's Docker folder:

- pocketbase.s3: media in an existing bucket elsewhere; the endpoint
  and key pair are required, host.docker.internal is mapped, and the
  homeserver waits up to 30 seconds for the endpoint before starting.
- pocketbase.seaweedfs: SeaweedFS as one `weed server -s3` container
  whose only identity is the homeserver's key pair, plus a one-shot
  init that creates the bucket.
- pocketbase.seaweedfs.split: master, volume, filer, S3 gateway and
  admin UI as separate containers; the init seeds the bucket and the
  homeserver's identity into the filer's IAM store, and the gateway
  waits for it.

Only the S3 gateway and admin UI are published, on loopback. Both
SeaweedFS files share one project and data layout, so switching needs
no migration. .env.example gains the SEAWEED_* settings and leaves
WARP_PB_S3_FORCE_PATH_STYLE empty so the S3 file's default applies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
tajniak81andClaude Opus 5.5 committed 2026-10-22 20:52:38 +02:00
1 parent 332916e9db
commit 39583a9db0
8 files changed
+1011 -1

No files matched your search

@@ -0,0 +1,239 @@
# Warp on PocketBase with media in SeaweedFS split into its roles: the
# homeserver, the admin panel and the web client built from this checkout,
# plus SeaweedFS's master, volume, filer, S3 gateway and admin UI as
# separate containers.
#
# cd Docker
# cp .env.example .env # then edit it; set WARP_PB_S3_ACCESS_KEY,
# # WARP_PB_S3_SECRET and SEAWEED_ADMIN_PASSWORD
# docker compose -f docker-compose.pocketbase.seaweedfs.split.yml up -d --build
#
# The database stays in /data/pb_data (warp-data); media goes to the bucket
# WARP_PB_S3_BUCKET (default warp) in SeaweedFS (seaweed-data). Over
# docker-compose.pocketbase.seaweedfs.yml this buys SeaweedFS's admin UI,
# where S3 identities are created and revoked under Object Store → Users,
# per-role restarts, upgrades and metrics, and room for a second volume
# server. The S3 behaviour is the same. Media stored before S3 was on is not
# moved into the bucket.
#
# Homeserver http://localhost:8008, admin panel http://localhost:8009, web
# client http://localhost:8080. The S3 gateway (8333) and the SeaweedFS admin
# UI (23646) are published on loopback; SEAWEED_S3_BIND and
# SEAWEED_ADMIN_BIND=0.0.0.0 expose them, the admin UI only behind a proxy.
# Master, volume and filer are not published: the volume server serves file
# content by id with no authentication at all.
#
# Master, volume and filer share seaweed-data in the layout `weed server
# -dir` writes, and this file shares its project with
# docker-compose.pocketbase.seaweedfs.yml, so switching between the two
# needs no migration.
#
# Create the first administrator once the server is up:
#
# docker compose -f docker-compose.pocketbase.seaweedfs.split.yml exec warp warp create-account --username admin --admin
name: warp-pocketbase-seaweedfs
services:
# Volume and topology metadata; hands out file ids. -ip is the name the
# other roles reach it by.
seaweedfs-master:
image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45}
restart: unless-stopped
command: >
master -ip=seaweedfs-master -ip.bind=0.0.0.0 -mdir=/data
-volumeSizeLimitMB=1024 -metricsPort=9324
volumes:
- seaweed-data:/data
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9333/cluster/status >/dev/null || exit 1"]
interval: 10s
timeout: 3s
retries: 12
start_period: 15s
# Where the bytes land. -max=0 sizes itself from free disk instead of the
# default cap of 8 volumes.
seaweedfs-volume:
image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45}
restart: unless-stopped
command: >
volume -master=seaweedfs-master:9333 -ip=seaweedfs-volume -ip.bind=0.0.0.0
-port=8080 -dir=/data -max=0 -metricsPort=9325
depends_on:
seaweedfs-master:
condition: service_healthy
volumes:
- seaweed-data:/data
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null || exit 1"]
interval: 10s
timeout: 3s
retries: 12
start_period: 15s
# The directory tree over the volume store (buckets, object keys) and the
# IAM store the S3 identities live in, kept on the data volume.
seaweedfs-filer:
image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45}
restart: unless-stopped
command: >
filer -master=seaweedfs-master:9333 -ip=seaweedfs-filer -ip.bind=0.0.0.0
-port=8888 -defaultStoreDir=/data -metricsPort=9326
depends_on:
seaweedfs-volume:
condition: service_healthy
volumes:
- seaweed-data:/data
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8888/healthz >/dev/null || exit 1"]
interval: 10s
timeout: 3s
retries: 12
start_period: 15s
# Runs once per start and exits: creates the bucket (the homeserver never
# does) and writes the homeserver's identity into the filer's IAM store,
# both in place, so a rotated WARP_PB_S3_SECRET lands on the next start.
# That store is the one the admin UI writes, so the gateway gets no AWS_*
# variables or -config: those lower-priority sources would be displaced by
# the first identity added in the admin UI. The closing grep fails the
# start if the identity is missing, rather than letting the gateway come
# up open to anyone.
seaweedfs-init:
image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45}
restart: "no"
depends_on:
seaweedfs-filer:
condition: service_healthy
environment:
# Expanded by the shell inside the container, so the secret stays off
# the command line.
WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp}
WARP_PB_S3_ACCESS_KEY: ${WARP_PB_S3_ACCESS_KEY:?set WARP_PB_S3_ACCESS_KEY in .env}
WARP_PB_S3_SECRET: ${WARP_PB_S3_SECRET:?set WARP_PB_S3_SECRET in .env}
entrypoint: ["/bin/sh", "-c"]
command:
- |
set -e
printf '%s\n' \
"s3.bucket.create -name $$WARP_PB_S3_BUCKET" \
"s3.configure -user warp -access_key $$WARP_PB_S3_ACCESS_KEY -secret_key $$WARP_PB_S3_SECRET -actions Admin -apply" \
| weed shell -master=seaweedfs-master:9333 -filer=seaweedfs-filer:8888 >/dev/null
echo "s3.configure" \
| weed shell -master=seaweedfs-master:9333 -filer=seaweedfs-filer:8888 \
| grep -q "$$WARP_PB_S3_ACCESS_KEY"
# The homeserver's endpoint. Credentials come from the filer's IAM store.
seaweedfs-s3:
image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45}
restart: unless-stopped
command: s3 -filer=seaweedfs-filer:8888 -ip.bind=0.0.0.0 -port=8333 -metricsPort=9327
depends_on:
seaweedfs-filer:
condition: service_healthy
seaweedfs-init:
condition: service_completed_successfully
ports:
# For aws-cli and the like; the homeserver uses the compose network.
- "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8333/healthz >/dev/null || exit 1"]
interval: 10s
timeout: 3s
retries: 12
start_period: 15s
# Cluster view, buckets, maintenance, and Object Store → Users. It can mint
# credentials for the bucket, and weed serves it with no authentication
# when the password is empty, so the password is required. Read from
# WEED_ADMIN_* to keep it off the command line.
seaweedfs-admin:
image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45}
restart: unless-stopped
command: admin -port=23646 -master=seaweedfs-master:9333 -dataDir=/data -metricsPort=9328
depends_on:
seaweedfs-master:
condition: service_healthy
environment:
WEED_ADMIN_USER: ${SEAWEED_ADMIN_USER:-admin}
WEED_ADMIN_PASSWORD: ${SEAWEED_ADMIN_PASSWORD:?set SEAWEED_ADMIN_PASSWORD in .env}
WEED_ADMIN_READONLY_USER: ${SEAWEED_ADMIN_READONLY_USER:-}
WEED_ADMIN_READONLY_PASSWORD: ${SEAWEED_ADMIN_READONLY_PASSWORD:-}
volumes:
- seaweed-admin:/data
ports:
- "${SEAWEED_ADMIN_BIND:-127.0.0.1}:${SEAWEED_ADMIN_PORT:-23646}:23646"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:23646/health >/dev/null || exit 1"]
interval: 10s
timeout: 3s
retries: 12
start_period: 15s
warp:
build:
context: ..
dockerfile: Dockerfile
image: warp:latest
restart: unless-stopped
depends_on:
seaweedfs-s3:
condition: service_healthy
# The signing key is created on first start and kept in the volume:
# losing it means other servers no longer recognise this one.
entrypoint: ["/bin/sh", "-c"]
command:
- test -f /data/signing.key || warp generate-keys --output /data/signing.key; exec warp serve
env_file: .env
environment:
WARP_DB_DRIVER: pocketbase
WARP_DB_CONNECTION_STRING: /data/pb_data
# The gateway on the compose network; these override whatever .env
# says about S3.
WARP_PB_S3_ENABLED: "true"
WARP_PB_S3_ENDPOINT: http://seaweedfs-s3:8333
WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp}
WARP_PB_S3_REGION: ${WARP_PB_S3_REGION:-us-east-1}
WARP_PB_S3_FORCE_PATH_STYLE: "true"
WARP_PB_S3_ACCESS_KEY: ${WARP_PB_S3_ACCESS_KEY:?set WARP_PB_S3_ACCESS_KEY in .env}
WARP_PB_S3_SECRET: ${WARP_PB_S3_SECRET:?set WARP_PB_S3_SECRET in .env}
volumes:
- warp-data:/data
ports:
- "${WARP_PORT:-8008}:8008"
admin:
build:
context: ..
dockerfile: apps/admin/Dockerfile
image: warp-admin:latest
restart: unless-stopped
depends_on:
- warp
env_file: .env
environment:
WARP_ADMIN_HOMESERVER: http://warp:8008
ports:
- "${WARP_ADMIN_PORT:-8009}:8009"
web:
build:
context: ..
dockerfile: apps/web/Dockerfile
image: warp-web:latest
restart: unless-stopped
depends_on:
- warp
env_file: .env
environment:
WARP_WEB_HOMESERVER: http://warp:8008
ports:
- "${WARP_WEB_PORT:-8080}:8080"
volumes:
warp-data:
# Master, volume and filer share it, as `weed server -dir` lays it out.
seaweed-data:
# The admin UI's session key and maintenance state; no object data.
seaweed-admin: