Skip to content
Select theme
  • Auto
  • Dark
  • Light

The lean, fast artifact repository that guards every publish.

Telesphorepo is a new self-hosted home for Maven artifacts, Gradle plugins, Debian packages and raw files. One binary with SQLite inside runs all of it. It keeps its speed from 100k to a million files, and nothing publishes without a short-lived publish session.

Run it

docker run -d -p 8080:8080 -v telesphorepo-data:/data ghcr.io/telesphorepo/telesphorepo:latest

Then open port 8080. The first start prints the admin password to the container log. The image runs on linux/amd64 and linux/arm64.

A repository should hold your builds and stay out of your way. These are the parts that make it do that.

Publishing a leaked password cannot touch

Nothing extra to run

Fast at any size

  • The same speed at a million filesEvery request uses an index, and lists page by key. In the scale test, no p95 moved by more than 21 µs from 100k to 1M assets.

Your server, your rules

An easy way off Nexus

In many setups, a CI job holds a deploy password that never changes. Here, no long-lived credential can write. The client proves who it is, and the server issues a publish session: a token for one repo that ends after 15 minutes.

The Gradle plugin and the Maven extension do this on every publish, so you do not see it. GitHub Actions and Jenkins use their own identity tokens, so a CI job has no secret to leak. The publish log keeps every session for 30 days after it ends.

Publish sessiontrs_9c1e4b7a02f3_...reporeleasesviadevice, Gradleopened14:02:11expires14:17:11One repo15 minClosed 14:03:41
An example session. The Gradle plugin closed it after a 90-second publish.

A publish has four steps. First, the client proves who it is in one of three ways: an enrolled device key signs a one-time nonce (POST /api/publish/sessions), a CI system sends its OIDC token and a trust policy must match it (POST /api/publish/sessions/oidc), or a person enters a fresh second-factor code in the web UI (POST /api/publish/sessions/manual). Second, the server answers with a token that starts with trs_. The token works for one repository and lives at most publish.session_ttl, 15 minutes by default. Third, the client uploads with the token as the HTTP Basic password. Fourth, the client closes the session with DELETE /api/publish/sessions/{id}, or the session expires by itself. In the example, a publish takes 90 seconds and the client closes the session, so the token is dead for the other 13 and a half minutes.

  1. 1. Prove who you are

    • Device key

      The Gradle plugin or the Maven extension signs a one-time nonce with its Ed25519 key.

      POST /api/publish/sessions

    • CI identity token

      GitHub Actions or Jenkins sends its OIDC token. A trust policy must match it.

      POST /api/publish/sessions/oidc

    • By hand

      A person signs in to the web UI and enters a fresh second-factor code.

      POST /api/publish/sessions/manual

  2. 2. Get a session for one repo

    trs_9c1e4b7a02f3_...
    repo
    releases
    ends
    in 15 min

    The limit is publish.session_ttl. One hour at most.

  3. 3. Upload

    PUT /maven/releases/...

    The token is the Basic password. Any user name works.

    The token also reads this repo. No other repo takes it.

  4. 4. Close

    DELETE /api/publish/sessions/{id}

    closed

    Or it expires by itself.

One session, from open to close

A Gradle publish of 90 seconds, then the client closes the session. The token is dead for the other 13.5 minutes.

A session works for one repo. After the close, its token opens nothing.

Read how publishing works, then set up the Gradle plugin, the Maven extension or GitHub Actions.

As fast at a million files as at a hundred thousand

Section titled “As fast at a million files as at a hundred thousand”

Every query on a request path uses an index, and lists page with keys, not offsets. So a request costs the same in a large repo as in a small one. The scale test checks this on every run of go tool task test scale. It seeds 100k synthetic assets, measures, seeds up to 1M, and measures again.

The scale test measures the p95 latency of four operations at 100k and at 1M assets. Maven GET: 586 µs at 100k, 607 µs at 1M. Asset list page: 1.49 ms at 100k, 1.51 ms at 1M. Search: 3.38 ms at 100k, 3.39 ms at 1M. Token auth: 1.03 ms at 100k, 1.03 ms at 1M. An operation fails the test when its p95 at 1M exceeds the larger of twice its p95 at 100k and its p95 at 100k plus 2 ms.

p95 of 400 runs per operation, over HTTP, against the server image. With ten times the assets, no p95 moves by more than 21 µs.

There is no database server and no search cluster. Search runs on an SQLite full-text index. The server, the web UI and the database run in one process. Settings live in the database and apply without a restart. To move the files from local disk to a bucket, change the storage on the Settings page. The server copies every file in the background while reads and uploads continue.

The whole install is three things. The binary, /telesphorepo, serves the web UI, the JSON API and the format routes on one port, 8080. One SQLite file, /data/telesphorepo.db, holds users, repos, settings and an index of every stored file. One blob store holds the file contents: /data/blobs/ on local disk, or any S3-compatible bucket such as Cloudflare R2, SeaweedFS or AWS S3.

The binary/telesphorepo
The server, the web UI, the JSON API and the format routes, on one port (8080).
The database/data/telesphorepo.db
One SQLite file: users, repos, settings, and an index of every stored file.
The blobs/data/blobs/ or s3://bucket
The file contents, on local disk or in any S3-compatible bucket: Cloudflare R2, SeaweedFS, AWS S3.
Back up the /data volume, and the bucket if you use S3. Unless you set TR_MASTER_KEY, /data also holds master.key, the key that encrypts secret settings.

Start the image with a volume for /data:

Terminal window
docker run -d --name telesphorepo -p 8080:8080 -v telesphorepo-data:/data ghcr.io/telesphorepo/telesphorepo:latest

On the first start, the server prints a generated admin password once to the container log. Read it, then open http://localhost:8080 and sign in.

Terminal window
docker logs telesphorepo

Binaries for Linux, macOS and Windows are on the GitHub Releases page. The install guide has the other ways to run it.

Maven, Gradle plugins, Debian and raw today

Section titled “Maven, Gradle plugins, Debian and raw today”

Each format is a plugin with its own routes and its own rules. More formats will come over time.

Nexus 3 needs a JVM and a database to run, and some of its upgrades are projects of their own. The move from OrientDB to H2 is one example: its issue was opened in 2024 and is still open.

The move takes three steps, and your builds keep working the whole time.

Moving off Nexus takes three steps. First, add your Nexus server in Telesphorepo and pick the repos. Second, Telesphorepo copies them, then keeps copying new uploads every 15 minutes, while your builds still publish to Nexus. Third, when you are ready, switch your builds to Telesphorepo and press Cut over.

  1. 1Add your Nexus server in Telesphorepo and pick the repos.

    Nexus 3

    • maven-releases
    • maven-snapshots
    • apt-hosted
    • raw-archive
  2. 2Telesphorepo copies them, then keeps copying new uploads every 15 minutes, while your builds still publish to Nexus.

    Your builds

    Nexus

    Nexus

    Telesphorepo

  3. 3When you are ready, switch your builds to Telesphorepo and press Cut over.

    Your builds

    Telesphorepo

    Cut over

Nexus keeps serving your builds until you cut over.

The Nexus migration guide has each step in detail.