Docker-AIO: PocketBase image with media in an external S3 bucket
Dockerfile.PocketBase.s3 is the PocketBase all-in-one image with S3 turned on: media goes to an existing bucket on WARP_PB_S3_ENDPOINT (AWS, MinIO, or a SeaweedFS elsewhere), and the database stays in /data/pb_data. The compose files require the endpoint and key pair, default the bucket to warp, the region to us-east-1 and path style to true, and add host.docker.internal so the endpoint can be on the Docker host. Modelled on DriverVault's external-S3 compose files. The entrypoint now waits up to 30 seconds for an external S3 endpoint to answer and stops if it doesn't, instead of starting a homeserver that would fail on its first upload. WARP_PB_S3_FORCE_PATH_STYLE is left empty in .env.example so the S3 file's default applies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
36d83551aa
commit
332916e9db
6 files changed
+283
-2
No files matched your search
@@ -119,13 +119,19 @@ WARP_DB_AUTO_MIGRATE=
|
||||
# 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.
|
||||
#
|
||||
# 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=
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# warp-aio-pocketbase-s3 — the homeserver, the admin panel and the web client
|
||||
# in one image, on PocketBase, with media in an S3 bucket that already exists
|
||||
# somewhere else: AWS, MinIO, a SeaweedFS on another host. The database stays
|
||||
# in /data/pb_data. For a SeaweedFS that comes up inside the container, see
|
||||
# Dockerfile.PocketBase.SeaweedFS.
|
||||
#
|
||||
# Build from the repository root:
|
||||
#
|
||||
# docker build -t warp-aio-pocketbase-s3 -f Docker-AIO/Dockerfile.PocketBase.s3 .
|
||||
# docker run -p 8008:8008 -p 8009:8009 -p 8080:8080 -v warp-data:/data \
|
||||
# -e WARP_SERVER_NAME=warp.example \
|
||||
# -e WARP_PB_S3_ENDPOINT=https://s3.example.org -e WARP_PB_S3_BUCKET=warp \
|
||||
# -e WARP_PB_S3_ACCESS_KEY=... -e WARP_PB_S3_SECRET=... \
|
||||
# warp-aio-pocketbase-s3
|
||||
#
|
||||
# WARP_PB_S3_ENDPOINT, WARP_PB_S3_ACCESS_KEY and WARP_PB_S3_SECRET are
|
||||
# required; the bucket (default warp) must exist, since nothing here creates
|
||||
# it. Path-style addressing is on, which SeaweedFS and MinIO need; set
|
||||
# WARP_PB_S3_FORCE_PATH_STYLE=false for AWS S3 proper. The container waits up
|
||||
# to 30 seconds for the endpoint to answer before it starts the homeserver.
|
||||
|
||||
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
|
||||
|
||||
|
||||
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 warp.yaml.example /etc/warp/warp.yaml.example
|
||||
COPY --chmod=755 Docker-AIO/entrypoint.sh /usr/local/bin/warp-aio
|
||||
|
||||
# The database, signing key and anything stored before S3 was on live
|
||||
# here; mount a volume on it.
|
||||
WORKDIR /data
|
||||
RUN chown warp:warp /data
|
||||
USER warp
|
||||
|
||||
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_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"
|
||||
|
||||
EXPOSE 8008 8009 8080
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD curl -fsS http://127.0.0.1:8008/healthz && \
|
||||
{ [ "$WARP_AIO_ADMIN" != true ] || curl -fsS http://127.0.0.1:8009/healthz; } && \
|
||||
{ [ "$WARP_AIO_WEB" != true ] || curl -fsS http://127.0.0.1:8080/healthz; } || exit 1
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--", "warp-aio"]
|
||||
+24
-1
@@ -10,6 +10,7 @@ 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.s3`](Dockerfile.PocketBase.s3) | `warp-aio-pocketbase-s3` | PocketBase, media in an existing S3 bucket elsewhere |
|
||||
| [`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 |
|
||||
|
||||
@@ -18,6 +19,7 @@ policy brings the set back.
|
||||
| `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.s3.yml` | `warp-aio-pocketbase-s3`, 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 |
|
||||
@@ -44,6 +46,26 @@ 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.
|
||||
|
||||
## S3 elsewhere
|
||||
|
||||
`Dockerfile.PocketBase.s3` keeps the PocketBase database in
|
||||
`/data/pb_data` and puts media in a bucket on an S3 endpoint that already
|
||||
exists: AWS, MinIO, or a SeaweedFS on another host. Nothing in the
|
||||
container runs a gateway or creates the bucket.
|
||||
|
||||
```sh
|
||||
cp .env.example .env # set WARP_PB_S3_ENDPOINT, WARP_PB_S3_ACCESS_KEY,
|
||||
# WARP_PB_S3_SECRET (and WARP_PB_S3_BUCKET)
|
||||
docker compose -f docker-compose.pocketbase.s3.yml up -d --build
|
||||
```
|
||||
|
||||
Path-style addressing is on by default, which SeaweedFS and MinIO need;
|
||||
set `WARP_PB_S3_FORCE_PATH_STYLE=false` for AWS S3. For a gateway on the
|
||||
Docker host, use `http://host.docker.internal:8333`: the compose files add
|
||||
that host entry. The container waits up to 30 seconds for the endpoint to
|
||||
answer and stops if it does not, rather than starting a homeserver that
|
||||
would fail on its first upload.
|
||||
|
||||
## SeaweedFS
|
||||
|
||||
The two SeaweedFS images keep the PocketBase database in `/data/pb_data`
|
||||
@@ -107,9 +129,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 .
|
||||
docker build -t $WARP_REGISTRY/warp-aio-pocketbase-s3:$WARP_VERSION -f Docker-AIO/Dockerfile.PocketBase.s3 .
|
||||
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 \
|
||||
for i in warp-aio warp-aio-pocketbase warp-aio-postgresql warp-aio-pocketbase-s3 \
|
||||
warp-aio-pocketbase-seaweedfs warp-aio-pocketbase-seaweedfs-split; do
|
||||
docker push $WARP_REGISTRY/$i:$WARP_VERSION
|
||||
done
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Warp all-in-one on PocketBase with media in an S3 bucket elsewhere: the
|
||||
# homeserver, the admin panel and the web client in one container pulled
|
||||
# from a registry (the image is built from Dockerfile.PocketBase.s3).
|
||||
#
|
||||
# cd Docker-AIO
|
||||
# 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 inside the container instead, see
|
||||
# docker-compose.pocketbase.seaweedfs.prod.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 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.s3.prod.yml pull && docker compose -f docker-compose.pocketbase.s3.prod.yml up -d
|
||||
|
||||
name: warp-aio-pocketbase-s3
|
||||
|
||||
services:
|
||||
warp:
|
||||
image: ${WARP_REGISTRY:-ghcr.io/dariuszjaniak}/warp-aio-pocketbase-s3:${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"
|
||||
env_file: .env
|
||||
environment:
|
||||
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"
|
||||
- "${WARP_ADMIN_PORT:-8009}:8009"
|
||||
- "${WARP_WEB_PORT:-8080}:8080"
|
||||
|
||||
volumes:
|
||||
warp-data:
|
||||
@@ -0,0 +1,56 @@
|
||||
# Warp all-in-one on PocketBase with media in an S3 bucket elsewhere: the
|
||||
# homeserver, the admin panel and the web client in one container built from
|
||||
# this checkout (Dockerfile.PocketBase.s3).
|
||||
#
|
||||
# cd Docker-AIO
|
||||
# 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 inside the container instead, 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-aio-pocketbase-s3
|
||||
|
||||
services:
|
||||
warp:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Docker-AIO/Dockerfile.PocketBase.s3
|
||||
image: warp-aio-pocketbase-s3:latest
|
||||
restart: unless-stopped
|
||||
extra_hosts:
|
||||
# Lets WARP_PB_S3_ENDPOINT name the Docker host as host.docker.internal.
|
||||
- "host.docker.internal:host-gateway"
|
||||
env_file: .env
|
||||
environment:
|
||||
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"
|
||||
- "${WARP_ADMIN_PORT:-8009}:8009"
|
||||
- "${WARP_WEB_PORT:-8080}:8080"
|
||||
|
||||
volumes:
|
||||
warp-data:
|
||||
@@ -179,6 +179,22 @@ split)
|
||||
;;
|
||||
esac
|
||||
|
||||
# An S3 bucket elsewhere: the homeserver starts without checking it, and
|
||||
# would only fail on the first upload. Wait for the endpoint to answer at
|
||||
# all (any HTTP status will do: without credentials it may well refuse).
|
||||
if [ "${WARP_PB_S3_ENABLED:-false}" = true ] && [ -z "$seaweedfs" ] && [ -n "${WARP_PB_S3_ENDPOINT:-}" ]; then
|
||||
tries=0
|
||||
until curl -s -o /dev/null --max-time 5 "$WARP_PB_S3_ENDPOINT"; do
|
||||
tries=$((tries + 1))
|
||||
if [ "$tries" -ge 30 ]; then
|
||||
log "S3 endpoint $WARP_PB_S3_ENDPOINT does not answer; stopping"
|
||||
shutdown 1
|
||||
fi
|
||||
log "waiting for S3 endpoint $WARP_PB_S3_ENDPOINT"
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
# 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
|
||||
|
||||
Reference in new issue
Block a user