---
title: Self-hosted storage service
description: Publish Allure Report output and keep cross-run history with Allure Report Storage - a free, open-source service you run yourself via Docker or Cloudflare Workers.
---

# Self-hosted storage service (Allure 3)

[**Allure Report Storage**](https://github.com/allure-framework/allure-report-storage) is a free, open-source (Apache-2.0) service that gives Allure 3 somewhere to publish HTML reports and keep cross-run history, without relying on third-party hosting. You run it yourself - as a Docker container or a Cloudflare Worker - and Allure 3 talks to it through the built-in `allureService` client.

With [`allureService.accessToken`](/docs/v3/configure/#_11-report-publishing-allure-service) set and [`publish: true`](/docs/v3/configure/#_11-report-publishing-allure-service) on the relevant plugin(s), publishing is automatic: every report is uploaded, gets a shareable URL, and becomes part of that project's history.

## How it works

Reports are organized by **repository and branch** rather than a fixed project ID, so history is scoped per branch by default. Allure 3 detects `repo` and `branch` automatically: from CI-provided environment variables on a [supported CI server](https://github.com/allure-framework/allure3/tree/main/packages/ci), or otherwise from the current git repository name and checked-out branch.

Each publish:

1. creates a draft report for that `repo`/`branch`;
2. uploads the generated report files;
3. marks the report complete and attaches a history data point.

A completed report is immutable: it can only be deleted, not modified. The history data point is what powers [History and retries](/docs/history-and-retries/) across separate runs.

Images: /images/allure-report-storage/history-tab-light.png, /images/allure-report-storage/history-tab-dark.png

## Deployment

Run it via the [Docker guide](/docs/guides/self-hosted-storage-docker/) or the [Cloudflare Worker guide](/docs/guides/self-hosted-storage-cloudflare/). Both cover full setup step by step. Neither needs an external database or bucket by default: Docker uses a bundled SQLite database and local disk, the Worker uses Cloudflare D1 and R2.

Where report files are stored is independent of where the service runs:

- **Local filesystem** - default for Docker/Node; files go under the configured data directory.
- **Any S3-compatible bucket** - AWS S3, R2's S3-compatible endpoint, or another provider such as MinIO. See the [Docker guide](/docs/guides/self-hosted-storage-docker/#s3-compatible-storage) for setup.
- **Cloudflare R2** - used automatically via a native binding when running as a Worker.

## Connecting Allure 3 to the storage service

Mint a report-access token using the bootstrap `ACCESS_TOKEN` set up during deployment:

```bash
curl -sS -X POST http://localhost:3000/api/token \
  -H "Authorization: Bearer storage_bootstrap_token"
```

This returns a token in the `ars1.<payload>.<signature>` format. Unlike the bootstrap token, it's safe to hand to CI: it only authorizes publishing and history reads. It's also self-describing: the payload embeds the storage instance's URL, so Allure 3 needs no separate server-address setting.

Add it to the Allure 3 configuration, alongside `publish: true` on the plugin(s) to publish:

```javascript
import { defineConfig } from "allure";

export default defineConfig({
  name: "Allure Report",
  plugins: {
    awesome: {
      options: {
        publish: true, // explicitly specify which reports should be published
      },
    },
  },
  allureService: {
    accessToken: "ars1...",
  },
});
```

From here, running tests through Allure 3 publishes the report automatically.

## Choosing a main branch

A repository's main branch defaults to `main`. If yours is named differently, set it explicitly (also using the bootstrap token) so other branches compare against the right baseline:

```bash
curl -sS -X POST http://localhost:3000/api/projects/main-branch \
  -H "Authorization: Bearer storage_bootstrap_token" \
  -H "Content-Type: application/json" \
  -d '{ "repo": "repo_name", "main_branch": "main_branch_name"}'
```

## History depth and branches

Every completed report's history point is kept permanently: nothing expires automatically, only explicit deletion removes it. Allure 3 fetches the **10 most recent** points per branch by default; set `historyLimit` to fetch further back:

```javascript
export default defineConfig({
  historyLimit: 50,
});
```

Unlike `historyPath` (which prunes older entries from a local file), `historyLimit` here only limits what's _fetched_: full history stays stored regardless. The two options are also mutually exclusive: `historyPath` is ignored if `allureService.accessToken` is set.

A non-main branch's history is padded with the main branch's older reports when it doesn't have enough of its own to fill the requested count, extending its own timeline further back, never interleaved with it. A branch with one or two publishes still gets a populated trend chart this way. Main's own history is never padded, and a branch with enough reports of its own doesn't get padded either. This is based on branch _name_, not git ancestry: a git-orphan branch is treated the same as a real feature branch. Only `GET /api/history` (and therefore trend charts and the history tab) does this blending; the [reports tree page](#browsing-published-reports) always groups strictly by branch.

## Browsing published reports

![Reports tree page](/images/allure-report-storage/reports-tree.png "Reports tree page")

Every instance also serves a page listing completed reports for a repository, grouped by branch:

```
https://<your-storage-domain>/reports/tree?repo=<repo>
```

Useful for finding a previous report without digging through CI logs.

## Deleting a report

A completed report can only be deleted, not edited: this removes both its files and its history point, authenticated with a report-access token rather than the bootstrap token:

```bash
curl -sS -X POST http://localhost:3000/api/report/<report-uuid>/delete \
  -H "Authorization: Bearer ars1..."
```

Afterward, the report's URL stops resolving, and it disappears from `GET /api/history` and the [tree page](#browsing-published-reports). If it was one of main's reports, branches that were padded with it fall back to whatever remains.
