Skip to content
Select theme
  • Auto
  • Dark
  • Light

Move your projects by hand

After the copy from Nexus, each project still names Nexus. You move one project in two stages:

  1. Downloads. The build downloads from Telesphorepo. Publishing stays on Nexus.
  2. Publishing. The project publishes to Telesphorepo, first to a test repository.

You can do the first stage while the copy syncs. To let a coding agent do it with you, see Move your projects with an AI agent.

Write down the name of each repository in Nexus and in Telesphorepo. The Repositories page lists them. A lock after a name means the repository is private.

The examples use https://repo.example.com for Telesphorepo and https://nexus.example.com for Nexus.

Search every file of the project, hidden folders too, for two things:

  • the Nexus host name
  • /repository/, which every Nexus address holds

Look in the files below. Also look in CI files, Dockerfiles, compose files, scripts and READMEs.

Gradle or Maven: where to look

  • pom.xml, .mvn/maven.config and every settings.xml
  • build.gradle(.kts), settings.gradle(.kts), gradle.properties, gradle/*.gradle(.kts)
  • buildSrc/, build-logic/ and init scripts
  • gradle/wrapper/gradle-wrapper.properties
  • On your computer: ~/.m2/settings.xml, ~/.gradle/gradle.properties and ~/.gradle/init.d/

npm, pnpm, Yarn or Bun: where to look

  • .npmrc, in the project and in ~/.npmrc
  • .yarnrc.yml, .yarnrc and bunfig.toml
  • package.json and the lockfiles
  • registry-url of actions/setup-node in CI

Python: where to look

  • pip.conf or pip.ini, in the project, ~/.config/pip/ and /etc/
  • requirements*.txt and constraints files
  • pyproject.toml, uv.toml, poetry.toml, setup.cfg, tox.ini, noxfile.py
  • PIP_INDEX_URL, PIP_EXTRA_INDEX_URL, UV_INDEX_URL and UV_DEFAULT_INDEX in Dockerfiles and CI
  • .pypirc, for publishing

Rust and Cargo: where to look

  • .cargo/config.toml, in the project and in ~/.cargo/
  • Cargo.toml and Cargo.lock

.NET and NuGet: where to look

  • nuget.config, in the solution folder, its parents and your user folder
  • Directory.Build.props and .targets
  • *.csproj and *.fsproj
  • dotnet nuget add source, --source and source-url of actions/setup-dotnet in CI

Docker and Helm: where to look

Also search for each Nexus Docker host and port, such as nexus.example.com:8082.

  • Dockerfile* and compose files
  • Kubernetes manifests and Kustomize
  • Chart.yaml, Chart.lock and values*.yaml
  • helmfile, skaffold, Tiltfile and devcontainer files
  • docker login, docker pull, helm registry login and helm repo add in CI

Debian or raw files: where to look

  • apt sources, and the Dockerfiles, Ansible, cloud-init files and scripts that write them
  • keyrings
  • curl and wget of Nexus files

Change each download address to Telesphorepo. Leave the publish addresses and publish steps on Nexus for now.

The files are the same bytes as in Nexus. Keep every checksum and hash.

Gradle or Maven: the addresses

https://nexus.example.com/repository/<nexus name>/ becomes:

https://repo.example.com/maven/<name>
  • Only the prefix changes. The files keep their paths.
  • Point a <mirror> with <mirrorOf>*</mirrorOf> at a Telesphorepo group.
  • A distributionUrl on a Nexus raw repository becomes https://repo.example.com/raw/<name>/<path>. Keep distributionSha256Sum.
  • Lockfiles and gradle/verification-metadata.xml hold no address. Keep them.

npm, pnpm, Yarn or Bun: the addresses

https://nexus.example.com/repository/<nexus name>/ becomes:

https://repo.example.com/npm/<name>/
  • Keep the slash at the end.
  • Change scoped registries (@acme:registry=) the same way.
  • Remove the Nexus _auth, always-auth and email lines.
  • Yarn 2 and later: change npmRegistryServer and npmScopes.<scope>.npmRegistryServer.
  • Bun reads .npmrc.

Then fix the lockfiles. In package-lock.json, pnpm-lock.yaml, bun.lock and a Yarn 1 yarn.lock, replace the Nexus prefix with the new one. Keep every integrity.

Run the frozen install of your tool:

Terminal window
npm ci # or:
pnpm install --frozen-lockfile
yarn install --immutable
bun install --frozen-lockfile

Python: the addresses

https://nexus.example.com/repository/<nexus name>/simple/ becomes:

https://repo.example.com/pypi/<name>/simple/

Then renew the lockfiles with the lock command. Do not edit them by hand:

Terminal window
uv lock # or:
poetry lock # Poetry 1: poetry lock --no-update

Read git diff. The versions and hashes stay the same; only the addresses change.

In requirements files with --hash, change only the index lines.

Rust and Cargo: the addresses

The index becomes:

sparse+https://repo.example.com/cargo/<name>/index/
  • Keep the registry names. Change only index in [registries.<name>].

  • For a proxy or a group of crates.io, also add this:

    .cargo/config.toml
    [source.crates-io]
    replace-with = "<name>"
  • In Cargo.lock, change each source = to the new index, exactly as in the config. Keep every checksum.

Then run cargo fetch --locked.

.NET and NuGet: the addresses

https://nexus.example.com/repository/<nexus name>/index.json becomes:

https://repo.example.com/nuget/<name>/index.json
  • Keep the source keys, so packageSourceMapping and the credentials still match.
  • packages.lock.json holds no address. Keep it.

Docker and Helm: the addresses

The repository name is the first part of the image name:

nexus.example.com:8082/team/app:1.0 → repo.example.com/<name>/team/app:1.0
  • Through a proxy of Docker Hub, nginx:1.27 is repo.example.com/<proxy name>/nginx:1.27.
  • Every @sha256: digest stays the same.

Helm charts live in Docker repositories:

  • A chart dependency on Nexus becomes oci://repo.example.com/<name>.
  • Remove its helm repo add line.
  • Ask your admin which Docker repository holds the charts.
  • Run helm dependency update to renew Chart.lock.

Debian or raw files: the addresses

An apt source changes like this:

deb https://nexus.example.com/repository/<nexus name>/ <dist> main
deb [signed-by=/etc/apt/keyrings/<name>.asc] https://repo.example.com/debian/<name> <dist> main

Every package is in the component main. Ask your admin which signing key the repository has:

  • The Nexus key: the machines trust it already.
  • Another key: install https://repo.example.com/debian/<name>/key.asc into /etc/apt/keyrings/<name>.asc.

A raw address https://nexus.example.com/repository/<nexus name>/<path> becomes https://repo.example.com/raw/<name>/<path>.

Skip this step when every repository is public.

  1. Open Settings › Tokens, and click New read token.

  2. Type a name, such as my-app builds, and click Create token.

  3. Copy the token. It shows only once.

  4. Put your username and the token in the variables below. Set them on your computer and as CI secrets.

When you choose the names, use TELESPHOREPO_READ_USER and TELESPHOREPO_READ_TOKEN. Never use TELESPHOREPO_TOKEN: the CLI uses that name when it publishes.

Gradle or Maven: the read token

Gradle: in the maven { } block, set a name and the credentials:

build.gradle.kts
name = "myLibs"
credentials(PasswordCredentials::class)

Gradle then reads ORG_GRADLE_PROJECT_myLibsUsername and ORG_GRADLE_PROJECT_myLibsPassword. The name is the repository name as a Gradle identifier: my-libs is myLibs. In Groovy, write credentials(PasswordCredentials).

Maven: add a <server> whose <id> is the <id> of the repository or mirror. Put it in ~/.m2/settings.xml, and in a settings file that CI passes with -s:

settings.xml
<server>
<id>maven-public</id>
<username>${env.TELESPHOREPO_READ_USER}</username>
<password>${env.TELESPHOREPO_READ_TOKEN}</password>
</server>

npm, pnpm, Yarn or Bun: the read token

Set NPM_TOKEN to the read token, and add this line:

.npmrc
//repo.example.com/npm/<name>/:_authToken=${NPM_TOKEN}

Yarn 2 and later: set npmAlwaysAuth: true and npmAuthToken: "${NPM_TOKEN}" instead.

Python: the read token

pip reads the credentials from the address. Put the whole address in the environment, and keep the plain address in the files:

Terminal window
export PIP_INDEX_URL="https://$TELESPHOREPO_READ_USER:$TELESPHOREPO_READ_TOKEN@repo.example.com/pypi/<name>/simple/"

uv reads UV_INDEX_<NAME>_USERNAME and UV_INDEX_<NAME>_PASSWORD. <NAME> is the index name in capitals, with _ for every other character.

Poetry reads POETRY_HTTP_BASIC_<SOURCE>_USERNAME and POETRY_HTTP_BASIC_<SOURCE>_PASSWORD.

Rust and Cargo: the read token

Add credential-provider = "cargo:token" to the registry. Cargo then reads the token from CARGO_REGISTRIES_<NAME>_TOKEN: the registry name in capitals, with _ for -. This needs Cargo 1.74 or later.

.NET and NuGet: the read token

Add credentials under the source key, here dotnet:

nuget.config
<packageSourceCredentials>
<dotnet>
<add key="Username" value="%NUGET_USERNAME%" />
<add key="ClearTextPassword" value="%NUGET_TOKEN%" />
</dotnet>
</packageSourceCredentials>

Docker and Helm: the read token

On your computer, use the credential helper of the CLI instead of a token:

  1. Install the CLI, and run telesphorepo login https://repo.example.com.

  2. Add the helper to ~/.docker/config.json, and keep the other keys:

    ~/.docker/config.json
    {
    "credHelpers": {
    "repo.example.com": "telesphorepo"
    }
    }

Docker, Podman, Helm and ORAS then pull with no docker login.

In CI and on servers, log in with the read token:

Terminal window
printf '%s' "$TELESPHOREPO_READ_TOKEN" | docker login repo.example.com -u "$TELESPHOREPO_READ_USER" --password-stdin

In Kubernetes, make a pull secret with kubectl create secret docker-registry.

Debian or raw files: the read token

apt reads credentials only from /etc/apt/auth.conf.d/. Run these lines. They ask for the token without showing it:

Terminal window
printf 'Read token: '; read -rs READ_TOKEN; echo
sudo install -m 600 /dev/null /etc/apt/auth.conf.d/<name>.conf
printf 'machine %s login <user> password %s\n' https://repo.example.com/debian/<name> "$READ_TOKEN" \
| sudo tee -a /etc/apt/auth.conf.d/<name>.conf > /dev/null

curl: pass the credentials on stdin, never as an argument:

Terminal window
printf 'user = "%s:%s"\n' "$TELESPHOREPO_READ_USER" "$TELESPHOREPO_READ_TOKEN" | curl -fsSL -K - -o <file> <address>
  1. Search again for the Nexus host and /repository/. Find out why each hit is still there.

  2. Build with an empty cache, as below.

  3. Check in the output that every download comes from repo.example.com.

  4. Commit the changes.

Gradle or Maven: the check

Terminal window
./gradlew build --refresh-dependencies
mvn -U -Dmaven.repo.local=/tmp/empty-m2 verify

npm, pnpm, Yarn or Bun: the check

Remove node_modules, then run the frozen install, such as npm ci.

Python: the check

In a new virtual environment:

Terminal window
pip install --no-cache-dir -r requirements.txt # or: uv sync, poetry install

Rust and Cargo: the check

Terminal window
cargo fetch --locked
cargo build

.NET and NuGet: the check

Terminal window
dotnet nuget locals all --clear
dotnet restore

Docker and Helm: the check

Terminal window
docker build --no-cache --pull .

Then pull each image of the compose files and manifests.

Debian or raw files: the check

Run sudo apt update, then install a package. Download each raw file with curl, and compare its SHA-256.

If you move only the downloads, you are done.

The copies refuse uploads until the switch-over. So the project first publishes to a test repository.

Ask your admin for two things:

  • A test repository: a hosted repository of the same format, such as maven-releases-rehearsal, where your account has Publish.
  • A CI pipeline for the project, with the test repository and the real one. See Ask for a CI pipeline.

Then change the project:

  1. Remove the Nexus publishing from the project and its CI, as below. Keep the steps that build the artifacts.

  2. Open the test repository, then the Publish to this repository tab.

  3. Click Set up a project, and follow Publish from a project. The CLI writes telesphorepo.toml and, if you want, a CI file.

  4. Delete the CI secrets that only Nexus used.

Gradle or Maven: what to remove

  • The Nexus <distributionManagement> entries and <server> credentials
  • Nexus staging plugins, such as nexus-staging-maven-plugin and io.github.gradle-nexus.publish-plugin
  • The Nexus repository in publishing { repositories { } }

Keep maven-publish, the publications, group and version. telesphorepo publish runs mvn deploy or gradle publish with the repository as the target.

npm, pnpm, Yarn or Bun: what to remove

  • publishConfig.registry
  • The Nexus publish auth lines
  • The npm publish steps of CI and their secrets

telesphorepo publish picks the tool by the lockfile. In a workspace, put a telesphorepo.toml in the folder of each package that publishes.

Python: what to remove

  • .pypirc and twine upload
  • uv publish and publish-url
  • poetry publish -r and the Poetry repository for Nexus

telesphorepo publish builds with Poetry, uv or python -m build, then uploads every wheel and sdist.

Rust and Cargo: what to remove

  • The cargo publish --registry <nexus> steps and the Nexus tokens

Where Cargo.toml names Nexus in publish =, set publish = ["telesphorepo"]. Keep the key: without it, a plain cargo publish goes to crates.io.

.NET and NuGet: what to remove

  • The dotnet nuget push or nuget push steps to Nexus
  • <apikeys> and the API key secrets

telesphorepo publish runs dotnet pack -c Release and pushes each package.

Docker and Helm: what to remove

  • docker login to Nexus and the Nexus push credentials
  • helm push and curl uploads to Nexus

Images push with docker push repo.example.com/<repo>/<image>:<tag> through the credential helper. They need no telesphorepo.toml. A chart folder gets one, and telesphorepo publish packages and pushes the chart.

Debian or raw files: what to remove

  • The curl -u uploads to Nexus and their passwords

telesphorepo publish <file> uploads the files.

  1. In the project folder, run telesphorepo status. Check the command it names.

  2. Publish to the test repository:

    Terminal window
    telesphorepo publish
  3. In a new empty folder outside the project, install what you published, as below. Then remove the folder.

  4. To rehearse CI too, push to a branch that the CI pipeline lists. Check the run.

Gradle or Maven: the install check

Write a small build that depends on the group:artifact:version you published, from https://repo.example.com/maven/<test repository>. Build it with an empty cache.

npm, pnpm, Yarn or Bun: the install check

Run npm init -y, and write an .npmrc with the test registry. Then run npm install <package>@<version>.

Python: the install check

In a new virtual environment:

Terminal window
pip install --index-url https://repo.example.com/pypi/<test repository>/simple/ \
--extra-index-url https://pypi.org/simple/ <package>==<version>

Rust and Cargo: the install check

Run cargo new verify, and write a .cargo/config.toml with the test registry. Then run cargo add <crate>@<version> --registry <name> and cargo build.

.NET and NuGet: the install check

Run dotnet new console, and write a nuget.config with the test source. Then run dotnet add package <id> --version <version>.

Docker and Helm: the install check

Remove the local image, then pull it:

Terminal window
docker pull repo.example.com/<test repository>/<image>:<tag>
helm pull oci://repo.example.com/<test repository>/<chart> --version <version>

Debian or raw files: the install check

For raw files, download the file with curl, and compare its SHA-256. For Debian, add the test source and its key in a throwaway debian container. Then run apt-get update and apt-get install <package>.

When your admin has switched over, the copies take uploads.

  1. Set repo in telesphorepo.toml to the real repository:

    telesphorepo.toml
    server = "https://repo.example.com"
    repo = "maven-releases"
  2. Check that the CI pipeline lists the real repository.

  3. Publish once.

  4. Delete the test repository.

The publish answers 403

Your account lacks Publish on the repository. Ask your admin for it.

The Tokens page has no New read token

You sign in with single sign-on, and such an account has no read tokens. Ask your admin for an account with a password for the builds.

CI does not find telesphorepo.toml

Run git check-ignore telesphorepo.toml. If git ignores the file, CI never sees it. Change .gitignore, then commit the file.

The server runs on plain http://

Each tool needs the host allowed:

  • Gradle: isAllowInsecureProtocol = true. Maven refuses http:// other than localhost.
  • pip: trusted-host = <host>. uv: allow-insecure-host.
  • Yarn: the host in unsafeHttpWhitelist.
  • NuGet: allowInsecureConnections="true" on the source.
  • Docker: the host in insecure-registries of /etc/docker/daemon.json.

Gradle or Maven: problems

Public dependencies stop resolving

A hosted repository holds only its own packages. Keep mavenCentral(), or use a group with a proxy of Maven Central.

npm, pnpm, Yarn or Bun: problems

Yarn 4 does not install a new version

On a hosted repository, add npmMinimalAgeGate: 0, or Yarn 4 waits a week. On a group or a proxy, list your own packages in npmPreapprovedPackages instead.

A group misses versions of a package

In an npm group, the first member that has a package name serves every version of it. Nexus merges them. If you need versions of one name from two members, ask your admin.

npm audit fails

Telesphorepo does not serve npm audit. Run it with --registry=https://registry.npmjs.org/.

Python: problems

Dependencies stop resolving

A hosted repository holds only your own packages. Use a group with a proxy of PyPI, or PyPI itself. In a group, the first member that has a project name serves every file of it.

Rust and Cargo: problems

A group misses versions of a crate

In a Cargo group, the first member that has a crate name serves every version of it.

.NET and NuGet: problems

A group misses versions of a package

In a NuGet group, the first member that has a package id serves every version of it.

The source key starts with a digit

In <packageSourceCredentials>, write such a key as _x003N_....

Docker and Helm: problems

CI cannot pull a private base image

With its identity token, a CI job reads only what anyone can read. Log in with the read token before the build.

docker pull asks for a login on your computer

telesphorepo login does not add the credential helper. Run telesphorepo status: it shows whether ~/.docker/config.json has the entry.