Pier¶
A private Maven repository on top of S3-compatible object storage, fitting in one stateless Go binary.
- Every request carries an OIDC token. Pier authorizes the request only after it verifies the token against the configured issuers — no deploy tokens, no shared secrets. GitHub Actions OIDC (trusted publishing) is one example of a source; so is any IdP you configure (Authentik, Keycloak, ...).
- Authorization uses a list of CEL rules. Pier evaluates the rules
per request against the verified token claims, the Maven coordinates
of the path, and the action (
download/upload/delete). The token source does not decide; the rules do.
flowchart LR
GHA["GitHub Actions"] -- "OIDC token" --> P[Pier]
IdP["IdP (SSO, ...)"] -- "OIDC token" --> P
P --> S3[("S3-compatible object storage")]
P -- "per request" --> CEL["CEL policy"]
Maven protocol¶
The service speaks the plain-HTTP Maven repository layout:
| Operation | HTTP | Path |
|---|---|---|
| resolve artifact | GET/HEAD |
com/example/foo/1.0.0/foo-1.0.0.jar |
| resolve metadata | GET/HEAD |
com/example/foo/maven-metadata.xml |
| deploy artifact/pom | PUT |
same as above |
| deploy metadata | POST (or PUT) |
com/example/foo/maven-metadata.xml |
| deploy checksum | PUT/POST |
...jar.sha1 (also .md5, .sha256, .sha512) |
| delete | DELETE |
any of the above |
Behavior notes:
- Downloads support HTTP byte ranges (
Range: bytes=...). Satisfiable ranges return206 Partial ContentwithContent-Range, so a client can fetch large artifacts partially or resume an interrupted download; unsatisfiable ranges return416. - Pier stores
maven-metadata.xmlexactly as the deploy client sends it (the Maven deploy plugin generates the full XML, including snapshot versions). If it is missing: for a private GAV, Pier synthesizes the artifact-level document from the stored versions (Pull-through cache); Pier never synthesizes snapshot-level metadata (404if absent). For a pull-through GAV, Pier fetches the upstream's document through the cache. - Pier verifies checksum uploads against the stored base object: it
rejects a mismatch with
409, so a broken deploy cannot poison the repo. The base object must exist first. - Non-SNAPSHOT objects are immutable by default (
immutable_releases: true): re-uploading a published GAV (groupId:artifactId:version) returns409. You can re-upload snapshots andmaven-metadata.xml. 401= missing/invalid token (the body states the reason, for exampleaudience mismatch,token expired,JWKS unavailable);403= valid token, no rule allows the action.