Sign inSign up

plugwerk/plugwerk-server

By plugwerk

•Updated 3 months ago

Plugin management and marketplace software for the Java/PF4J ecosystem

Image
0

1.1K

plugwerk/plugwerk-server repository overview

⁠Plugwerk Server

Plugwerk is a self-hosted plugin marketplace for the PF4J⁠ ecosystem. It lets teams publish, version, and distribute Java/Kotlin plugins to their own applications without relying on a public registry.

⁠Supported tags

TagDescription
latestLatest stable release (semver, no pre-release)
1.0.0-alpha.2, 1.0, 1Specific versions and minor/major aliases
snapshotRolling development build from the main branch (on GHCR only⁠)

Multi-architecture: linux/amd64, linux/arm64.

⁠Quick start

Warning — placeholders in this snippet are intentionally invalid. PLUGWERK_AUTH_JWT_SECRET and PLUGWERK_AUTH_ENCRYPTION_KEY below fail the server's minimum-length validation on purpose, so a verbatim docker compose up aborts at startup with a clear property-validation error. Replace the database password and export real secrets as shown in the next step before running the stack.

Create a docker-compose.yml:

services:
  postgres:
    image: postgres:18-alpine
    environment:
      POSTGRES_DB: plugwerk
      POSTGRES_USER: plugwerk
      POSTGRES_PASSWORD: REPLACE_ME_STRONG_PASSWORD
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U plugwerk"]
      interval: 5s
      retries: 5

  plugwerk-server:
    image: plugwerk/plugwerk-server:latest
    ports:
      - "8080:8080"
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      PLUGWERK_DB_URL: jdbc:postgresql://postgres:5432/plugwerk
      PLUGWERK_DB_USERNAME: plugwerk
      PLUGWERK_DB_PASSWORD: REPLACE_ME_STRONG_PASSWORD
      # Short placeholder — fails @Size(min=32) and aborts startup until replaced.
      PLUGWERK_AUTH_JWT_SECRET: "REPLACE_ME_SEE_README"
      # Short placeholder — fails @Size(min=16) and aborts startup until replaced.
      PLUGWERK_AUTH_ENCRYPTION_KEY: "REPLACE_ME"
    volumes:
      - plugwerk-artifacts:/var/plugwerk/artifacts

volumes:
  postgres-data:
  plugwerk-artifacts:

Generate secure secrets and start:

# 1) Edit docker-compose.yml and replace each REPLACE_ME_* literal with the
#    value you want to pin (the database password is a plain string; the two
#    server secrets are easier to export and substitute, see below).
# 2) Export real secrets into your shell:
export PLUGWERK_AUTH_JWT_SECRET="$(openssl rand -base64 32)"
export PLUGWERK_AUTH_ENCRYPTION_KEY="$(openssl rand -base64 32)"
# 3) Substitute them in-place (or paste the values into the compose file).
# 4) Launch:
docker compose up -d

On first start the server generates a random superadmin password and surfaces it on two channels that bypass SLF4J entirely (log aggregators like Datadog/ELK/CloudWatch do not capture the credential). Retrieve it from either:

# Container stderr (forwarded by `docker compose logs`):
docker compose logs --no-log-prefix plugwerk-server | grep -A 6 "Initial Superadmin Password"

# Or the 0600 file inside the container:
docker compose exec plugwerk-server cat /tmp/plugwerk-admin-password.txt

Open http://localhost:8080⁠ and log in with admin / that value. You will be required to change the password on first login.

⁠Configuration

VariableRequiredDefaultDescription
PLUGWERK_AUTH_JWT_SECRETyes—HMAC signing key for JWTs, min 32 chars
PLUGWERK_AUTH_ENCRYPTION_KEYyes—Password for the AES-256-CBC text encryptor that protects OIDC client secrets at rest. PBKDF2-derived key, so length controls input entropy (not AES key size). Min 16 chars; 32+ recommended.
PLUGWERK_DB_URLnojdbc:postgresql://localhost:5432/plugwerkJDBC URL
PLUGWERK_DB_USERNAMEnoplugwerkDB user
PLUGWERK_DB_PASSWORDnoplugwerkDB password
PLUGWERK_STORAGE_ROOTno/var/plugwerk/artifactsArtifact storage directory
PLUGWERK_AUTH_ADMIN_PASSWORDno(random, written to stderr + /tmp/plugwerk-admin-password.txt (0600), passwordChangeRequired)Pin the initial superadmin password (CI/smoke-test only — do not set in production). Blank or whitespace-only values are treated the same as unset.
PLUGWERK_SERVER_CORS_ALLOWED_ORIGINSno(empty = same-origin-only)Comma-separated origins allowed to make cross-origin requests (e.g. https://frontend.example.com). Default empty preserves the bundled-frontend same-origin deployment. Wildcards not supported with default credentials.
PLUGWERK_SERVER_CORS_ALLOWED_METHODSnoGET,POST,PUT,PATCH,DELETE,OPTIONSComma-separated HTTP methods for cross-origin requests
PLUGWERK_SERVER_CORS_ALLOWED_HEADERSnoAuthorization,Content-Type,X-Api-KeyComma-separated request headers for cross-origin requests
PLUGWERK_SERVER_CORS_ALLOW_CREDENTIALSnotrueWhether browsers may include credentials on cross-origin requests. Must be false when PLUGWERK_SERVER_CORS_ALLOWED_ORIGINS=*.
PLUGWERK_SERVER_CORS_MAX_AGEno3600Preflight cache duration in seconds (0..86400)
JAVA_OPTSno-Xms256m -Xmx512m -XX:+UseG1GCJVM options

Full reference: https://www.plugwerk.io/server/configuration/⁠

⁠Persistent storage

The container writes uploaded plugin artifacts to /var/plugwerk/artifacts (owned by the non-root plugwerk user). Always mount a persistent volume here — the example above uses a named Docker volume (plugwerk-artifacts).

⁠Health check

curl http://localhost:8080/actuator/health
# → {"status":"UP"}

/actuator/health is public by design — safe for Docker / Kubernetes healthchecks with no credentials.

⁠Monitoring

Three actuator endpoints are exposed:

EndpointAuthIntended consumer
/actuator/healthpublichealthchecks, uptime probes
/actuator/infosuperadmin JWT or HTTP Basic scrape accountad-hoc inspection, metric collectors
/actuator/prometheussuperadmin JWT or HTTP Basic scrape accountPrometheus scraper
⁠Unattended Prometheus scraping

Configure a dedicated scrape account by setting both env vars on the container:

docker run -d \
  -e PLUGWERK_AUTH_ACTUATOR_SCRAPE_USERNAME=prometheus \
  -e PLUGWERK_AUTH_ACTUATOR_SCRAPE_PASSWORD="$(openssl rand -base64 32)" \
  # ... other PLUGWERK_* env vars ...
  plugwerk/plugwerk-server:latest

The password must be at least 16 characters (32+ recommended). If only one of the two is set, the container refuses to start — that asymmetry would silently be misconfigured at scrape time.

Example scrape_configs for Prometheus:

scrape_configs:
  - job_name: plugwerk
    metrics_path: /actuator/prometheus
    scheme: https
    basic_auth:
      username: prometheus
      password_file: /etc/prometheus/plugwerk-scrape-password
    static_configs:
      - targets: ['plugwerk.example.com']
⁠No scrape account configured?

When the two env vars are unset (the default), /actuator/info and /actuator/prometheus fall back to superadmin-JWT-only. That is deliberate — namespace members cannot read metrics even if they are logged in. /actuator/health stays public regardless.

See ADR-0025⁠ for the full decision record and threat model.

⁠Deployment guides

⁠License

AGPL-3.0 — see LICENSE⁠.

Tag summary

Content type

Image

Digest

sha256:afbfb6e0a…

Size

150.7 MB

Last updated

3 months ago

docker pull plugwerk/plugwerk-server