Publish from GitHub Actions
A GitHub Actions workflow publishes to CloudRepo the way your laptop does: the build tool sends a repository token with the upload. What changes is where the token lives. Keep it in a GitHub secret, hand it to the one step that needs it as an environment variable, and let a committed config file name that variable instead of holding the token.
Before you start
Section titled “Before you start”- A repository, and a repository token that reaches it. A token that publishes is Read + write; a build that only pulls needs Read only.
- A token you create under Repository Tokens belongs to your account. Deleting a user from your organization deletes that user’s tokens, and any build using one stops authenticating. So create the token as an account that outlives any one person: a user you make for builds (see Create Your First User), signed in to the admin portal when it mints the token. The owner can instead create a service key, which belongs to your organization rather than to a user.
- Your repository URL,
https://your-org.mycloudrepo.io/repositories/your-repo, and the username that Maven, Gradle, Python and Docker clients send: the email address of the account that created the token. npm sends the token alone.
Store the token as a secret
Section titled “Store the token as a secret”- In your GitHub repository, click Settings, then under Security click Secrets and variables, then Actions.
- On the Secrets tab, click New repository secret. Name it
CLOUDREPO_TOKENand paste the token as the value.
For one token shared by several repositories, GitHub also keeps secrets for an organization; see Using secrets in GitHub Actions.
The workflows below hand the secret to one step through that step’s env:, where the tool reads it
as CLOUDREPO_TOKEN. GitHub’s own guidance is to avoid passing secrets between processes on the
command line, where other users can see them with ps, and the blocks on this page follow it: the
token reaches each tool through the environment or a file the tool reads, never as an argument.
GitHub passes no secret except GITHUB_TOKEN to a workflow triggered from a fork. In that run
CLOUDREPO_TOKEN is empty, so a publish step cannot authenticate.
Publish from a workflow
Section titled “Publish from a workflow”Pick your client. The choice is remembered on every page of these docs. Each tab has the same three parts: a file that names the token’s variable, the repository settings of your build, and the workflow. Commit all three; none of them holds the token.
Maven reads the credential from a settings file. Commit this one as .mvn/settings.xml;
${env.CLOUDREPO_TOKEN} is replaced by the environment variable when Maven runs. Replace
you@example.com with the email address of the account that created the token.
<settings> <servers> <server> <id>cloudrepo</id> <username>you@example.com</username> <password>${env.CLOUDREPO_TOKEN}</password> </server> </servers></settings>The <id> must match the repository’s <id> in your pom.xml. <distributionManagement> is where
mvn deploy publishes:
<distributionManagement> <repository> <id>cloudrepo</id> <url>https://your-org.mycloudrepo.io/repositories/your-repo</url> </repository></distributionManagement>The workflow runs on a tag push. It passes the settings file with --settings and gives the secret
to the deploy step:
name: Publishon: push: tags: ["release-*"]jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-java@v4 with: distribution: temurin java-version: "17" - name: Publish to CloudRepo env: CLOUDREPO_TOKEN: ${{ secrets.CLOUDREPO_TOKEN }} run: mvn --batch-mode --settings .mvn/settings.xml deployExpected: the job ends with BUILD SUCCESS, and the artifact is listed in the repository in the
admin portal. To resolve dependencies from the same repository in a
build, add a <repositories> block with the same URL; see
Maven Repositories.
Gradle reads the token from the environment in the build script, so nothing needs to sit in
~/.gradle on the runner. Replace you@example.com with the email address of the account that
created the token.
publishing { publications { create<MavenPublication>("library") { from(components["java"]) } } repositories { maven { url = uri("https://your-org.mycloudrepo.io/repositories/your-repo") credentials { username = "you@example.com" password = providers.environmentVariable("CLOUDREPO_TOKEN").orNull } } }}.orNull keeps every other Gradle command working on a machine where the variable is not set;
only publish needs it. Add the java-library and maven-publish plugins to the plugins block
if your build does not have them.
name: Publishon: push: tags: ["release-*"]jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-java@v4 with: distribution: temurin java-version: "17" - name: Publish to CloudRepo env: CLOUDREPO_TOKEN: ${{ secrets.CLOUDREPO_TOKEN }} run: gradle publishIf your project has the Gradle wrapper, run ./gradlew publish instead. A Groovy build script takes
the same maven { ... } block under publishing { repositories { ... } }, with the credential read
from System.getenv("CLOUDREPO_TOKEN").
npm replaces ${CLOUDREPO_TOKEN} in .npmrc with the environment variable of that name, so this
file can be committed. Keep the trailing / on both lines, and keep the second line identical to
the registry URL without https:.
registry=https://your-org.mycloudrepo.io/repositories/your-repo///your-org.mycloudrepo.io/repositories/your-repo/:_authToken=${CLOUDREPO_TOKEN}npm sends the token on its own, so there is no username to set.
name: Publishon: push: tags: ["release-*"]jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Publish to CloudRepo env: CLOUDREPO_TOKEN: ${{ secrets.CLOUDREPO_TOKEN }} run: npm publishThe same .npmrc and secret serve npm ci and npm install in a job that only pulls. For a scoped
package, or a repository that sits beside the public registry, see
npm Repositories.
twine uploads a built package. It reads its username and password from TWINE_USERNAME and
TWINE_PASSWORD, which the step sets from the secret, and takes the repository URL without
/simple/. Replace you@example.com with the email address of the account that created the token.
name: Publishon: push: tags: ["release-*"]jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v5 with: python-version: "3.x" - name: Build run: pip wheel . --no-deps -w dist - name: Install twine run: pip install twine - name: Publish to CloudRepo env: TWINE_USERNAME: you@example.com TWINE_PASSWORD: ${{ secrets.CLOUDREPO_TOKEN }} run: twine upload --repository-url https://your-org.mycloudrepo.io/repositories/your-repo dist/*To install from the repository in a job, set PIP_INDEX_URL from the secret rather than passing
--index-url, which would put the token on the command line. See
Repository Tokens for the URL, and write the @ in
your email address as %40.
Log in to the host alone, with your email address and the token on standard input, then build and push. The image reference carries the repository after the host.
name: Publishon: push: tags: ["release-*"]jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Log in to CloudRepo env: CLOUDREPO_TOKEN: ${{ secrets.CLOUDREPO_TOKEN }} run: echo "$CLOUDREPO_TOKEN" | docker login your-org.mycloudrepo.io --username you@example.com --password-stdin - name: Build and push run: | docker build -t your-org.mycloudrepo.io/repositories/your-repo/my-app:1.0.0 . docker push your-org.mycloudrepo.io/repositories/your-repo/my-app:1.0.0Use your own image name and tag. The password is the repository token, not a portal password. More: Docker Repositories.
When publishing fails
Section titled “When publishing fails”401 Unauthorized. The credential was refused. The username must be the email address of the account that created the token, and the token must not be expired or revoked. Check that the secret is namedCLOUDREPO_TOKEN, is set on this repository (or shared with it), and that the step names it inenv:. The full list is on Repository Tokens.403 Forbidden. The token does not reach this repository, or it is Read only and the step publishes. Create a Read + write token that includes the repository.409 Conflictfrommvn deploy. The release version you are publishing already exists, and Overwrite Protection refuses to replace it. Publish a new version, or use a-SNAPSHOTversion while you iterate. See Maven Overwrite Protection. Python repositories have the same setting: Python Repositories.