Publishing from GitHub Actions¶
Trusted publishing: the job presents its own OIDC token, and the policy
binds the repository (and ref) claims to decide what a job can
publish.
Workflow:
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with: { distribution: temurin, java-version: "17" }
- name: Request the OIDC token
id: token
uses: actions/github-script@v9
with:
script: |
// audience must match config audiences
core.setOutput('oidc', await core.getIDToken('https://maven.example.com'))
- run: mvn -B deploy # with the token attached, see below
The Maven deploy plugin does not natively send OIDC tokens. Two practical options:
- Wagon custom headers (simplest). Put the token into a Maven settings file and let the wagon HTTP transport send it:
<!-- settings.xml -->
<settings>
<servers>
<server>
<id>pier</id>
<configuration>
<httpHeaders>
<property>
<name>Authorization</name>
<value>Bearer ${env.PIER_TOKEN}</value>
</property>
</httpHeaders>
</configuration>
</server>
</servers>
<profiles>
<profile>
<id>pier</id>
<repositories>
<repository>
<id>pier</id>
<url>https://maven.example.com</url>
</repository>
</repositories>
</profile>
</profiles>
</settings>
export PIER_TOKEN="${{ steps.token.outputs.oidc }}"
mvn -B -s settings.xml deploy:deploy-file \
-Dfile=target/myapp.jar -DpomFile=pom.xml \
-DgroupId=com.example -DartifactId=myapp -Dversion=1.2.3 \
-Dpackaging=jar -DrepositoryId=pier \
-Durl=https://maven.example.com
A full mvn deploy lifecycle works the same way for the whole
project (it also POSTs maven-metadata.xml).
- curl — for scripts that already produce the files,
PUTthe jar, pom and checksums andPOSTthe metadata; the deploy plugin just does that.
Enabling the wagon transport globally¶
Maven 3.9+ uses the native HTTP resolver transport by default, which
does not send the wagon httpHeaders above — the token never
reaches pier and requests fail with 401. Instead of
-Dmaven.resolver.transport=wagon on every command, set the property
once:
- In the settings file (recommended — it travels with the
httpHeadersit enables). Put it in a profile that you activate explicitly: in the same file's<activeProfiles>, or with-Pon the command line. Maven does not consider conditionally-activated profiles (<activation>,<activeByDefault>) — Maven builds the resolver session, and with it the transport, before it evaluates profile activation:
<profile>
<id>pier</id>
<properties>
<maven.resolver.transport>wagon</maven.resolver.transport>
</properties>
<!-- ...repositories... -->
</profile>
<activeProfiles>
<activeProfile>pier</activeProfile>
</activeProfiles>
- Machine-wide: the
mvnlauncher prependsMAVEN_ARGSto every invocation, so export it in your shell profile or the CI environment:
- Per project: a
.mvn/maven.configfile in the repository (one option per line) applies to every Maven invocation run in that project:
Example policy rule binding the GitHub repo to publication, tag-only:
- name: publish-from-tags
action: [upload]
when: |
claims.repository == "example/myproject" &&
claims.sub.startsWith("repo:example/myproject:ref:refs/tags/v")
GitHub token claims usable in rules: iss, sub
(repo:owner/name:ref:<ref>), repository, repository_owner, aud,
plus any custom claims your workflow requested.