Docker-AIO: PocketBase images with SeaweedFS for media

Dockerfile.PocketBase.SeaweedFS bakes SeaweedFS into the PocketBase
all-in-one image as one `weed server -s3` process;
Dockerfile.PocketBase.SeaweedFS.split runs master, volume, filer, S3
gateway and the admin UI as separate processes, with the homeserver's
S3 identity seeded into the filer's IAM store on every boot. Media goes
to the bucket, the database stays in /data/pb_data, and both layouts
share /seaweed/data, so switching between them needs no migration.

The entrypoint now tracks every process in one list and stops them in
reverse start order, and it refuses to start an open S3 gateway or
admin UI. Each image gets a build and a prod compose file, and
.env.example and the README cover the new settings. Modelled on
DriverVault's Docker-AIO SeaweedFS images.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
tajniak81andClaude Opus 5.5 committed 2026-10-22 20:33:21 +02:00
1 parent 89f89b5d36
commit 36d83551aa
9 files changed
+804 -49

No files matched your search

+30
View File
@@ -33,6 +33,31 @@ WARP_VERSION=latest
WARP_AIO_ADMIN=true
WARP_AIO_WEB=true
# ------------------------------------------------------------- seaweedfs
# docker-compose.pocketbase.seaweedfs*.yml only; set WARP_PB_S3_ACCESS_KEY
# and WARP_PB_S3_SECRET below as well.
#
# The S3 gateway's host port, on loopback unless the bind address says
# otherwise. The homeserver reaches it inside the container; 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=
# Building only: the SeaweedFS release to bake in. Leave it commented out to
# keep the Dockerfile's pin; an empty value breaks the build.
# SEAWEED_VERSION=4.45
# ------------------------------------------------------------ homeserver
# Part of every user and room ID: it cannot be changed later.
@@ -89,6 +114,11 @@ 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 the
# SeaweedFS in the container; there only the access key and secret are
# needed (required: they are the gateway's credentials too), and the bucket
# defaults to warp.
WARP_PB_S3_ENABLED=false
WARP_PB_S3_BUCKET=
WARP_PB_S3_REGION=
+147
View File
@@ -0,0 +1,147 @@
# warp-aio-pocketbase-seaweedfs — the homeserver, the admin panel and the
# web client on PocketBase, with SeaweedFS in the same image as the media
# store: one `weed server -s3` process, master, volume, filer and S3 gateway
# in one. The homeserver keeps its database in /data/pb_data and its media
# in the bucket WARP_PB_S3_BUCKET (default warp) on the gateway at
# 127.0.0.1:8333. For per-role processes and SeaweedFS's admin UI, see
# Dockerfile.PocketBase.SeaweedFS.split.
#
# Build from the repository root:
#
# docker build -t warp-aio-pocketbase-seaweedfs -f Docker-AIO/Dockerfile.PocketBase.SeaweedFS .
# docker run -p 8008:8008 -p 8009:8009 -p 8080:8080 -p 127.0.0.1:8333:8333 \
# -v warp-data:/data -v seaweed-data:/seaweed/data \
# -e WARP_SERVER_NAME=warp.example \
# -e WARP_PB_S3_ACCESS_KEY=warp -e WARP_PB_S3_SECRET=change-me \
# warp-aio-pocketbase-seaweedfs
#
# WARP_PB_S3_ACCESS_KEY and WARP_PB_S3_SECRET are required: they are the
# gateway's only identity and what the homeserver signs its requests with.
#
# Ports inside the container (publish 8333 on loopback at most; never 8081,
# the volume server, which serves content by id with no authentication):
#
# 8008 homeserver 8333 SeaweedFS S3 gateway
# 8009 admin panel 8888 SeaweedFS filer
# 8080 web client 9333 SeaweedFS master
# 8081 SeaweedFS volume (not its usual 8080, which is the web client)
# SeaweedFS release to bake in, pinned so a rebuild later brings up the same
# one; --build-arg SEAWEED_VERSION=... to upgrade. Global, because a FROM uses
# it.
ARG SEAWEED_VERSION="4.45"
FROM node:22-alpine AS web
WORKDIR /src
RUN corepack enable
# Lockfile and manifests first, so the package store survives source changes.
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
COPY apps/ui/package.json ./apps/ui/
COPY apps/admin/package.json ./apps/admin/
COPY apps/web/package.json ./apps/web/
RUN pnpm install --frozen-lockfile --filter @warp/admin... --filter @warp/web...
COPY apps/ui ./apps/ui
COPY apps/admin ./apps/admin
COPY apps/web ./apps/web
RUN pnpm --filter @warp/admin build && pnpm --filter @warp/web build
FROM golang:1.26-alpine AS build
ARG VERSION=0.0.0-dev
ARG COMMIT=unknown
ARG BUILD_DATE=unknown
ENV CGO_ENABLED=0
WORKDIR /src
# The homeserver. Dependencies first, so the module cache survives source
# changes.
COPY server/go.mod server/go.sum ./server/
RUN cd server && go mod download
COPY server ./server
RUN cd server && go build -trimpath \
-ldflags "-s -w \
-X github.com/dariuszjaniak/warp/server/internal/buildinfo.Version=${VERSION} \
-X github.com/dariuszjaniak/warp/server/internal/buildinfo.Commit=${COMMIT} \
-X github.com/dariuszjaniak/warp/server/internal/buildinfo.BuildDate=${BUILD_DATE}" \
-o /out/warp ./cmd/warp
# The admin panel and the web client, each with its frontend embedded.
COPY apps/admin/go.mod ./apps/admin/
COPY apps/admin/cmd ./apps/admin/cmd
COPY apps/admin/internal ./apps/admin/internal
COPY --from=web /src/apps/admin/internal/panel/build/dist ./apps/admin/internal/panel/build/dist
RUN cd apps/admin && GOWORK=off go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \
-o /out/warp-admin ./cmd/warp-admin
COPY apps/web/go.mod ./apps/web/
COPY apps/web/cmd ./apps/web/cmd
COPY apps/web/host ./apps/web/host
COPY apps/web/client/client.go ./apps/web/client/
COPY --from=web /src/apps/web/client/build/dist ./apps/web/client/build/dist
RUN cd apps/web && GOWORK=off go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \
-o /out/warp-web ./cmd/warp-web
# The one static weed binary from the official image.
FROM chrislusf/seaweedfs:${SEAWEED_VERSION} AS seaweed
FROM alpine:3.21
# tini is PID 1 so signals reach the entrypoint and zombies are reaped.
RUN apk add --no-cache ca-certificates tzdata curl tini && \
adduser -D -u 10001 -h /data warp
COPY --from=build /out/warp /out/warp-admin /out/warp-web /usr/local/bin/
COPY --from=seaweed /usr/bin/weed /usr/local/bin/weed
COPY warp.yaml.example /etc/warp/warp.yaml.example
COPY --chmod=755 Docker-AIO/entrypoint.sh /usr/local/bin/warp-aio
# /data: the database, signing key and anything stored before S3 was on.
# /seaweed/data: everything SeaweedFS keeps.
# Mount a volume on each; a fresh named volume inherits the ownership.
RUN mkdir -p /data /seaweed/data && chown -R warp:warp /data /seaweed
WORKDIR /data
USER warp
# Media goes to the bucket on the gateway inside this image: a fixed
# loopback address, path style because a self-hosted gateway has no
# per-bucket DNS, and a region SeaweedFS ignores. Media stored on the
# filesystem before S3 was on is not moved into the bucket.
ENV WARP_LISTEN_ADDRESS=":8008" \
WARP_PRIVATE_KEY="/data/signing.key" \
WARP_DB_DRIVER="pocketbase" \
WARP_DB_CONNECTION_STRING="/data/pb_data" \
WARP_PB_S3_ENABLED="true" \
WARP_PB_S3_ENDPOINT="http://127.0.0.1:8333" \
WARP_PB_S3_BUCKET="warp" \
WARP_PB_S3_REGION="us-east-1" \
WARP_PB_S3_FORCE_PATH_STYLE="true" \
WARP_LOG_FORMAT="json" \
WARP_ADMIN_LISTEN=":8009" \
WARP_ADMIN_HOMESERVER="http://127.0.0.1:8008" \
WARP_ADMIN_LOG_FORMAT="json" \
WARP_WEB_LISTEN=":8080" \
WARP_WEB_HOMESERVER="http://127.0.0.1:8008" \
WARP_WEB_LOG_FORMAT="json" \
WARP_AIO_ADMIN="true" \
WARP_AIO_WEB="true" \
WARP_AIO_EMBEDDED_POSTGRES="false" \
WARP_AIO_SEAWEEDFS="server"
EXPOSE 8008 8009 8080 8333
# Every process must answer, so a wedged one shows in `docker ps`.
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -fsS -o /dev/null http://127.0.0.1:9333/cluster/status && \
curl -fsS -o /dev/null http://127.0.0.1:8333/healthz && \
curl -fsS -o /dev/null http://127.0.0.1:8008/healthz && \
{ [ "$WARP_AIO_ADMIN" != true ] || curl -fsS -o /dev/null http://127.0.0.1:8009/healthz; } && \
{ [ "$WARP_AIO_WEB" != true ] || curl -fsS -o /dev/null http://127.0.0.1:8080/healthz; } || exit 1
ENTRYPOINT ["/sbin/tini", "--", "warp-aio"]
@@ -0,0 +1,159 @@
# warp-aio-pocketbase-seaweedfs-split — the homeserver, the admin panel and
# the web client on PocketBase, with SeaweedFS in the same image as the
# media store, split into its roles: master, volume, filer, S3 gateway and
# admin UI, one process each. Over Dockerfile.PocketBase.SeaweedFS that buys
# per-role restart, per-role metrics ports and the admin UI, where S3
# identities are minted and revoked under Object Store → Users.
#
# The homeserver's identity lives in the filer's IAM store, seeded on every
# boot, the same store the admin UI writes; so a key added there never
# displaces the homeserver's. /seaweed/data has the layout `weed server -dir`
# writes, so it moves between the two SeaweedFS images unchanged.
#
# Build from the repository root:
#
# docker build -t warp-aio-pocketbase-seaweedfs-split -f Docker-AIO/Dockerfile.PocketBase.SeaweedFS.split .
# docker run -p 8008:8008 -p 8009:8009 -p 8080:8080 \
# -p 127.0.0.1:8333:8333 -p 127.0.0.1:23646:23646 \
# -v warp-data:/data -v seaweed-data:/seaweed/data -v seaweed-admin:/seaweed/admin \
# -e WARP_SERVER_NAME=warp.example \
# -e WARP_PB_S3_ACCESS_KEY=warp -e WARP_PB_S3_SECRET=change-me \
# -e WEED_ADMIN_PASSWORD=change-me \
# warp-aio-pocketbase-seaweedfs-split
#
# WARP_PB_S3_ACCESS_KEY, WARP_PB_S3_SECRET and WEED_ADMIN_PASSWORD are
# required; the container refuses to start an open gateway or admin UI.
#
# Ports inside the container (publish 8333 and 23646 on loopback at most;
# master, volume and filer listen on loopback only):
#
# 8008 homeserver 8333 SeaweedFS S3 gateway
# 8009 admin panel 8888 SeaweedFS filer
# 8080 web client 9333 SeaweedFS master
# 8081 SeaweedFS volume 23646 SeaweedFS admin UI
# 9324-9328 SeaweedFS metrics: master, volume, filer, S3, admin
# SeaweedFS release to bake in, pinned so a rebuild later brings up the same
# one; --build-arg SEAWEED_VERSION=... to upgrade. Global, because a FROM uses
# it.
ARG SEAWEED_VERSION="4.45"
FROM node:22-alpine AS web
WORKDIR /src
RUN corepack enable
# Lockfile and manifests first, so the package store survives source changes.
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
COPY apps/ui/package.json ./apps/ui/
COPY apps/admin/package.json ./apps/admin/
COPY apps/web/package.json ./apps/web/
RUN pnpm install --frozen-lockfile --filter @warp/admin... --filter @warp/web...
COPY apps/ui ./apps/ui
COPY apps/admin ./apps/admin
COPY apps/web ./apps/web
RUN pnpm --filter @warp/admin build && pnpm --filter @warp/web build
FROM golang:1.26-alpine AS build
ARG VERSION=0.0.0-dev
ARG COMMIT=unknown
ARG BUILD_DATE=unknown
ENV CGO_ENABLED=0
WORKDIR /src
# The homeserver. Dependencies first, so the module cache survives source
# changes.
COPY server/go.mod server/go.sum ./server/
RUN cd server && go mod download
COPY server ./server
RUN cd server && go build -trimpath \
-ldflags "-s -w \
-X github.com/dariuszjaniak/warp/server/internal/buildinfo.Version=${VERSION} \
-X github.com/dariuszjaniak/warp/server/internal/buildinfo.Commit=${COMMIT} \
-X github.com/dariuszjaniak/warp/server/internal/buildinfo.BuildDate=${BUILD_DATE}" \
-o /out/warp ./cmd/warp
# The admin panel and the web client, each with its frontend embedded.
COPY apps/admin/go.mod ./apps/admin/
COPY apps/admin/cmd ./apps/admin/cmd
COPY apps/admin/internal ./apps/admin/internal
COPY --from=web /src/apps/admin/internal/panel/build/dist ./apps/admin/internal/panel/build/dist
RUN cd apps/admin && GOWORK=off go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \
-o /out/warp-admin ./cmd/warp-admin
COPY apps/web/go.mod ./apps/web/
COPY apps/web/cmd ./apps/web/cmd
COPY apps/web/host ./apps/web/host
COPY apps/web/client/client.go ./apps/web/client/
COPY --from=web /src/apps/web/client/build/dist ./apps/web/client/build/dist
RUN cd apps/web && GOWORK=off go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \
-o /out/warp-web ./cmd/warp-web
# The one static weed binary from the official image.
FROM chrislusf/seaweedfs:${SEAWEED_VERSION} AS seaweed
FROM alpine:3.21
# tini is PID 1 so signals reach the entrypoint and zombies are reaped.
RUN apk add --no-cache ca-certificates tzdata curl tini && \
adduser -D -u 10001 -h /data warp
COPY --from=build /out/warp /out/warp-admin /out/warp-web /usr/local/bin/
COPY --from=seaweed /usr/bin/weed /usr/local/bin/weed
COPY warp.yaml.example /etc/warp/warp.yaml.example
COPY --chmod=755 Docker-AIO/entrypoint.sh /usr/local/bin/warp-aio
# /data: the database, signing key and anything stored before S3 was on.
# /seaweed/data: master, volume and filer, sharing it.
# /seaweed/admin: the admin UI's session key and maintenance state.
# Mount a volume on each; a fresh named volume inherits the ownership.
RUN mkdir -p /data /seaweed/data /seaweed/admin && chown -R warp:warp /data /seaweed
WORKDIR /data
USER warp
# Media goes to the bucket on the gateway inside this image: a fixed
# loopback address, path style because a self-hosted gateway has no
# per-bucket DNS, and a region SeaweedFS ignores. Media stored on the
# filesystem before S3 was on is not moved into the bucket.
ENV WARP_LISTEN_ADDRESS=":8008" \
WARP_PRIVATE_KEY="/data/signing.key" \
WARP_DB_DRIVER="pocketbase" \
WARP_DB_CONNECTION_STRING="/data/pb_data" \
WARP_PB_S3_ENABLED="true" \
WARP_PB_S3_ENDPOINT="http://127.0.0.1:8333" \
WARP_PB_S3_BUCKET="warp" \
WARP_PB_S3_REGION="us-east-1" \
WARP_PB_S3_FORCE_PATH_STYLE="true" \
WARP_LOG_FORMAT="json" \
WARP_ADMIN_LISTEN=":8009" \
WARP_ADMIN_HOMESERVER="http://127.0.0.1:8008" \
WARP_ADMIN_LOG_FORMAT="json" \
WARP_WEB_LISTEN=":8080" \
WARP_WEB_HOMESERVER="http://127.0.0.1:8008" \
WARP_WEB_LOG_FORMAT="json" \
WARP_AIO_ADMIN="true" \
WARP_AIO_WEB="true" \
WARP_AIO_EMBEDDED_POSTGRES="false" \
WARP_AIO_SEAWEEDFS="split" \
WEED_ADMIN_USER="admin"
EXPOSE 8008 8009 8080 8333 23646
# Every process must answer, so a wedged one shows in `docker ps`.
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -fsS -o /dev/null http://127.0.0.1:9333/cluster/status && \
curl -fsS -o /dev/null http://127.0.0.1:8081/healthz && \
curl -fsS -o /dev/null http://127.0.0.1:8888/healthz && \
curl -fsS -o /dev/null http://127.0.0.1:8333/healthz && \
curl -fsS -o /dev/null http://127.0.0.1:23646/health && \
curl -fsS -o /dev/null http://127.0.0.1:8008/healthz && \
{ [ "$WARP_AIO_ADMIN" != true ] || curl -fsS -o /dev/null http://127.0.0.1:8009/healthz; } && \
{ [ "$WARP_AIO_WEB" != true ] || curl -fsS -o /dev/null http://127.0.0.1:8080/healthz; } || exit 1
ENTRYPOINT ["/sbin/tini", "--", "warp-aio"]
+53 -5
View File
@@ -10,15 +10,21 @@ policy brings the set back.
| [`Dockerfile`](Dockerfile) | `warp-aio` | SQLite, `/data/warp.db` |
| [`Dockerfile.PocketBase`](Dockerfile.PocketBase) | `warp-aio-pocketbase` | PocketBase (embedded driver), `/data/pb_data` |
| [`Dockerfile.PostgreSQL`](Dockerfile.PostgreSQL) | `warp-aio-postgresql` | PostgreSQL 17 in the container, `/data/postgres` |
| [`Dockerfile.PocketBase.SeaweedFS`](Dockerfile.PocketBase.SeaweedFS) | `warp-aio-pocketbase-seaweedfs` | PocketBase, media in SeaweedFS (`weed server -s3`) in the container |
| [`Dockerfile.PocketBase.SeaweedFS.split`](Dockerfile.PocketBase.SeaweedFS.split) | `warp-aio-pocketbase-seaweedfs-split` | PocketBase, media in SeaweedFS split into its roles, with its admin UI |
| Compose file | Image |
| --- | --- |
| `docker-compose.sqlite.yml` | `warp-aio`, built here |
| `docker-compose.pocketbase.yml` | `warp-aio-pocketbase`, built here |
| `docker-compose.postgresql.yml` | `warp-aio-postgresql`, built here |
| `docker-compose.pocketbase.seaweedfs.yml` | `warp-aio-pocketbase-seaweedfs`, built here |
| `docker-compose.pocketbase.seaweedfs.split.yml` | `warp-aio-pocketbase-seaweedfs-split`, built here |
| `docker-compose.*.prod.yml` | the same images, pulled, never built |
Everything lives in the `warp-data` volume, mounted on `/data`.
Everything lives in the `warp-data` volume, mounted on `/data`; the
SeaweedFS images add `seaweed-data` on `/seaweed/data` (and the split one
`seaweed-admin` on `/seaweed/admin`).
```sh
cd Docker-AIO
@@ -34,9 +40,48 @@ inside the container, so it needs no password and is not reachable from
outside. To point that image at a PostgreSQL elsewhere instead, set
`WARP_AIO_EMBEDDED_POSTGRES=false` and `WARP_DB_CONNECTION_STRING`.
The three Dockerfiles share their build stages line for line; only the
header and the final stage differ. Keep the build stages in step when
changing one.
The Dockerfiles share their build stages line for line; only the header,
the SeaweedFS stage and the final stage differ. Keep the build stages in
step when changing one.
## SeaweedFS
The two SeaweedFS images keep the PocketBase database in `/data/pb_data`
and put media in an S3 bucket (`WARP_PB_S3_BUCKET`, default `warp`) on a
SeaweedFS inside the same container. The compose files point the
homeserver at it; `.env` only has to give it credentials:
```sh
cp .env.example .env # set WARP_PB_S3_ACCESS_KEY, WARP_PB_S3_SECRET
# (and SEAWEED_ADMIN_PASSWORD for split)
docker compose -f docker-compose.pocketbase.seaweedfs.yml up -d --build
```
- **`Dockerfile.PocketBase.SeaweedFS`** runs `weed server -s3`: master,
volume, filer and S3 gateway in one process. The gateway has exactly
one identity, the homeserver's key pair.
- **`Dockerfile.PocketBase.SeaweedFS.split`** runs each role as its own
process, plus SeaweedFS's admin UI on port 23646, where further S3
identities are created and revoked under *Object Store → Users*. The
homeserver's identity is written into the filer's IAM store on every
boot (the store the admin UI writes too), so a key added there never
displaces it, and a rotated `WARP_PB_S3_SECRET` lands on the next start.
Both refuse to start without the key pair, and the split one without
`SEAWEED_ADMIN_PASSWORD`: either would otherwise serve an open gateway or
an open panel that can mint credentials. The S3 gateway and the admin UI
are published on 127.0.0.1 only; the volume server, which serves content
by id with no authentication, is never published. Inside, it is on 8081,
since 8080 is the web client.
Both write `/seaweed/data` in the layout `weed server -dir` uses, and their
compose files share a project and volumes, so switching between them
needs no migration.
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. The SeaweedFS release is pinned in the Dockerfiles
(`SEAWEED_VERSION`, 4.45); upgrading it means a rebuild.
## Settings
@@ -62,7 +107,10 @@ from the repository root:
docker build -t $WARP_REGISTRY/warp-aio:$WARP_VERSION -f Docker-AIO/Dockerfile .
docker build -t $WARP_REGISTRY/warp-aio-pocketbase:$WARP_VERSION -f Docker-AIO/Dockerfile.PocketBase .
docker build -t $WARP_REGISTRY/warp-aio-postgresql:$WARP_VERSION -f Docker-AIO/Dockerfile.PostgreSQL .
for i in warp-aio warp-aio-pocketbase warp-aio-postgresql; do
docker build -t $WARP_REGISTRY/warp-aio-pocketbase-seaweedfs:$WARP_VERSION -f Docker-AIO/Dockerfile.PocketBase.SeaweedFS .
docker build -t $WARP_REGISTRY/warp-aio-pocketbase-seaweedfs-split:$WARP_VERSION -f Docker-AIO/Dockerfile.PocketBase.SeaweedFS.split .
for i in warp-aio warp-aio-pocketbase warp-aio-postgresql \
warp-aio-pocketbase-seaweedfs warp-aio-pocketbase-seaweedfs-split; do
docker push $WARP_REGISTRY/$i:$WARP_VERSION
done
```
@@ -0,0 +1,64 @@
# Warp all-in-one on PocketBase with SeaweedFS: the homeserver, the admin
# panel, the web client and SeaweedFS (one `weed server -s3` process) in one
# container pulled from a registry (the image is built from
# Dockerfile.PocketBase.SeaweedFS).
#
# cd Docker-AIO
# 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 in SeaweedFS (seaweed-data), whose gateway has exactly
# one identity: the homeserver's. 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 and docker-compose.pocketbase.seaweedfs.split.yml share their project
# and volumes: SeaweedFS's data has the same layout either way, so switching
# between them 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 image comes 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.seaweedfs.prod.yml pull && docker compose -f docker-compose.pocketbase.seaweedfs.prod.yml up -d
name: warp-aio-pocketbase-seaweedfs
services:
warp:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-aio-pocketbase-seaweedfs:${WARP_VERSION:-latest}
pull_policy: always
restart: unless-stopped
env_file: .env
environment:
# The gateway is in the container at a fixed address; these override
# whatever .env says about S3.
WARP_PB_S3_ENABLED: "true"
WARP_PB_S3_ENDPOINT: http://127.0.0.1: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"
# The gateway's only identity, and what the homeserver signs with.
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
- seaweed-data:/seaweed/data
ports:
- "${WARP_PORT:-8008}:8008"
- "${WARP_ADMIN_PORT:-8009}:8009"
- "${WARP_WEB_PORT:-8080}:8080"
- "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333"
volumes:
warp-data:
seaweed-data:
@@ -0,0 +1,76 @@
# Warp all-in-one on PocketBase with SeaweedFS split into its roles: the
# homeserver, the admin panel, the web client, and SeaweedFS's master,
# volume, filer, S3 gateway and admin UI, all in one container pulled from
# a registry (the image is built from Dockerfile.PocketBase.SeaweedFS.split).
#
# cd Docker-AIO
# 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 in SeaweedFS (seaweed-data). The homeserver's S3 identity
# is seeded into the filer's IAM store on every boot, beside any added in
# the admin UI under Object Store → Users. 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.
#
# This and docker-compose.pocketbase.seaweedfs.yml share their project and
# volumes: SeaweedFS's data has the same layout either way, so switching
# between them 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 image comes 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.seaweedfs.split.prod.yml pull && docker compose -f docker-compose.pocketbase.seaweedfs.split.prod.yml up -d
name: warp-aio-pocketbase-seaweedfs
services:
warp:
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-aio-pocketbase-seaweedfs-split:${WARP_VERSION:-latest}
pull_policy: always
restart: unless-stopped
env_file: .env
environment:
# The gateway is in the container at a fixed address; these override
# whatever .env says about S3.
WARP_PB_S3_ENABLED: "true"
WARP_PB_S3_ENDPOINT: http://127.0.0.1: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"
# The homeserver's identity in SeaweedFS, and what it signs with.
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}
# The admin UI can mint bucket credentials, so it needs a password.
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:
- warp-data:/data
- seaweed-data:/seaweed/data
- seaweed-admin:/seaweed/admin
ports:
- "${WARP_PORT:-8008}:8008"
- "${WARP_ADMIN_PORT:-8009}:8009"
- "${WARP_WEB_PORT:-8080}:8080"
- "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333"
- "${SEAWEED_ADMIN_BIND:-127.0.0.1}:${SEAWEED_ADMIN_PORT:-23646}:23646"
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,75 @@
# Warp all-in-one on PocketBase with SeaweedFS split into its roles: the
# homeserver, the admin panel, the web client, and SeaweedFS's master,
# volume, filer, S3 gateway and admin UI, all in one container built from
# this checkout (Dockerfile.PocketBase.SeaweedFS.split).
#
# cd Docker-AIO
# 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 in SeaweedFS (seaweed-data). The homeserver's S3 identity
# is seeded into the filer's IAM store on every boot, beside any added in
# the admin UI under Object Store → Users. 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.
#
# This and docker-compose.pocketbase.seaweedfs.yml share their project and
# volumes: SeaweedFS's data has the same layout either way, so switching
# between them 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-aio-pocketbase-seaweedfs
services:
warp:
build:
context: ..
dockerfile: Docker-AIO/Dockerfile.PocketBase.SeaweedFS.split
args:
# Passed only when set, so the Dockerfile's pin stays otherwise.
- SEAWEED_VERSION
image: warp-aio-pocketbase-seaweedfs-split:latest
restart: unless-stopped
env_file: .env
environment:
# The gateway is in the container at a fixed address; these override
# whatever .env says about S3.
WARP_PB_S3_ENABLED: "true"
WARP_PB_S3_ENDPOINT: http://127.0.0.1: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"
# The homeserver's identity in SeaweedFS, and what it signs with.
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}
# The admin UI can mint bucket credentials, so it needs a password.
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:
- warp-data:/data
- seaweed-data:/seaweed/data
- seaweed-admin:/seaweed/admin
ports:
- "${WARP_PORT:-8008}:8008"
- "${WARP_ADMIN_PORT:-8009}:8009"
- "${WARP_WEB_PORT:-8080}:8080"
- "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333"
- "${SEAWEED_ADMIN_BIND:-127.0.0.1}:${SEAWEED_ADMIN_PORT:-23646}:23646"
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,62 @@
# Warp all-in-one on PocketBase with SeaweedFS: the homeserver, the admin
# panel, the web client and SeaweedFS (one `weed server -s3` process) in one
# container built from this checkout (Dockerfile.PocketBase.SeaweedFS).
#
# cd Docker-AIO
# 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 in SeaweedFS (seaweed-data), whose gateway has exactly
# one identity: the homeserver's. 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 and docker-compose.pocketbase.seaweedfs.split.yml share their project
# and volumes: SeaweedFS's data has the same layout either way, so switching
# between them 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-aio-pocketbase-seaweedfs
services:
warp:
build:
context: ..
dockerfile: Docker-AIO/Dockerfile.PocketBase.SeaweedFS
args:
# Passed only when set, so the Dockerfile's pin stays otherwise.
- SEAWEED_VERSION
image: warp-aio-pocketbase-seaweedfs:latest
restart: unless-stopped
env_file: .env
environment:
# The gateway is in the container at a fixed address; these override
# whatever .env says about S3.
WARP_PB_S3_ENABLED: "true"
WARP_PB_S3_ENDPOINT: http://127.0.0.1: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"
# The gateway's only identity, and what the homeserver signs with.
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
- seaweed-data:/seaweed/data
ports:
- "${WARP_PORT:-8008}:8008"
- "${WARP_ADMIN_PORT:-8009}:8009"
- "${WARP_WEB_PORT:-8080}:8080"
- "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333"
volumes:
warp-data:
seaweed-data:
+138 -44
View File
@@ -1,10 +1,12 @@
#!/bin/sh
# warp-aio: runs the homeserver, the admin panel and the web client (and,
# with WARP_AIO_EMBEDDED_POSTGRES=true, PostgreSQL) in one container.
# warp-aio: runs the homeserver, the admin panel and the web client in one
# container, with whatever the image adds: PostgreSQL, or SeaweedFS as the
# media store.
#
# If any of them exits, the others are stopped and the container exits, so
# the restart policy brings the whole set back. SIGTERM stops the clients
# first, then the homeserver, then PostgreSQL.
# If any process exits, the others are stopped and the container exits, so
# the restart policy brings the whole set back. SIGTERM stops them in the
# reverse of the order they started: the clients, the homeserver, then
# SeaweedFS and PostgreSQL underneath it.
#
# With arguments, runs them instead: `docker run warp-aio warp version`.
#
@@ -14,6 +16,11 @@
# $PGDATA (/data/postgres); the default
# in warp-aio-postgresql, the only image
# that has PostgreSQL
# WARP_AIO_SEAWEEDFS=server SeaweedFS as one `weed server -s3`
# WARP_AIO_SEAWEEDFS=split SeaweedFS as master, volume, filer, S3
# gateway and admin UI, one process each;
# set by the warp-aio-pocketbase-seaweedfs
# images, the only ones with SeaweedFS
set -eu
@@ -23,36 +30,53 @@ fi
log() { echo "warp-aio: $*" >&2; }
pg_pid=""
warp_pid=""
admin_pid=""
web_pid=""
# "name:pid" of every process started, newest first, so shutdown walks it in
# reverse start order.
procs=""
# stop PID...: SIGTERM, then wait for each to exit.
stop() {
for pid in "$@"; do
[ -n "$pid" ] && kill -TERM "$pid" 2>/dev/null || true
done
for pid in "$@"; do
[ -n "$pid" ] && wait "$pid" 2>/dev/null || true
done
# run NAME COMMAND...: start COMMAND in the background and track it.
run() {
name=$1
shift
"$@" &
procs="$name:$! $procs"
}
shutdown() {
trap '' TERM INT
stop "$web_pid" "$admin_pid"
stop "$warp_pid"
if [ -n "$pg_pid" ]; then
for entry in $procs; do
name=${entry%%:*}
pid=${entry#*:}
# SIGINT is PostgreSQL's fast shutdown: it does not wait for
# clients, and the homeserver is gone by now anyway.
kill -INT "$pg_pid" 2>/dev/null || true
wait "$pg_pid" 2>/dev/null || true
fi
# clients, and everything that used it is gone by now.
if [ "$name" = postgres ]; then sig=INT; else sig=TERM; fi
kill -"$sig" "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
done
exit "${1:-0}"
}
trap 'log "stopping"; shutdown 0' TERM INT
if [ "$WARP_AIO_EMBEDDED_POSTGRES" = true ]; then
# check: stop everything if any process has exited.
check() {
for entry in $procs; do
if ! kill -0 "${entry#*:}" 2>/dev/null; then
log "${entry%%:*} exited; stopping the rest"
shutdown 1
fi
done
}
# wait_http NAME URL: wait until URL answers, as long as nothing has died.
wait_http() {
until curl -fsS -o /dev/null "$2" 2>/dev/null; do
check
log "waiting for $1"
sleep 1
done
}
if [ "${WARP_AIO_EMBEDDED_POSTGRES:-false}" = true ]; then
if ! command -v postgres >/dev/null; then
log "WARP_AIO_EMBEDDED_POSTGRES=true, but this image has no PostgreSQL; use warp-aio-postgresql"
exit 1
@@ -64,10 +88,9 @@ if [ "$WARP_AIO_EMBEDDED_POSTGRES" = true ]; then
# a socket inside this container.
initdb -D "$PGDATA" -U warp --auth=trust --encoding=UTF8 --locale=C >/dev/null
fi
postgres -D "$PGDATA" -c listen_addresses='' -c unix_socket_directories=/run/postgresql &
pg_pid=$!
run postgres postgres -D "$PGDATA" -c listen_addresses='' -c unix_socket_directories=/run/postgresql
until pg_isready -q -h /run/postgresql -U warp; do
kill -0 "$pg_pid" 2>/dev/null || { log "PostgreSQL failed to start"; exit 1; }
check
sleep 1
done
if [ "$(psql -h /run/postgresql -U warp -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname = 'warp'")" != 1 ]; then
@@ -75,34 +98,105 @@ if [ "$WARP_AIO_EMBEDDED_POSTGRES" = true ]; then
fi
fi
# SeaweedFS keeps everything under /seaweed/data, in the layout `weed server
# -dir` writes, which the split roles share as well: switching between the
# two images needs no migration. Every role advertises 127.0.0.1; the volume
# server serves file content by id with no authentication at all, so only
# the S3 gateway and the admin UI are meant to be published. The volume
# server is on 8081 because 8080 is the web client.
seaweedfs="${WARP_AIO_SEAWEEDFS:-}"
if [ -n "$seaweedfs" ]; then
if ! command -v weed >/dev/null; then
log "WARP_AIO_SEAWEEDFS=$seaweedfs, but this image has no SeaweedFS; use warp-aio-pocketbase-seaweedfs"
exit 1
fi
# One pair of credentials: the gateway's identity and what the
# homeserver signs its requests with. An empty pair would leave the
# gateway in its allow-anyone default, so refuse instead.
if [ -z "${WARP_PB_S3_ACCESS_KEY:-}" ] || [ -z "${WARP_PB_S3_SECRET:-}" ]; then
log "WARP_PB_S3_ACCESS_KEY and WARP_PB_S3_SECRET are required; refusing to start an unauthenticated S3 gateway"
exit 1
fi
bucket="${WARP_PB_S3_BUCKET:-warp}"
mkdir -p /seaweed/data
fi
case "$seaweedfs" in
"") ;;
server)
# Master, volume, filer and gateway as goroutines of one process. With
# no -s3.config, the gateway takes its single identity from
# AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY, given to it alone.
run seaweedfs env AWS_ACCESS_KEY_ID="$WARP_PB_S3_ACCESS_KEY" AWS_SECRET_ACCESS_KEY="$WARP_PB_S3_SECRET" \
weed server -dir=/seaweed/data -ip=127.0.0.1 -ip.bind=0.0.0.0 \
-volume.port=8081 -volume.max=0 -master.volumeSizeLimitMB=1024 -s3 -s3.port=8333
wait_http "SeaweedFS S3 gateway" http://127.0.0.1:8333/healthz
# The homeserver never creates its bucket. Creating one that exists is
# a no-op.
echo "s3.bucket.create -name $bucket" | weed shell -master=127.0.0.1:9333 >/dev/null || true
;;
split)
# The admin UI can mint credentials for the bucket, and weed serves it
# with no authentication when the password is empty.
if [ -z "${WEED_ADMIN_PASSWORD:-}" ]; then
log "WEED_ADMIN_PASSWORD is empty; refusing to serve an unauthenticated SeaweedFS admin UI"
exit 1
fi
mkdir -p /seaweed/admin
run seaweedfs-master weed master -ip=127.0.0.1 -ip.bind=127.0.0.1 -port=9333 \
-mdir=/seaweed/data -volumeSizeLimitMB=1024 -metricsPort=9324
wait_http "SeaweedFS master" http://127.0.0.1:9333/cluster/status
run seaweedfs-volume weed volume -master=127.0.0.1:9333 -ip=127.0.0.1 -ip.bind=127.0.0.1 \
-port=8081 -dir=/seaweed/data -max=0 -metricsPort=9325
wait_http "SeaweedFS volume server" http://127.0.0.1:8081/healthz
run seaweedfs-filer weed filer -master=127.0.0.1:9333 -ip=127.0.0.1 -ip.bind=127.0.0.1 \
-port=8888 -defaultStoreDir=/seaweed/data -metricsPort=9326
wait_http "SeaweedFS filer" http://127.0.0.1:8888/healthz
# The bucket, and the homeserver's identity in the filer's IAM store,
# both re-applied on every boot; that is how a rotated secret lands.
# The store is the one the admin UI writes, so the gateway runs with no
# -config and no AWS_* variables: those are lower-priority sources that
# the first identity added in the admin UI would silently displace.
shell() { weed shell -master=127.0.0.1:9333 -filer=127.0.0.1:8888; }
printf '%s\n' \
"s3.bucket.create -name $bucket" \
"s3.configure -user warp -access_key $WARP_PB_S3_ACCESS_KEY -secret_key $WARP_PB_S3_SECRET -actions Admin -apply" |
shell >/dev/null || true
# An empty IAM store would leave the gateway open to anyone.
if ! echo "s3.configure" | shell 2>/dev/null | grep -q "$WARP_PB_S3_ACCESS_KEY"; then
log "the homeserver's S3 identity is not in the filer's IAM store; refusing to start the gateway"
shutdown 1
fi
run seaweedfs-s3 weed s3 -filer=127.0.0.1:8888 -ip.bind=0.0.0.0 -port=8333 -metricsPort=9327
run seaweedfs-admin weed admin -port=23646 -master=127.0.0.1:9333 -dataDir=/seaweed/admin -metricsPort=9328
wait_http "SeaweedFS S3 gateway" http://127.0.0.1:8333/healthz
;;
*)
log "WARP_AIO_SEAWEEDFS must be server, split or empty, not $seaweedfs"
exit 1
;;
esac
# The signing key is created on first start and kept in the volume: losing
# it means other servers no longer recognise this one.
if [ ! -f "$WARP_PRIVATE_KEY" ]; then
warp generate-keys --output "$WARP_PRIVATE_KEY"
fi
warp serve &
warp_pid=$!
if [ "$WARP_AIO_ADMIN" = true ]; then
warp-admin &
admin_pid=$!
run warp warp serve
if [ "${WARP_AIO_ADMIN:-true}" = true ]; then
run warp-admin warp-admin
fi
if [ "$WARP_AIO_WEB" = true ]; then
warp-web &
web_pid=$!
if [ "${WARP_AIO_WEB:-true}" = true ]; then
run warp-web warp-web
fi
# Busybox sh has no `wait -n`, so check on the processes every second. The
# sleep runs in the background so a signal is handled at once.
while :; do
for entry in "postgres:$pg_pid" "warp:$warp_pid" "warp-admin:$admin_pid" "warp-web:$web_pid"; do
pid="${entry#*:}"
if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then
log "${entry%%:*} exited; stopping the rest"
shutdown 1
fi
done
check
sleep 1 &
wait $! || true
done