Skip to content

Storage backends

Plugwerk persists every uploaded plugin artifact via a pluggable storage backend. Two backends ship today:

  • fs — local filesystem under PLUGWERK_STORAGE_ROOT. Default. Single-node, no extra moving parts.
  • s3 — S3-compatible object storage. Enables horizontal scaling, cloud-managed hosting (no persistent volume), DR / geographic replication, and SaaS multi-tenant setups. Tested against AWS S3, MinIO, Cloudflare R2, and Hetzner Object Storage — every other S3-compatible target should work the same way.

The backend is selected via PLUGWERK_STORAGE_TYPE. Switching it on a running deployment requires a manual sync — see Migration between backends.

Use case Pick
Single-node Docker Compose / single VM fs
Existing deployment, no horizontal-scaling requirement fs
Kubernetes / multi-replica deployment without a shared volume s3
Cloud-managed (serverless container, no persistent disk) s3
Cross-region disaster recovery / replication s3
SaaS multi-tenant where one bucket per tenant or shared bucket s3

The fs default needs no configuration beyond the standard Docker volume mount in Deployment — a fresh install starts there and stays there until the operator decides otherwise.

Variable Default Description
PLUGWERK_STORAGE_TYPE fs Storage backend selector
PLUGWERK_STORAGE_ROOT /var/plugwerk/artifacts Directory for uploaded plugin artifacts

The default directory is created and owned by the non-root plugwerk user in the official Docker image. Operators who override PLUGWERK_STORAGE_ROOT to a custom path are responsible for ensuring the target directory is writable by the container user.

Artifacts are stored as flat files named <namespace-uuid>:<plugin-id>:<version>:<extension>. The colons are deliberate — see Migration between backends for why this matters when copying to or from S3.

Setting PLUGWERK_STORAGE_TYPE=s3 switches Plugwerk to the S3 backend. The S3 client is the AWS SDK for Java v2; any endpoint that speaks the S3 API works.

Variable Required when Default Purpose
PLUGWERK_STORAGE_TYPE always fs Set to s3 to switch backend
PLUGWERK_STORAGE_S3_BUCKET type=s3 Must already exist — Plugwerk does NOT create buckets
PLUGWERK_STORAGE_S3_REGION type=s3 AWS region (eu-central-1) or vendor-required placeholder (auto for R2, us-east-1 for MinIO)
PLUGWERK_STORAGE_S3_ENDPOINT non-AWS empty Override URL. Leave blank for AWS S3
PLUGWERK_STORAGE_S3_ACCESS_KEY static credentials empty Set together with SECRET_KEY. Both blank → AWS DefaultCredentialsProvider chain
PLUGWERK_STORAGE_S3_SECRET_KEY static credentials empty See ACCESS_KEY
PLUGWERK_STORAGE_S3_KEY_PREFIX optional empty Prepended to every artifact key. For shared buckets across environments (prod/plugwerk/, staging/plugwerk/). Must not start with /
PLUGWERK_STORAGE_S3_PATH_STYLE_ACCESS MinIO false true for MinIO and other endpoints without virtual-hosted URLs. AWS S3 / R2 / Hetzner all work with the default
PLUGWERK_STORAGE_S3_FAIL_FAST_ON_BUCKET_MISSING production false When true, the server refuses to start if the startup HeadBucket probe fails. Default keeps the server running with an ERROR log line. Set true in container orchestrators that prefer fail-closed semantics

The four endpoint variants Plugwerk treats as first-class are below. All three knobs that vary by provider — endpoint, region, path-style-access — are highlighted in each block.

Terminal window
PLUGWERK_STORAGE_TYPE=s3
PLUGWERK_STORAGE_S3_BUCKET=plugwerk-artifacts
PLUGWERK_STORAGE_S3_REGION=eu-central-1
# Leave creds + endpoint blank → DefaultCredentialsProvider picks them up

For the canonical defaults and inline operator notes shipped with the server, see the full .env.example reference on the Configuration page.

Plugwerk does not ship a built-in migration command. The key layout is identical between backends — every artifact is a flat file or object named <namespace-uuid>:<plugin-id>:<version>:<extension>, no nested directories — so a plain sync works in either direction.

Terminal window
# 1. Maintenance window — stop the server.
docker compose stop plugwerk-server
# 2. Sync the artifact directory to the bucket. Key layout is identical:
# every file becomes an object with the same name.
aws s3 sync /var/plugwerk/artifacts s3://plugwerk-artifacts/
# 3. Flip the env vars and start the server.
cat >> .env <<'EOF'
PLUGWERK_STORAGE_TYPE=s3
PLUGWERK_STORAGE_S3_BUCKET=plugwerk-artifacts
PLUGWERK_STORAGE_S3_REGION=eu-central-1
EOF
docker compose up -d plugwerk-server
# 4. Verify the server log:
# "S3 bucket 'plugwerk-artifacts' is reachable"
# 5. Smoke-test by downloading one pre-switch plugin via the Admin UI.

Symmetric:

Terminal window
docker compose stop plugwerk-server
aws s3 sync s3://plugwerk-artifacts/ /var/plugwerk/artifacts
# Flip PLUGWERK_STORAGE_TYPE=fs (the S3_* vars can stay — they are ignored
# when type=fs).
docker compose up -d plugwerk-server

Open Admin → Storage Consistency in the web UI and trigger a scan. The check reports two diffs: artifact files in storage that no longer have a matching plugin_release row (orphans, harmless), and DB rows whose artifact is missing from storage (broken — fix before declaring the migration done).

For a quick CLI sanity check without opening the UI:

Terminal window
# Row count
psql "$PLUGWERK_DB_URL" -c "SELECT COUNT(*) FROM plugin_release;"
# File count in target backend
find /var/plugwerk/artifacts -type f | wc -l # fs
aws s3 ls s3://plugwerk-artifacts/ --recursive | wc -l # s3

The numbers should match. Storage may legitimately carry slightly more entries (orphans from manual operations), but it must never carry fewer than the number of plugin releases.

  • Pre-signed URLs for direct client uploads — every upload still flows through the server.
  • Google Cloud Storage and Azure Blob Storage native APIs — both vendors offer S3-compatible facades that work, but their native SDKs are not wired in.
  • Multiple buckets per Plugwerk instance — one backend, one bucket. Use PLUGWERK_STORAGE_S3_KEY_PREFIX to share a single bucket across environments.

These are deliberate scope boundaries, not bugs.

  • Configuration — the full environment-variable reference
  • Deployment — Docker Compose, standalone container, JAR run options
  • Monitoring — health endpoint, Prometheus scrape configuration