Initial commit: PocketBase Docker setup (root & rootless variants)
Add Dockerfiles, Compose stacks, and entrypoints for running PocketBase in both root and rootless container configurations, plus .env.example documenting version, port, superuser bootstrap, and encryption settings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
# Copy this file to `.env` and adjust as needed:
|
||||||
|
# cp .env.example .env
|
||||||
|
# Compose reads `.env` automatically. Keep `.env` OUT of version control.
|
||||||
|
|
||||||
|
# ── General ───────────────────────────────────────────────────────────
|
||||||
|
# PocketBase version to build (must match a published release tag, without the leading "v")
|
||||||
|
PB_VERSION=0.39.7
|
||||||
|
|
||||||
|
# Name of the running container
|
||||||
|
CONTAINER_NAME=pocketbase
|
||||||
|
|
||||||
|
# Port PocketBase listens on (used both inside the container and for the host mapping)
|
||||||
|
PB_PORT=8080
|
||||||
|
|
||||||
|
# Port for the root variant (only used with `docker compose --profile root up`)
|
||||||
|
PB_PORT_ROOT=8081
|
||||||
|
|
||||||
|
# ── Superuser bootstrap ───────────────────────────────────────────────
|
||||||
|
# On every start the entrypoint checks whether PB_ADMIN_EMAIL already exists:
|
||||||
|
# • exists → nothing happens (PB_ADMIN_PASSWORD is ignored/optional)
|
||||||
|
# • missing → the superuser is created using PB_ADMIN_PASSWORD (required in that case)
|
||||||
|
# No defaults. Leave blank to skip bootstrapping entirely.
|
||||||
|
PB_ADMIN_EMAIL=
|
||||||
|
PB_ADMIN_PASSWORD=
|
||||||
|
|
||||||
|
# ── Encryption ────────────────────────────────────────────────────────
|
||||||
|
# Encrypts the application settings stored in PocketBase's database.
|
||||||
|
# Must be EXACTLY 32 characters. Leave blank to disable.
|
||||||
|
# Generate one with: openssl rand -hex 16
|
||||||
|
# Docs: https://pocketbase.io/docs/going-to-production/#enable-settings-encryption
|
||||||
|
PB_ENCRYPTION_KEY=
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Shell scripts and Docker files run inside Linux containers — they MUST keep
|
||||||
|
# LF endings or the container fails with "no such file or directory".
|
||||||
|
*.sh text eol=lf
|
||||||
|
Dockerfile* text eol=lf
|
||||||
|
docker-compose*.yml text eol=lf
|
||||||
|
.env.example text eol=lf
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# ── Secrets / local config ────────────────────────────────────────────
|
||||||
|
# .env holds admin credentials and the encryption key — never commit it.
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# ── PocketBase runtime data ───────────────────────────────────────────
|
||||||
|
# Local bind-mounts of the SQLite DB, uploads, and generated files.
|
||||||
|
pb_data/
|
||||||
|
pb_public/
|
||||||
|
pocketbase
|
||||||
|
pocketbase.exe
|
||||||
|
|
||||||
|
# Migrations/hooks are usually versioned; ignore only local scratch copies.
|
||||||
|
# pb_migrations/
|
||||||
|
# pb_hooks/
|
||||||
|
|
||||||
|
# ── Tooling ───────────────────────────────────────────────────────────
|
||||||
|
.claude/settings.local.json
|
||||||
|
|
||||||
|
# ── OS / editor cruft ─────────────────────────────────────────────────
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# ---- build stage: fetch + verify + unzip PocketBase ----
|
||||||
|
FROM alpine:3.22 AS build
|
||||||
|
|
||||||
|
ARG PB_VERSION=0.39.7
|
||||||
|
# provided automatically by buildx (e.g. amd64, arm64)
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
RUN apk add --no-cache unzip ca-certificates
|
||||||
|
|
||||||
|
# download the release zip and its checksums file
|
||||||
|
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_${TARGETARCH}.zip /tmp/pb.zip
|
||||||
|
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_checksums.txt /tmp/pb_checksums.txt
|
||||||
|
|
||||||
|
# verify integrity against the published checksums, then unzip
|
||||||
|
RUN grep "pocketbase_${PB_VERSION}_linux_${TARGETARCH}.zip" /tmp/pb_checksums.txt \
|
||||||
|
| sed "s#pocketbase_${PB_VERSION}_linux_${TARGETARCH}.zip#/tmp/pb.zip#" \
|
||||||
|
| sha256sum -c - \
|
||||||
|
&& unzip /tmp/pb.zip -d /pb/
|
||||||
|
|
||||||
|
# ---- runtime stage: minimal image with just the binary ----
|
||||||
|
# NOTE: this variant runs PocketBase as root (no dedicated user / USER directive).
|
||||||
|
FROM alpine:3.22
|
||||||
|
|
||||||
|
# ca-certificates: outbound TLS (e.g. OAuth, mailer)
|
||||||
|
# sqlite: used by the entrypoint to check whether the superuser already exists
|
||||||
|
RUN apk add --no-cache ca-certificates sqlite \
|
||||||
|
&& mkdir -p /pb/pb_data
|
||||||
|
|
||||||
|
COPY --from=build /pb/pocketbase /pb/pocketbase
|
||||||
|
COPY --chmod=0755 entrypoint.root.sh /pb/entrypoint.root.sh
|
||||||
|
|
||||||
|
# uncomment to copy the local pb_migrations dir into the image
|
||||||
|
# COPY ./pb_migrations /pb/pb_migrations
|
||||||
|
|
||||||
|
# uncomment to copy the local pb_hooks dir into the image
|
||||||
|
# COPY ./pb_hooks /pb/pb_hooks
|
||||||
|
|
||||||
|
# persist the SQLite database and uploaded files across container recreations
|
||||||
|
VOLUME /pb/pb_data
|
||||||
|
|
||||||
|
# default listen port; override at runtime with -e PB_PORT=...
|
||||||
|
ENV PB_PORT=8080
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# shell form so ${PB_PORT} is expanded from the runtime environment
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||||
|
CMD wget -qO- "http://127.0.0.1:${PB_PORT}/api/health" || exit 1
|
||||||
|
|
||||||
|
# runs as root (default user — no USER directive)
|
||||||
|
# entrypoint handles superuser bootstrap + encryption, then execs `serve`
|
||||||
|
ENTRYPOINT ["/pb/entrypoint.root.sh"]
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# ---- build stage: fetch + verify + unzip PocketBase ----
|
||||||
|
FROM alpine:3.22 AS build
|
||||||
|
|
||||||
|
ARG PB_VERSION=0.39.7
|
||||||
|
# provided automatically by buildx (e.g. amd64, arm64)
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
RUN apk add --no-cache unzip ca-certificates
|
||||||
|
|
||||||
|
# download the release zip and its checksums file
|
||||||
|
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_${TARGETARCH}.zip /tmp/pb.zip
|
||||||
|
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_checksums.txt /tmp/pb_checksums.txt
|
||||||
|
|
||||||
|
# verify integrity against the published checksums, then unzip
|
||||||
|
RUN grep "pocketbase_${PB_VERSION}_linux_${TARGETARCH}.zip" /tmp/pb_checksums.txt \
|
||||||
|
| sed "s#pocketbase_${PB_VERSION}_linux_${TARGETARCH}.zip#/tmp/pb.zip#" \
|
||||||
|
| sha256sum -c - \
|
||||||
|
&& unzip /tmp/pb.zip -d /pb/
|
||||||
|
|
||||||
|
# ---- runtime stage: minimal image with just the binary ----
|
||||||
|
FROM alpine:3.22
|
||||||
|
|
||||||
|
# ca-certificates: outbound TLS (e.g. OAuth, mailer)
|
||||||
|
# sqlite: used by the entrypoint to check whether the superuser already exists
|
||||||
|
RUN apk add --no-cache ca-certificates sqlite \
|
||||||
|
# non-root user to run PocketBase
|
||||||
|
&& addgroup -S pb && adduser -S -G pb -H -D pb \
|
||||||
|
&& mkdir -p /pb/pb_data \
|
||||||
|
&& chown -R pb:pb /pb
|
||||||
|
|
||||||
|
COPY --from=build --chown=pb:pb /pb/pocketbase /pb/pocketbase
|
||||||
|
COPY --chown=pb:pb --chmod=0755 entrypoint.rootless.sh /pb/entrypoint.rootless.sh
|
||||||
|
|
||||||
|
# uncomment to copy the local pb_migrations dir into the image
|
||||||
|
# COPY --chown=pb:pb ./pb_migrations /pb/pb_migrations
|
||||||
|
|
||||||
|
# uncomment to copy the local pb_hooks dir into the image
|
||||||
|
# COPY --chown=pb:pb ./pb_hooks /pb/pb_hooks
|
||||||
|
|
||||||
|
# persist the SQLite database and uploaded files across container recreations
|
||||||
|
VOLUME /pb/pb_data
|
||||||
|
|
||||||
|
USER pb
|
||||||
|
|
||||||
|
# default listen port; override at runtime with -e PB_PORT=...
|
||||||
|
ENV PB_PORT=8080
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# shell form so ${PB_PORT} is expanded from the runtime environment
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||||
|
CMD wget -qO- "http://127.0.0.1:${PB_PORT}/api/health" || exit 1
|
||||||
|
|
||||||
|
# entrypoint handles superuser bootstrap + encryption, then execs `serve`
|
||||||
|
ENTRYPOINT ["/pb/entrypoint.rootless.sh"]
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Root PocketBase (runs as root inside the container).
|
||||||
|
# Run: docker compose -f docker-compose.root.yml up -d
|
||||||
|
# A distinct project name keeps its container/volume separate from the rootless stack.
|
||||||
|
name: pocketbase-root
|
||||||
|
|
||||||
|
services:
|
||||||
|
pocketbase:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.root
|
||||||
|
args:
|
||||||
|
# PocketBase version to bake into the image (see .env)
|
||||||
|
PB_VERSION: ${PB_VERSION:-0.39.7}
|
||||||
|
image: pocketbase:${PB_VERSION:-0.39.7}-root
|
||||||
|
container_name: ${CONTAINER_NAME:-pocketbase}-root
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# server
|
||||||
|
PB_PORT: ${PB_PORT_ROOT:-8081}
|
||||||
|
# superuser bootstrap (checked/created by the entrypoint on every start)
|
||||||
|
PB_ADMIN_EMAIL: ${PB_ADMIN_EMAIL:-}
|
||||||
|
PB_ADMIN_PASSWORD: ${PB_ADMIN_PASSWORD:-}
|
||||||
|
# settings encryption (must be exactly 32 characters when set)
|
||||||
|
PB_ENCRYPTION_KEY: ${PB_ENCRYPTION_KEY:-}
|
||||||
|
ports:
|
||||||
|
# host:container
|
||||||
|
- "${PB_PORT_ROOT:-8081}:${PB_PORT_ROOT:-8081}"
|
||||||
|
volumes:
|
||||||
|
# persist the SQLite database and uploaded files
|
||||||
|
- pb_data:/pb/pb_data
|
||||||
|
# uncomment to develop migrations/hooks against the running instance
|
||||||
|
# - ./pb_migrations:/pb/pb_migrations
|
||||||
|
# - ./pb_hooks:/pb/pb_hooks
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:${PB_PORT_ROOT:-8081}/api/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
start_period: 5s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pb_data:
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Rootless PocketBase (runs as the non-root `pb` user).
|
||||||
|
# Run: docker compose -f docker-compose.rootless.yml up -d
|
||||||
|
# For the root variant, use: docker compose -f docker-compose.root.yml up -d
|
||||||
|
name: pocketbase
|
||||||
|
|
||||||
|
services:
|
||||||
|
pocketbase:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.rootless
|
||||||
|
args:
|
||||||
|
# PocketBase version to bake into the image (see .env)
|
||||||
|
PB_VERSION: ${PB_VERSION:-0.39.7}
|
||||||
|
image: pocketbase:${PB_VERSION:-0.39.7}
|
||||||
|
container_name: ${CONTAINER_NAME:-pocketbase}
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# server
|
||||||
|
PB_PORT: ${PB_PORT:-8080}
|
||||||
|
# superuser bootstrap (checked/created by the entrypoint on every start)
|
||||||
|
PB_ADMIN_EMAIL: ${PB_ADMIN_EMAIL:-}
|
||||||
|
PB_ADMIN_PASSWORD: ${PB_ADMIN_PASSWORD:-}
|
||||||
|
# settings encryption (must be exactly 32 characters when set)
|
||||||
|
PB_ENCRYPTION_KEY: ${PB_ENCRYPTION_KEY:-}
|
||||||
|
ports:
|
||||||
|
# host:container
|
||||||
|
- "${PB_PORT:-8080}:${PB_PORT:-8080}"
|
||||||
|
volumes:
|
||||||
|
# persist the SQLite database and uploaded files
|
||||||
|
- pb_data:/pb/pb_data
|
||||||
|
# uncomment to develop migrations/hooks against the running instance
|
||||||
|
# - ./pb_migrations:/pb/pb_migrations
|
||||||
|
# - ./pb_hooks:/pb/pb_hooks
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:${PB_PORT:-8080}/api/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
start_period: 5s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pb_data:
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PocketBase container entrypoint (root variant).
|
||||||
|
# Runs on every start: validates config, conditionally bootstraps the superuser,
|
||||||
|
# then execs the server. POSIX sh (busybox ash on Alpine).
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DATA_DIR="/pb/pb_data"
|
||||||
|
DB_FILE="${DATA_DIR}/data.db"
|
||||||
|
PB_PORT="${PB_PORT:-8080}"
|
||||||
|
|
||||||
|
# ---- assemble global flags shared by every pocketbase invocation ----
|
||||||
|
set -- --dir="${DATA_DIR}"
|
||||||
|
|
||||||
|
if [ -n "${PB_ENCRYPTION_KEY}" ]; then
|
||||||
|
# PocketBase requires the settings-encryption key to be exactly 32 chars.
|
||||||
|
if [ "${#PB_ENCRYPTION_KEY}" -ne 32 ]; then
|
||||||
|
echo "[entrypoint] ERROR: PB_ENCRYPTION_KEY must be exactly 32 characters (got ${#PB_ENCRYPTION_KEY})." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[entrypoint] Settings encryption enabled."
|
||||||
|
set -- "$@" --encryptionEnv=PB_ENCRYPTION_KEY
|
||||||
|
fi
|
||||||
|
|
||||||
|
GLOBAL_FLAGS="$*"
|
||||||
|
|
||||||
|
# ---- does the requested superuser already exist? ----
|
||||||
|
superuser_exists() {
|
||||||
|
# No DB yet => first boot => cannot exist.
|
||||||
|
[ -f "${DB_FILE}" ] || return 1
|
||||||
|
count=$(sqlite3 "${DB_FILE}" \
|
||||||
|
"SELECT COUNT(*) FROM _superusers WHERE email = '${PB_ADMIN_EMAIL}';" 2>/dev/null) || return 1
|
||||||
|
[ "${count:-0}" -gt 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- superuser bootstrap (always evaluated, before the server starts) ----
|
||||||
|
if [ -n "${PB_ADMIN_EMAIL}" ]; then
|
||||||
|
if superuser_exists; then
|
||||||
|
echo "[entrypoint] Superuser '${PB_ADMIN_EMAIL}' already exists — skipping creation."
|
||||||
|
else
|
||||||
|
if [ -z "${PB_ADMIN_PASSWORD}" ]; then
|
||||||
|
echo "[entrypoint] ERROR: superuser '${PB_ADMIN_EMAIL}' does not exist and PB_ADMIN_PASSWORD is not set." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[entrypoint] Creating superuser '${PB_ADMIN_EMAIL}'..."
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
/pb/pocketbase superuser create "${PB_ADMIN_EMAIL}" "${PB_ADMIN_PASSWORD}" ${GLOBAL_FLAGS}
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[entrypoint] PB_ADMIN_EMAIL not set — skipping superuser bootstrap."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[entrypoint] Starting PocketBase on 0.0.0.0:${PB_PORT}"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
exec /pb/pocketbase serve --http="0.0.0.0:${PB_PORT}" ${GLOBAL_FLAGS}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PocketBase container entrypoint.
|
||||||
|
# Runs on every start: validates config, conditionally bootstraps the superuser,
|
||||||
|
# then execs the server. POSIX sh (busybox ash on Alpine).
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DATA_DIR="/pb/pb_data"
|
||||||
|
DB_FILE="${DATA_DIR}/data.db"
|
||||||
|
PB_PORT="${PB_PORT:-8080}"
|
||||||
|
|
||||||
|
# ---- assemble global flags shared by every pocketbase invocation ----
|
||||||
|
set -- --dir="${DATA_DIR}"
|
||||||
|
|
||||||
|
if [ -n "${PB_ENCRYPTION_KEY}" ]; then
|
||||||
|
# PocketBase requires the settings-encryption key to be exactly 32 chars.
|
||||||
|
if [ "${#PB_ENCRYPTION_KEY}" -ne 32 ]; then
|
||||||
|
echo "[entrypoint] ERROR: PB_ENCRYPTION_KEY must be exactly 32 characters (got ${#PB_ENCRYPTION_KEY})." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[entrypoint] Settings encryption enabled."
|
||||||
|
set -- "$@" --encryptionEnv=PB_ENCRYPTION_KEY
|
||||||
|
fi
|
||||||
|
|
||||||
|
GLOBAL_FLAGS="$*"
|
||||||
|
|
||||||
|
# ---- does the requested superuser already exist? ----
|
||||||
|
superuser_exists() {
|
||||||
|
# No DB yet => first boot => cannot exist.
|
||||||
|
[ -f "${DB_FILE}" ] || return 1
|
||||||
|
count=$(sqlite3 "${DB_FILE}" \
|
||||||
|
"SELECT COUNT(*) FROM _superusers WHERE email = '${PB_ADMIN_EMAIL}';" 2>/dev/null) || return 1
|
||||||
|
[ "${count:-0}" -gt 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- superuser bootstrap (always evaluated, before the server starts) ----
|
||||||
|
if [ -n "${PB_ADMIN_EMAIL}" ]; then
|
||||||
|
if superuser_exists; then
|
||||||
|
echo "[entrypoint] Superuser '${PB_ADMIN_EMAIL}' already exists — skipping creation."
|
||||||
|
else
|
||||||
|
if [ -z "${PB_ADMIN_PASSWORD}" ]; then
|
||||||
|
echo "[entrypoint] ERROR: superuser '${PB_ADMIN_EMAIL}' does not exist and PB_ADMIN_PASSWORD is not set." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[entrypoint] Creating superuser '${PB_ADMIN_EMAIL}'..."
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
/pb/pocketbase superuser create "${PB_ADMIN_EMAIL}" "${PB_ADMIN_PASSWORD}" ${GLOBAL_FLAGS}
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[entrypoint] PB_ADMIN_EMAIL not set — skipping superuser bootstrap."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[entrypoint] Starting PocketBase on 0.0.0.0:${PB_PORT}"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
exec /pb/pocketbase serve --http="0.0.0.0:${PB_PORT}" ${GLOBAL_FLAGS}
|
||||||
Reference in New Issue
Block a user