Storage backends
Plugwerk persists every uploaded plugin artifact via a pluggable storage backend. Two backends ship today:
fs— local filesystem underPLUGWERK_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.
Choosing a backend
Section titled “Choosing a backend”| 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.
Filesystem (fs)
Section titled “Filesystem (fs)”| 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.
S3-compatible object storage (s3)
Section titled “S3-compatible object storage (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.
Configuration
Section titled “Configuration”| 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 |
Provider examples
Section titled “Provider examples”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.
PLUGWERK_STORAGE_TYPE=s3PLUGWERK_STORAGE_S3_BUCKET=plugwerk-artifactsPLUGWERK_STORAGE_S3_REGION=eu-central-1# Leave creds + endpoint blank → DefaultCredentialsProvider picks them upPLUGWERK_STORAGE_TYPE=s3PLUGWERK_STORAGE_S3_ENDPOINT=http://minio:9000PLUGWERK_STORAGE_S3_BUCKET=plugwerk-artifactsPLUGWERK_STORAGE_S3_REGION=us-east-1PLUGWERK_STORAGE_S3_ACCESS_KEY=minioadminPLUGWERK_STORAGE_S3_SECRET_KEY=minioadminPLUGWERK_STORAGE_S3_PATH_STYLE_ACCESS=truePLUGWERK_STORAGE_TYPE=s3PLUGWERK_STORAGE_S3_ENDPOINT=https://<account>.r2.cloudflarestorage.comPLUGWERK_STORAGE_S3_BUCKET=plugwerk-artifactsPLUGWERK_STORAGE_S3_REGION=autoPLUGWERK_STORAGE_S3_ACCESS_KEY=<r2-access-key>PLUGWERK_STORAGE_S3_SECRET_KEY=<r2-secret-key>PLUGWERK_STORAGE_TYPE=s3PLUGWERK_STORAGE_S3_ENDPOINT=https://fsn1.your-objectstorage.comPLUGWERK_STORAGE_S3_BUCKET=plugwerk-artifactsPLUGWERK_STORAGE_S3_REGION=fsn1PLUGWERK_STORAGE_S3_ACCESS_KEY=<hetzner-access-key>PLUGWERK_STORAGE_S3_SECRET_KEY=<hetzner-secret-key>For the canonical defaults and inline operator notes shipped with the server, see the full .env.example reference on the Configuration page.
Migration between backends
Section titled “Migration between backends”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.
Filesystem → S3
Section titled “Filesystem → S3”# 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=s3PLUGWERK_STORAGE_S3_BUCKET=plugwerk-artifactsPLUGWERK_STORAGE_S3_REGION=eu-central-1EOFdocker 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.# 1. Maintenance window — stop the server.docker compose stop plugwerk-server
# 2. Sync via mc against any S3-compatible endpoint.mc alias set target https://<endpoint> <access-key> <secret-key>mc mirror /var/plugwerk/artifacts target/plugwerk-artifacts
# 3. Flip the env vars and start the server (see the AWS CLI tab for# the .env block; only the provider examples differ).docker compose up -d plugwerk-server
# 4. Verify the server log + smoke-test as above.S3 → Filesystem
Section titled “S3 → Filesystem”Symmetric:
docker compose stop plugwerk-serveraws 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-serverVerifying the migration
Section titled “Verifying the migration”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:
# Row countpsql "$PLUGWERK_DB_URL" -c "SELECT COUNT(*) FROM plugin_release;"
# File count in target backendfind /var/plugwerk/artifacts -type f | wc -l # fsaws s3 ls s3://plugwerk-artifacts/ --recursive | wc -l # s3The 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.
What's not supported (today)
Section titled “What's not supported (today)”- 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_PREFIXto share a single bucket across environments.
These are deliberate scope boundaries, not bugs.
Related pages
Section titled “Related pages”- Configuration — the full environment-variable reference
- Deployment — Docker Compose, standalone container, JAR run options
- Monitoring — health endpoint, Prometheus scrape configuration