Skip to content

Publish your first Maven/npm/PyPI package

What you'll learn:

  • How to configure your build tool's credentials for Repod
  • How to publish a package with the native protocol your tool already speaks (mvn deploy, npm publish, or twine upload — no Repod-specific plugin)
  • How to resolve it back with the same tool, proving it's a real, working private registry

Time: ~15 minutes Prerequisites: Repod running locally (see Getting Started), one of: a JDK + Maven, Node.js + npm, or Python + pip/twine. Pick the tab that matches your ecosystem below — the three paths are independent, you only need to follow one.


Step 1 — Get credentials

All three formats authenticate with your normal Repod account (or, for CI/CD, an API token — Settings → API Tokens, role uploader or higher). There's no separate registration step: the target repository/index/namespace is created automatically the first time you publish into it.


Step 2 — Configure your build tool

Add a server credential to ~/.m2/settings.xml. The <id> must match what you'll reference from pom.xml in the next step.

~/.m2/settings.xml
<settings>
  <servers>
    <server>
      <id>repod-releases</id>
      <username>admin</username>
      <password>YourPassword1!</password>
    </server>
  </servers>
</settings>

Plain HTTP repositories

Maven blocks external http:// repositories by default since 3.8.1. If your Repod instance isn't behind TLS yet, add a mirror override to unblock this one repository id — see the "Plain HTTP repositories" note in Client Setup.

Generate an API token (Settings → API Tokens, role uploader or higher) — npm login's interactive flow isn't supported, so you paste a pre-generated token directly into .npmrc instead, same as GitHub Packages or Artifactory.

~/.npmrc or ./.npmrc (project-local)
registry=http://YOUR_HOST:8000/api/v1/npm/private/
//YOUR_HOST:8000/api/v1/npm/private/:_authToken=YOUR_API_TOKEN
~/.pypirc
[distutils]
index-servers = repod-private

[repod-private]
repository = http://YOUR_HOST:8000/api/v1/pypi/private/legacy/
username = admin
password = YourPassword1!

Step 3 — Create a minimal package

mkdir -p ~/hello-repod-maven/src/main/java/com/example
cd ~/hello-repod-maven

cat > src/main/java/com/example/Hello.java << 'EOF'
package com.example;

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello from Repod's Maven repository!");
    }
}
EOF

cat > pom.xml << 'EOF'
<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>hello-repod-maven</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <distributionManagement>
    <repository>
      <id>repod-releases</id>
      <url>http://YOUR_HOST:8000/api/v1/maven/releases</url>
    </repository>
  </distributionManagement>
</project>
EOF

releases is the target Maven repository name — it doesn't need to exist beforehand.

SNAPSHOT versions aren't supported in V1

Repod's Maven support rejects SNAPSHOT versions with a clear error. Use a release version, as above.

mkdir ~/hello-repod-npm && cd ~/hello-repod-npm
npm init -y

Edit the generated package.json so it looks like this (adjust name if it collides with something already published to this namespace):

package.json
{
  "name": "hello-repod-npm",
  "version": "1.0.0",
  "description": "A demo package for Repod",
  "main": "index.js"
}
echo 'console.log("Hello from Repod'"'"'s npm registry!");' > index.js
mkdir -p ~/hello-repod-pypi/hello_repod_pypi && cd ~/hello-repod-pypi

cat > hello_repod_pypi/__init__.py << 'EOF'
def hello():
    print("Hello from Repod's PyPI repository!")
EOF

cat > pyproject.toml << 'EOF'
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "hello-repod-pypi"
version = "1.0.0"
description = "A demo package for Repod"
EOF

pip install --upgrade build twine
python -m build

python -m build produces dist/hello_repod_pypi-1.0.0-py3-none-any.whl and a matching .tar.gz sdist.


Step 4 — Publish

mvn deploy

You should see a normal Maven build finishing with:

[INFO] BUILD SUCCESS

Checksums (.sha1/.md5) and maven-metadata.xml are regenerated server-side from what was actually stored — anything your client uploaded for those is accepted but discarded, never trusted as-is.

npm publish

Expected output:

npm notice
npm notice 📦  [email protected]
npm notice === Tarball Contents ===
...
+ [email protected]

twine upload --repository repod-private dist/*

Expected output:

Uploading distributions to http://YOUR_HOST:8000/api/v1/pypi/private/legacy/
Uploading hello_repod_pypi-1.0.0-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...
Uploading hello_repod_pypi-1.0.0.tar.gz
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...

Every format runs the same antivirus (ClamAV) + CVE (Grype) pipeline used for .deb/.rpm/.apk packages before it's servable. A clean, dependency-free demo package like this one passes in a couple of seconds.

What if a CVE is found?

A review-policy match lands the artifact in storage but keeps it invisible to mvn/pip/npm until a maintainer or admin records a decision — the exact same review queue used for OS packages. See Walk through a CVE remediation.


Step 5 — Resolve it back

Prove it's a real, working registry by fetching your own package back with the same tool that published it.

cd /tmp && rm -rf resolve-test && mkdir resolve-test && cd resolve-test
mvn dependency:get -Dartifact=com.example:hello-repod-maven:1.0.0 \
  -DremoteRepositories=repod-releases::default::http://YOUR_HOST:8000/api/v1/maven/releases

Expected output ends with:

[INFO] BUILD SUCCESS

cd /tmp && rm -rf resolve-test && mkdir resolve-test && cd resolve-test
npm view hello-repod-npm --registry http://YOUR_HOST:8000/api/v1/npm/private/

Expected output includes:

[email protected] | ISC
dist
.tarball: http://YOUR_HOST:8000/api/v1/npm/private/hello-repod-npm/-/hello-repod-npm-1.0.0.tgz

cd /tmp && rm -rf resolve-test && python3 -m venv resolve-test && cd resolve-test
source bin/activate
pip install --index-url http://admin:YourPassword1!@YOUR_HOST:8000/api/v1/pypi/private/simple/ hello-repod-pypi
python -c "from hello_repod_pypi import hello; hello()"

Expected output:

Hello from Repod's PyPI repository!


Troubleshooting

Maven: Return code is: 401

The <id> in pom.xml's <distributionManagement> doesn't match the <id> of the <server> entry in settings.xml, or the password/token is wrong.

Maven: deploy fails immediately with a SNAPSHOT-related error

Repod's Maven support only accepts release versions in V1. Change <version> to something that doesn't end in -SNAPSHOT.

npm: npm ERR! code E401

Check .npmrc — the token line must be scoped to the exact registry URL (including the trailing /), on a line starting with //YOUR_HOST:8000/api/v1/npm/private/:_authToken=.

twine: 403 Forbidden

Either the credentials in .pypirc are wrong, or the private index has been RBAC-restricted and your account's role isn't granted. Check POST /api/v1/pypi/repositories/private/access.

It uploaded, but I can't resolve it back — 404

If the package tripped a review-policy CVE, it's stored but hidden from every listing/download endpoint until a decision is recorded. Check Security → Review Queue in the web UI.


What you just did

  • Configured settings.xml / .npmrc / .pypirc with Repod credentials
  • Published a package using your build tool's own native protocol — no Repod-specific CLI or plugin
  • Watched the same antivirus + CVE pipeline used for OS packages run against it
  • Resolved the package back with the same tool, from a clean directory

Next steps