# How Harbor Handles Storage Quotas: Project-Level Limits and Enforcement

> Discover how Harbor handles storage quotas at the project level. Learn about its dual-middleware enforcement and PostgreSQL integration for efficient capacity management.

- Repository: [Harbor/harbor](https://github.com/goharbor/harbor)
- Tags: how-to-guide
- Published: 2026-04-09

---

**Harbor enforces storage quotas at the project level using a dual-middleware system that reserves capacity before blob uploads and refreshes actual usage afterward, storing limits as JSONB in PostgreSQL with a hard maximum of 1024 TB per project.**

Harbor implements storage quotas as a first-class resource to prevent individual projects from consuming excessive registry capacity. According to the goharbor/harbor source code, the quota system tracks consumed blob storage against configurable hard limits, rejecting push requests that would exceed the allocated capacity while emitting warnings when usage crosses configurable thresholds.

## Quota Data Model and Storage

The foundation of Harbor's quota system resides in [`src/pkg/quota/models/quota.go`](https://github.com/goharbor/harbor/blob/main/src/pkg/quota/models/quota.go), where the `Quota` struct persists hard limits and current usage as JSONB strings. The model stores two critical fields—`hard` and `used`—which the `MarshalJSON`, `GetHard`, `SetHard`, `GetUsed`, and `SetUsed` methods convert to and from the typed `types.ResourceList`.

The database schema uses optimistic locking via `HardVersion` and `UsedVersion` columns to prevent race conditions during concurrent updates. When stored in PostgreSQL or MySQL, the JSONB (or JSON) columns contain serialized resource lists, typically structured as `{"storage": <bytes>}`.

## Storage Limit Validation

Before persisting any quota configuration, Harbor validates the storage limit through `ValidateQuotaLimit` in [`src/lib/quota_storage_limit.go`](https://github.com/goharbor/harbor/blob/main/src/lib/quota_storage_limit.go). This function enforces two strict rules:

- **Unlimited storage**: Represented by `types.UNLIMITED` (`-1`)
- **Maximum bounded storage**: Any positive value must not exceed `types.MaxLimitedValue` (1024 TB)

If a submitted hard limit violates these constraints, Harbor returns a **400 Bad Request** response, ensuring no project can be configured beyond the system-wide maximum capacity.

## The Quota Controller

The `pkg/quota.Controller` provides the central business logic for quota operations, exposing methods including `Get`, `Count`, `List`, `Update`, `Request`, `Refresh`, and `IsEnabled`. The controller loads `Quota` records, compares requested storage against available capacity, and atomically updates usage metrics.

When the API layer or middleware components need to check or modify quotas, they invoke this controller rather than accessing the database directly, ensuring consistent business rule application across the codebase.

## Request and Refresh Middleware

Harbor implements a two-phase enforcement mechanism through HTTP middleware defined in [`src/server/middleware/quota/quota.go`](https://github.com/goharbor/harbor/blob/main/src/server/middleware/quota/quota.go).

**RequestMiddleware** intercepts incoming push operations before any data reaches the storage backend. It extracts the reference object (typically a project), calculates the anticipated storage consumption via a `Resources` function, and calls `quotaController.Request`. If the current `Used` plus the requested amount exceeds the `Hard` limit, the middleware aborts the request with a **403 Forbidden** error and triggers a `ResourcesExceeded` event.

**RefreshMiddleware** executes after successful blob storage. It invokes `quotaController.Refresh` to recalculate the actual used storage by summing all blob sizes belonging to the project, updating the database with precise consumption figures. This middleware skips execution during retention jobs to avoid unnecessary database load.

## Warning Thresholds and Notifications

The request middleware monitors quota utilization through configurable warning percentages. When storage consumption crosses the default **85%** threshold, Harbor emits a `ResourcesWarning` event, enabling webhook or email notifications before the hard limit is reached.

If a push request would exceed the hard limit, the system denies the operation and generates an `exceeded` event, providing clear feedback to CI/CD pipelines and end users about the storage constraint.

## REST API for Quota Management

Administrators interact with quotas through the API defined in [`src/server/v2.0/handler/quota.go`](https://github.com/goharbor/harbor/blob/main/src/server/v2.0/handler/quota.go). The handler exposes three primary endpoints:

- **GET /quotas/{id}**: Retrieve a specific quota record
- **GET /quotas**: List quotas with optional filtering
- **PUT /quotas/{id}**: Update hard limits via `UpdateQuota`

The update endpoint validates incoming limits through `quota.Validate` before persisting changes, ensuring all modifications comply with the 1024 TB maximum and positive value constraints.

## Code Examples

### Configuring a 100 GB Project Quota

Create a storage quota for a project using the REST API:

```bash
curl -u admin:Harbor12345 -X POST "https://harbor.example.com/api/v2.0/quotas" \
  -H "Content-Type: application/json" \
  -d '{
        "reference": "project",
        "reference_id": "42",
        "hard": {"storage": 107374182400}
      }'

```

This request is handled by `handler/quota.go → UpdateQuota`, which validates the limit through `ValidateQuotaLimit` and persists it to the database.

### Querying Quota Usage Programmatically

Retrieve quota information using the Go controller:

```go
import (
    "context"
    "github.com/goharbor/harbor/src/controller/quota"
)

func getProjectQuota(ctx context.Context, projectID int64) (*quota.Quota, error) {
    // quota.Ctl is the global controller
    return quota.Ctl.Get(ctx, projectID, quota.WithReferenceObject())
}

```

The controller returns a struct where `Hard` and `Used` are `types.ResourceList` objects containing the configured limits and current consumption.

### Implementing Quota Middleware in Custom Handlers

Attach quota enforcement to HTTP handlers using the request middleware:

```go
import (
    quotaMw "github.com/goharbor/harbor/src/server/middleware/quota"
    "github.com/goharbor/harbor/src/pkg/quota/types"
    "net/http"
)

func storageResources(r *http.Request, ref, refID string) (types.ResourceList, error) {
    // Parse image size from request body
    size := int64(500 * 1024 * 1024) // 500 MiB
    return types.ResourceList{types.ResourceNameStorage: size}, nil
}

func myUploadHandler(w http.ResponseWriter, r *http.Request) {
    // ... store blobs ...
    w.WriteHeader(http.StatusCreated)
}

func main() {
    cfg := quotaMw.RequestConfig{
        ReferenceObject: func(r *http.Request) (string, string, error) {
            // Extract project ID from URL path
            return "project", "42", nil
        },
        Resources: storageResources,
        ResourcesWarningPercent: 85,
    }

    handler := quotaMw.RequestMiddleware(cfg)(http.HandlerFunc(myUploadHandler))
    http.Handle("/upload", handler)
    http.ListenAndServe(":8080", nil)
}

```

The middleware calls `quotaController.Request` before executing the handler. If the storage request would exceed the project's quota, Harbor returns a 403 error before any data is written.

## Summary

- Harbor stores quota limits as JSONB in the `quota` table with optimistic locking via `HardVersion` and `UsedVersion` columns.
- **Storage limits** are validated against a hard maximum of 1024 TB (`types.MaxLimitedValue`) and support unlimited (`-1`) configurations.
- **RequestMiddleware** reserves capacity before blob uploads, rejecting requests that would exceed hard limits with 403 errors.
- **RefreshMiddleware** recalculates actual usage after successful operations, ensuring the `used` field reflects real blob consumption.
- The system emits **warning events** at 85% utilization and **exceeded events** when limits are breached.
- All quota operations flow through the central `pkg/quota.Controller`, providing consistent business logic for the REST API and middleware layers.

## Frequently Asked Questions

### How do I set an unlimited storage quota for a Harbor project?

Set the storage value to `-1` (represented as `types.UNLIMITED` in the codebase) when updating the quota through the API. In [`src/lib/quota_storage_limit.go`](https://github.com/goharbor/harbor/blob/main/src/lib/quota_storage_limit.go), the `ValidateQuotaLimit` function explicitly checks for this value to bypass size restrictions, allowing the project to consume storage without hard limits.

### Why is my image push being rejected with a 403 error?

Harbor returns **403 Forbidden** when the `RequestMiddleware` in [`src/server/middleware/quota/quota.go`](https://github.com/goharbor/harbor/blob/main/src/server/middleware/quota/quota.go) determines that the new blob would cause the project's storage usage to exceed the configured hard limit. The middleware calculates the sum of current `Used` storage (from the `quota` table) plus the incoming blob size, comparing it against the `Hard` limit before allowing the upload to proceed.

### How does Harbor calculate the actual storage used by a project?

After a successful image push, the `RefreshMiddleware` triggers `quotaController.Refresh`, which queries the database to sum the size of all blobs associated with the project. This recalculation ensures the `used` field in the quota record reflects accurate consumption rather than estimated reservations, accounting for deduplication and deletion operations.

### What is the maximum storage quota I can configure?

Harbor enforces a system-wide maximum of **1024 TB** per project, defined as `types.MaxLimitedValue` in the source code. The `ValidateQuotaLimit` function rejects any configuration exceeding this threshold, returning a 400 Bad Request error to prevent database overflow and resource exhaustion attacks.