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

+36 -1
View File
@@ -31,6 +31,31 @@ WARP_VERSION=latest
# refuses to start without it.
WARP_POSTGRES_PASSWORD=change-me
# ------------------------------------------------------------- seaweedfs
# docker-compose.pocketbase.seaweedfs*.yml only; set WARP_PB_S3_ACCESS_KEY
# and WARP_PB_S3_SECRET below as well.
#
# The SeaweedFS image every SeaweedFS container runs. Pin it: the data
# volume outlives the image.
SEAWEED_IMAGE=chrislusf/seaweedfs:4.45
# The S3 gateway's host port, on loopback unless the bind address says
# otherwise. The homeserver reaches it over the compose network; this is
# for aws-cli and the like.
SEAWEED_S3_BIND=127.0.0.1
SEAWEED_S3_PORT=8333
# The split file only: SeaweedFS's admin UI. It can mint credentials for
# the bucket, so the password is required. Put it behind a reverse proxy
# before binding it to 0.0.0.0. The read-only login is optional.
SEAWEED_ADMIN_BIND=127.0.0.1
SEAWEED_ADMIN_PORT=23646
SEAWEED_ADMIN_USER=admin
SEAWEED_ADMIN_PASSWORD=
SEAWEED_ADMIN_READONLY_USER=
SEAWEED_ADMIN_READONLY_PASSWORD=
# ------------------------------------------------------------ homeserver
# Part of every user and room ID: it cannot be changed later.
@@ -87,13 +112,23 @@ WARP_DB_AUTO_MIGRATE=
# docker-compose.pocketbase*.yml only: keep media in an S3 bucket instead
# of pb_data/storage. Bucket, region, endpoint and keys are all required
# when it is on.
#
# The pocketbase.seaweedfs files turn it on themselves, against their own
# SeaweedFS; there only the access key and secret are needed (required:
# they are the gateway's credentials too), and the bucket defaults to warp.
#
# The pocketbase.s3 files turn it on against a bucket elsewhere: the
# endpoint, access key and secret are required, the bucket (default warp)
# must exist, the region defaults to us-east-1 and path style to true
# (SeaweedFS, MinIO; set false for AWS S3). For a gateway on the Docker
# host, use http://host.docker.internal:8333.
WARP_PB_S3_ENABLED=false
WARP_PB_S3_BUCKET=
WARP_PB_S3_REGION=
WARP_PB_S3_ENDPOINT=
WARP_PB_S3_ACCESS_KEY=
WARP_PB_S3_SECRET=
WARP_PB_S3_FORCE_PATH_STYLE=false
WARP_PB_S3_FORCE_PATH_STYLE=
# Client API. Defaults *, false, 0s (never expire), 0s, true.
WARP_CORS_ALLOWED_ORIGINS=
+36
View File
@@ -9,6 +9,9 @@ The homeserver (`warp`), the admin panel (`warp-admin`) and the web client
| `docker-compose.sqlite.yml` | SQLite, in the `warp-data` volume | built here |
| `docker-compose.pocketbase.yml` | PocketBase, embedded in the homeserver, in `warp-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 |
```sh
@@ -26,6 +29,39 @@ TLS-terminating reverse proxy in front before exposing them; see
PocketBase is a database driver inside the homeserver, not a separate
server, so the PocketBase files have no PocketBase container.
## Media in S3
The three `pocketbase.s3` and `pocketbase.seaweedfs` setups keep the
PocketBase database in `/data/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 `warp-data` or copy the files across first.
- **`docker-compose.pocketbase.s3.yml`**: a bucket that already exists
elsewhere (AWS, MinIO, a SeaweedFS on another host). Set
`WARP_PB_S3_ENDPOINT`, `WARP_PB_S3_ACCESS_KEY` and `WARP_PB_S3_SECRET`.
Path style is on by default; set `WARP_PB_S3_FORCE_PATH_STYLE=false` for
AWS S3. `http://host.docker.internal:8333` reaches 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 one
`weed server -s3` container, plus a one-shot `seaweedfs-init` that
creates the bucket. The gateway's one identity is the homeserver's key
pair, `WARP_PB_S3_ACCESS_KEY` and `WARP_PB_S3_SECRET`.
- **`docker-compose.pocketbase.seaweedfs.split.yml`**: master, volume,
filer, S3 gateway and admin UI (port 23646) as separate containers.
`seaweedfs-init` writes 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. Set `SEAWEED_ADMIN_PASSWORD` as 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
@@ -0,0 +1,97 @@
# Warp on PocketBase with media in an S3 bucket elsewhere: the homeserver,
# the admin panel and the web client, each in its own container, pulled
# from a registry.
#
# cd Docker
# cp .env.example .env # then edit it; set WARP_PB_S3_ENDPOINT,
# # WARP_PB_S3_ACCESS_KEY and WARP_PB_S3_SECRET
# docker compose -f docker-compose.pocketbase.s3.prod.yml up -d
#
# The database stays in /data/pb_data (warp-data); media goes to the bucket
# WARP_PB_S3_BUCKET (default warp) on WARP_PB_S3_ENDPOINT, which must already
# exist: nothing here runs a gateway or creates the bucket. For a gateway on
# this Docker host, use http://host.docker.internal:8333; the host entry below
# makes that name resolve. For a SeaweedFS that comes up with the stack, see
# docker-compose.pocketbase.seaweedfs.yml. 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.
#
# Create the first administrator once the server is up:
#
# docker compose -f docker-compose.pocketbase.s3.prod.yml exec warp warp create-account --username admin --admin
#
# Nothing is built here: the homeserver, admin panel and web client images
# come from WARP_REGISTRY (default ghcr.io/dariuszjaniak) at tag
# WARP_VERSION (default latest). Pin WARP_VERSION to a release in
# production, and update with
#
# docker compose -f docker-compose.pocketbase.s3.prod.yml pull && docker compose -f docker-compose.pocketbase.s3.prod.yml up -d
name: warp-pocketbase-s3
services:
warp:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp:${WARP_VERSION:-latest}
pull_policy: always
restart: unless-stopped
extra_hosts:
# Lets WARP_PB_S3_ENDPOINT name the Docker host as host.docker.internal.
- "host.docker.internal:host-gateway"
# The homeserver does not check the endpoint at startup and would only
# fail on the first upload, so wait up to 30 seconds for it to answer at
# all (any HTTP status). 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:
- >-
i=0; until curl -s -o /dev/null --max-time 5 "$$WARP_PB_S3_ENDPOINT"; do
i=$$((i + 1)); if [ "$$i" -ge 30 ]; then echo "S3 endpoint $$WARP_PB_S3_ENDPOINT does not answer" >&2; exit 1; fi;
echo "waiting for S3 endpoint $$WARP_PB_S3_ENDPOINT" >&2; sleep 1; done;
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
WARP_PB_S3_ENABLED: "true"
WARP_PB_S3_ENDPOINT: ${WARP_PB_S3_ENDPOINT:?set WARP_PB_S3_ENDPOINT in .env}
WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp}
# SeaweedFS and MinIO ignore the region; the client insists on one.
WARP_PB_S3_REGION: ${WARP_PB_S3_REGION:-us-east-1}
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}
# true for SeaweedFS and MinIO, false for AWS S3 proper.
WARP_PB_S3_FORCE_PATH_STYLE: ${WARP_PB_S3_FORCE_PATH_STYLE:-true}
volumes:
- warp-data:/data
ports:
- "${WARP_PORT:-8008}:8008"
admin:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-admin:${WARP_VERSION:-latest}
pull_policy: always
restart: unless-stopped
depends_on:
- warp
env_file: .env
environment:
WARP_ADMIN_HOMESERVER: http://warp:8008
ports:
- "${WARP_ADMIN_PORT:-8009}:8009"
web:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-web:${WARP_VERSION:-latest}
pull_policy: always
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:
+96
View File
@@ -0,0 +1,96 @@
# Warp on PocketBase with media in an S3 bucket elsewhere: the homeserver,
# the admin panel and the web client, each in its own container, built from
# this checkout.
#
# cd Docker
# cp .env.example .env # then edit it; set WARP_PB_S3_ENDPOINT,
# # WARP_PB_S3_ACCESS_KEY and WARP_PB_S3_SECRET
# docker compose -f docker-compose.pocketbase.s3.yml up -d --build
#
# The database stays in /data/pb_data (warp-data); media goes to the bucket
# WARP_PB_S3_BUCKET (default warp) on WARP_PB_S3_ENDPOINT, which must already
# exist: nothing here runs a gateway or creates the bucket. For a gateway on
# this Docker host, use http://host.docker.internal:8333; the host entry below
# makes that name resolve. For a SeaweedFS that comes up with the stack, see
# docker-compose.pocketbase.seaweedfs.yml. 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.
#
# Create the first administrator once the server is up:
#
# docker compose -f docker-compose.pocketbase.s3.yml exec warp warp create-account --username admin --admin
name: warp-pocketbase-s3
services:
warp:
build:
context: ..
dockerfile: Dockerfile
image: warp:latest
restart: unless-stopped
extra_hosts:
# Lets WARP_PB_S3_ENDPOINT name the Docker host as host.docker.internal.
- "host.docker.internal:host-gateway"
# The homeserver does not check the endpoint at startup and would only
# fail on the first upload, so wait up to 30 seconds for it to answer at
# all (any HTTP status). 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:
- >-
i=0; until curl -s -o /dev/null --max-time 5 "$$WARP_PB_S3_ENDPOINT"; do
i=$$((i + 1)); if [ "$$i" -ge 30 ]; then echo "S3 endpoint $$WARP_PB_S3_ENDPOINT does not answer" >&2; exit 1; fi;
echo "waiting for S3 endpoint $$WARP_PB_S3_ENDPOINT" >&2; sleep 1; done;
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
WARP_PB_S3_ENABLED: "true"
WARP_PB_S3_ENDPOINT: ${WARP_PB_S3_ENDPOINT:?set WARP_PB_S3_ENDPOINT in .env}
WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp}
# SeaweedFS and MinIO ignore the region; the client insists on one.
WARP_PB_S3_REGION: ${WARP_PB_S3_REGION:-us-east-1}
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}
# true for SeaweedFS and MinIO, false for AWS S3 proper.
WARP_PB_S3_FORCE_PATH_STYLE: ${WARP_PB_S3_FORCE_PATH_STYLE:-true}
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:
@@ -0,0 +1,134 @@
# Warp on PocketBase with media in SeaweedFS: the homeserver, the admin panel
# and the web client pulled from a registry, plus SeaweedFS as one
# container (`weed server -s3`: master, volume, filer and S3 gateway in one
# process).
#
# cd Docker
# cp .env.example .env # then edit it; set WARP_PB_S3_ACCESS_KEY
# # and WARP_PB_S3_SECRET
# docker compose -f docker-compose.pocketbase.seaweedfs.prod.yml up -d
#
# The database stays in /data/pb_data (warp-data); media goes to the bucket
# WARP_PB_S3_BUCKET (default warp) in SeaweedFS (seaweed-data), whose gateway
# has exactly one identity: the homeserver's. For SeaweedFS's admin UI and
# per-role containers, see docker-compose.pocketbase.seaweedfs.split.yml.
# 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) is published on
# loopback, for aws-cli and the like; SEAWEED_S3_BIND=0.0.0.0 exposes it.
#
# This file shares its project and volumes with
# docker-compose.pocketbase.seaweedfs.split.yml: SeaweedFS's data has the
# same layout either way, so switching between the two needs no migration.
#
# Create the first administrator once the server is up:
#
# docker compose -f docker-compose.pocketbase.seaweedfs.prod.yml exec warp warp create-account --username admin --admin
#
# Nothing is built here: the homeserver, admin panel and web client images
# come from WARP_REGISTRY (default ghcr.io/dariuszjaniak) at tag
# WARP_VERSION (default latest), and SeaweedFS from SEAWEED_IMAGE. Pin
# WARP_VERSION to a release in production, and update with
#
# docker compose -f docker-compose.pocketbase.seaweedfs.prod.yml pull && docker compose -f docker-compose.pocketbase.seaweedfs.prod.yml up -d
name: warp-pocketbase-seaweedfs
services:
seaweedfs:
image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45}
restart: unless-stopped
command: server -dir=/data -s3 -volume.max=0 -master.volumeSizeLimitMB=1024
environment:
# With no -s3.config, the gateway takes its single identity from these,
# which is also what takes it out of its allow-anyone default. The same
# pair the homeserver signs with.
AWS_ACCESS_KEY_ID: ${WARP_PB_S3_ACCESS_KEY:?set WARP_PB_S3_ACCESS_KEY in .env}
AWS_SECRET_ACCESS_KEY: ${WARP_PB_S3_SECRET:?set WARP_PB_S3_SECRET in .env}
volumes:
- seaweed-data:/data
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:
# The gateway answering means master, volume and filer are up too.
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8333/healthz >/dev/null || exit 1"]
interval: 10s
timeout: 3s
retries: 12
start_period: 20s
# Runs once per start and exits. The homeserver never creates its bucket;
# creating one that exists is a no-op.
seaweedfs-init:
image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45}
restart: "no"
depends_on:
seaweedfs:
condition: service_healthy
environment:
WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp}
entrypoint: ["/bin/sh", "-c"]
command:
- echo "s3.bucket.create -name $$WARP_PB_S3_BUCKET" | weed shell -master=seaweedfs:9333 >/dev/null
warp:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp:${WARP_VERSION:-latest}
pull_policy: always
restart: unless-stopped
depends_on:
seaweedfs:
condition: service_healthy
seaweedfs-init:
condition: service_completed_successfully
# 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: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:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-admin:${WARP_VERSION:-latest}
pull_policy: always
restart: unless-stopped
depends_on:
- warp
env_file: .env
environment:
WARP_ADMIN_HOMESERVER: http://warp:8008
ports:
- "${WARP_ADMIN_PORT:-8009}:8009"
web:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-web:${WARP_VERSION:-latest}
pull_policy: always
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:
seaweed-data:
@@ -0,0 +1,240 @@
# Warp on PocketBase with media in SeaweedFS split into its roles: the
# homeserver, the admin panel and the web client pulled from a registry,
# 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.prod.yml up -d
#
# 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.prod.yml exec warp warp create-account --username admin --admin
#
# Nothing is built here: the homeserver, admin panel and web client images
# come from WARP_REGISTRY (default ghcr.io/dariuszjaniak) at tag
# WARP_VERSION (default latest), and SeaweedFS from SEAWEED_IMAGE. Pin
# WARP_VERSION to a release in production, and update with
#
# docker compose -f docker-compose.pocketbase.seaweedfs.split.prod.yml pull && docker compose -f docker-compose.pocketbase.seaweedfs.split.prod.yml up -d
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:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp:${WARP_VERSION:-latest}
pull_policy: always
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:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-admin:${WARP_VERSION:-latest}
pull_policy: always
restart: unless-stopped
depends_on:
- warp
env_file: .env
environment:
WARP_ADMIN_HOMESERVER: http://warp:8008
ports:
- "${WARP_ADMIN_PORT:-8009}:8009"
web:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-web:${WARP_VERSION:-latest}
pull_policy: always
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:
@@ -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:
@@ -0,0 +1,133 @@
# Warp on PocketBase with media in SeaweedFS: the homeserver, the admin panel
# and the web client built from this checkout, plus SeaweedFS as one
# container (`weed server -s3`: master, volume, filer and S3 gateway in one
# process).
#
# cd Docker
# cp .env.example .env # then edit it; set WARP_PB_S3_ACCESS_KEY
# # and WARP_PB_S3_SECRET
# docker compose -f docker-compose.pocketbase.seaweedfs.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), whose gateway
# has exactly one identity: the homeserver's. For SeaweedFS's admin UI and
# per-role containers, see docker-compose.pocketbase.seaweedfs.split.yml.
# 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) is published on
# loopback, for aws-cli and the like; SEAWEED_S3_BIND=0.0.0.0 exposes it.
#
# This file shares its project and volumes with
# docker-compose.pocketbase.seaweedfs.split.yml: SeaweedFS's data has the
# same layout either way, so switching between the two needs no migration.
#
# Create the first administrator once the server is up:
#
# docker compose -f docker-compose.pocketbase.seaweedfs.yml exec warp warp create-account --username admin --admin
name: warp-pocketbase-seaweedfs
services:
seaweedfs:
image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45}
restart: unless-stopped
command: server -dir=/data -s3 -volume.max=0 -master.volumeSizeLimitMB=1024
environment:
# With no -s3.config, the gateway takes its single identity from these,
# which is also what takes it out of its allow-anyone default. The same
# pair the homeserver signs with.
AWS_ACCESS_KEY_ID: ${WARP_PB_S3_ACCESS_KEY:?set WARP_PB_S3_ACCESS_KEY in .env}
AWS_SECRET_ACCESS_KEY: ${WARP_PB_S3_SECRET:?set WARP_PB_S3_SECRET in .env}
volumes:
- seaweed-data:/data
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:
# The gateway answering means master, volume and filer are up too.
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8333/healthz >/dev/null || exit 1"]
interval: 10s
timeout: 3s
retries: 12
start_period: 20s
# Runs once per start and exits. The homeserver never creates its bucket;
# creating one that exists is a no-op.
seaweedfs-init:
image: ${SEAWEED_IMAGE:-chrislusf/seaweedfs:4.45}
restart: "no"
depends_on:
seaweedfs:
condition: service_healthy
environment:
WARP_PB_S3_BUCKET: ${WARP_PB_S3_BUCKET:-warp}
entrypoint: ["/bin/sh", "-c"]
command:
- echo "s3.bucket.create -name $$WARP_PB_S3_BUCKET" | weed shell -master=seaweedfs:9333 >/dev/null
warp:
build:
context: ..
dockerfile: Dockerfile
image: warp:latest
restart: unless-stopped
depends_on:
seaweedfs:
condition: service_healthy
seaweedfs-init:
condition: service_completed_successfully
# 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: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:
seaweed-data: