# Telesphorepo

> A new, lean, self-hosted artifact repository for Maven, Gradle plugins, Debian packages and raw files. One binary with SQLite inside, the same speed at a million files, and a short-lived publish session for every upload.

Source: https://docs.telesphorepo.com/

## What you get

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

- [A new session for every publishAn upload needs a publish session: a token for one repo that ends after 15 minutes. The Gradle plugin and the Maven extension get one and close it on every publish. A password or a read token only reads.](/publishing/overview/)
- [A second factor for every new deviceA laptop enrolls its own key once. You approve it with an authenticator code or a passkey. A stolen password alone cannot add a device.](/security/devices/)
- [CI with no stored secretGitHub Actions and Jenkins prove who they are with their own OIDC token. A trust policy names the repo, the branch and the workflow that may publish.](/security/trust-policies/)
- [Single sign-on for adminsAdmins sign in through your identity provider with OpenID Connect: Keycloak, Microsoft Entra, Google, Okta or any other. The provider decides who is an admin, and its MFA applies.](/security/sso/)
- IP allow-lists per repoLimit who may read and who may publish to a repo, each with its own list of address ranges. A GitHub Actions preset stays current by itself; for other runners, add their fixed addresses as ranges.

### Nothing extra to run

- [One image, one binaryThe server, the web UI and the database run in one process. SQLite is inside, so there is no database server or search cluster to keep alive.](/getting-started/install/)
- [Any storage, moved liveKeep files on local disk or in any S3-compatible bucket: Cloudflare R2, SeaweedFS, AWS S3. Change the storage in Settings and the server copies every file while reads and uploads continue.](/operations/moving-storage/)
- [Settings in the UIEvery setting is on the Settings page and applies without a restart. Secret settings are stored encrypted.](/reference/settings/)
- Real client IPs behind Caddy and CloudflareWhen requests arrive through a proxy, Settings offers to trust it in one click. For Cloudflare, pick the built-in Cloudflare preset. Its ranges refresh every day. Rate limits, IP rules and the audit log see the real client address, with no proxy tuning.
- Drop a folder to uploadDrop a whole build folder, a Maven-layout tree or a folder of .deb files. The server finds the artifacts, checks each one strictly, and shows a preview. Nothing is stored until you confirm.

### 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

- Delete what you wantDelete a file, a version or a whole folder from the UI. A deleted Debian package leaves the apt indexes by itself.
- IP addresses only in the audit logFile lists show who uploaded, by username. Client IP addresses appear only in the admin audit log.
- [Open sourceThe server is AGPL-3.0. The Gradle plugin and the Maven extension are MIT.](/reference/licensing/)

### An easy way off Nexus

- [Copy first, switch laterTelesphorepo copies your Nexus 3 repos and keeps them up to date while your builds still publish to Nexus. When you are ready, switch your builds and press Cut over.](/operations/nexus-migration/)

## Every publish gets its own ticket

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.

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.

Read [how publishing works](/publishing/overview/), then set up the [Gradle plugin](/publishing/gradle-plugin/), the [Maven extension](/publishing/maven-extension/) or [GitHub Actions](/publishing/github-actions/).

## 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.

## One image. Nothing else to run.

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.

Start the image with a volume for `/data`:

```sh
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.

```sh
docker logs telesphorepo
```

Binaries for Linux, macOS and Windows are on the [GitHub Releases](https://github.com/Telesphorepo/Telesphorepo/releases) page. The [install guide](/getting-started/install/) has the other ways to run it.

## 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.

- [Maven`/maven/{repo}/`Artifacts with their checksums. A release file never changes. Metadata and SNAPSHOT files can.](/repositories/maven-gradle/)
- [Gradle plugins`/maven/{repo}/`Builds and Gradle plugins use a maven repo. The Gradle plugin gets its own publish sessions.](/publishing/gradle-plugin/)
- [Debian`/debian/{repo}/`A signed apt repository. Upload a .deb and the server builds the indexes.](/repositories/debian/)
- [Raw`/raw/{repo}/`Plain files at any path. Each repo allows or denies overwrites.](/repositories/raw/)

## Leaving Nexus? Bring your repos with you.

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](https://github.com/sonatype/nexus-public/issues/449) 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.

The [Nexus migration guide](/operations/nexus-migration/) has each step in detail.

## Read the docs

- [Install](/getting-started/install/): Run Telesphorepo in a container or as a binary.
- [Publishing](/publishing/overview/): Publish sessions, the Gradle plugin, the Maven extension and CI.
- [Trust policies](/security/trust-policies/): Let GitHub Actions and Jenkins publish with no stored secret.
- [Nexus migration](/operations/nexus-migration/): Copy your Nexus 3 repos, then cut over.
- [HTTP API](/reference/http-api/): Every route the server answers.
- [Source code](https://github.com/Telesphorepo/Telesphorepo): The server is AGPL-3.0. The publisher clients are MIT.
