# What Is the Role of the Controller Layer in Harbor?

> Discover the role of the Controller Layer in Harbor. It coordinates business logic, manages database transactions, enforces policies, and publishes events for seamless operations.

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

---

**The Controller Layer in Harbor serves as the central business logic coordinator that orchestrates complex operations, manages database transactions, enforces policies, and publishes events to bridge HTTP handlers and low-level data managers.**

The **Controller Layer** operates as the cognitive core of the goharbor/harbor architecture, residing under `src/controller/` and implementing the rules that govern container registry operations. Unlike the API layer that handles HTTP concerns or the Manager layer that executes raw database queries, controllers coordinate multi-step workflows to ensure data consistency across projects, repositories, and artifacts. This architectural separation allows Harbor to maintain atomic transactions while integrating with external systems like notification webhooks, OIDC providers, and vulnerability scanners.

## Core Responsibilities of the Controller Layer

Controllers in Harbor encapsulate six primary responsibilities that transform simple HTTP requests into reliable, auditable system operations.

### Orchestrating Multi-Step Operations

Controllers combine multiple manager calls into single logical actions. When creating a project, `project.Controller.Create` (implemented in [`src/controller/project/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/project/controller.go)) orchestrates several distinct operations within one workflow: it persists the project via `projectMgr.Create`, initializes an empty CVE allowlist through `allowlistMgr.CreateEmpty`, and attaches metadata using `metaMgr.Add`. This aggregation prevents API handlers from needing to understand the underlying dependency chain between these entities.

### Transaction Management and Atomicity

Database consistency relies on the controller's ability to wrap related writes in atomic transactions. The `repository.Controller.Ensure` method in [`src/controller/repository/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/repository/controller.go) demonstrates this pattern by utilizing `orm.WithTransaction` to create repository records. If any step fails—such as a conflict with an existing repository—the entire transaction rolls back, leaving the database in a consistent state rather than persisting partial data.

### Input Validation and Data Enrichment

Before executing business logic, controllers parse and verify incoming data. In [`src/controller/artifact/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/artifact/controller.go), the `artifact.Controller.Ensure` method extracts the project name from repository strings (e.g., parsing "myproject/myrepo" to identify "myproject") and validates the project's existence before proceeding with artifact creation. This validation layer ensures that downstream managers receive pre-verified, context-rich data structures.

### Policy Enforcement and Security Checks

Controllers enforce Harbor's governance policies including immutable tag rules, quota limits, and CVE allowlists. The artifact controller references `immutableMtr` to validate immutable tag constraints before allowing updates to existing artifacts, preventing accidental or unauthorized overwrites of critical images. These checks occur after API authentication but before database persistence, creating a centralized enforcement point.

### Event Publishing and System Integration

Harbor's event-driven architecture depends on controllers to emit notifications. After successfully creating a project, `project.Controller.Create` invokes `notification.AddEvent` with a `CreateProjectEventMetadata` payload. This decoupled approach allows replication services, audit loggers, and webhook notification systems to react to state changes without the controller needing direct knowledge of these subscribers.

### Cross-Component Coordination

Complex deletion operations demonstrate the controller's coordination role. The `repository.Controller.Delete` method in [`src/controller/repository/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/repository/controller.go) implements cascade logic: it first iterates through all associated artifacts, verifies no external references exist, removes accessory objects, and finally deletes the repository record itself. This sequencing prevents orphaned database records and ensures referential integrity across Harbor's relational schema.

## Where the Controller Layer Lives in the Codebase

All controller implementations reside under the `src/controller/` directory, following a consistent package-per-domain structure:

- **[`src/controller/project/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/project/controller.go)** – Project lifecycle management, metadata handling, and CVE allowlist coordination
- **[`src/controller/repository/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/repository/controller.go)** – Repository CRUD operations, pull-count tracking, and idempotent creation via `Ensure`
- **[`src/controller/artifact/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/artifact/controller.go)** – Artifact push/pull workflows, tag management, and accessory handling
- **[`src/controller/user/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/user/controller.go)** – User account management, OIDC onboarding flows, and GDPR deletion procedures
- **[`src/controller/scanner/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/scanner/controller.go)** – Vulnerability scanning job coordination and status aggregation
- **[`src/controller/retention/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/retention/controller.go)** – Tag retention policy execution and garbage collection scheduling
- **[`src/controller/quota/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/quota/controller.go)** – Resource quota calculations and enforcement mechanisms

Each package exposes a global singleton variable (e.g., `project.Ctl`, `artifact.Ctl`, `user.Ctl`) initialized via `var Ctl = NewController()`, allowing API handlers to invoke business logic without manual controller instantiation.

## Practical Examples: Using Controllers in Harbor

The following examples illustrate how Harbor's API layer interacts with controllers to execute business operations.

### Creating a Project with Business Logic

API handlers delegate complex creation workflows to the project controller:

```go
func CreateProjectHandler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    var proj models.Project
    
    // Decode JSON body (omitted for brevity)
    
    // Delegate to controller - handles transactions, allowlists, and events
    projectID, err := project.Ctl.Create(ctx, &proj)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    json.NewEncoder(w).Encode(map[string]int64{"project_id": projectID})
}

```

Behind the scenes, `project.Ctl.Create` executes the full workflow defined in [`src/controller/project/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/project/controller.go) (lines 86-119), including transaction management and event emission.

### Ensuring Repository Existence

Controllers provide idempotent operations for scenarios where a repository might already exist:

```go
created, repoID, err := repository.Ctl.Ensure(ctx, "library/nginx")
if err != nil {
    log.Fatalf("Repository ensure failed: %v", err)
}
if created {
    fmt.Printf("Created new repository with ID: %d\n", repoID)
} else {
    fmt.Printf("Repository exists with ID: %d\n", repoID)
}

```

The `Ensure` method in [`src/controller/repository/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/repository/controller.go) (lines 77-115) handles the conditional logic, creating the repository only if absent while maintaining transactional safety.

### Handling Artifact Pushes with Event Publishing

When container images are pushed, the artifact controller manages tagging and fires notification events:

```go
artCreated, artID, err := artifact.Ctl.Ensure(ctx,
    "production/backend", "sha256:abc123...", &artifact.ArtOption{
        Tags: []string{"v2.1", "latest"},
    })
if err != nil {
    handleError(err)
}

if artCreated {
    // Controller automatically published PushArtifactEventMetadata
    log.Printf("New artifact created: %d\n", artID)
}

```

As implemented in [`src/controller/artifact/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/artifact/controller.go) (lines 65-102), this method checks for proxy-cache configurations to avoid duplicate events, validates immutable tag policies, and publishes `PushArtifactEventMetadata` for downstream notification processing.

## Summary

- The **Controller Layer in Harbor** sits between API routers and data managers, implementing the registry's core business logic.
- Controllers orchestrate multi-step operations—such as project creation with metadata and allowlists—while maintaining atomicity through database transactions.
- Policy enforcement occurs at the controller level, including immutable tag validation, quota checks, and CVE allowlist verification.
- Global singletons (`project.Ctl`, `artifact.Ctl`, etc.) provide convenient access points for API handlers throughout the codebase.
- Event publishing within controllers enables Harbor's notification system, replication services, and audit logging to react to state changes asynchronously.
- All controller implementations reside under `src/controller/`, with each domain (project, artifact, repository, user) maintaining its own package and [`controller.go`](https://github.com/goharbor/harbor/blob/main/controller.go) file.

## Frequently Asked Questions

### What is the difference between the Controller and Manager layers in Harbor?

The **Controller Layer** contains business logic, orchestration, and policy enforcement, while the **Manager/DAO Layer** handles direct database and storage operations. Controllers call managers to execute queries but add transaction coordination, input validation, and cross-component coordination that managers do not handle. For example, the project controller coordinates creation of CVE allowlists and metadata, while the project manager only executes the SQL INSERT statements.

### How does the Controller Layer ensure database consistency?

Controllers utilize `orm.WithTransaction` (from Harbor's ORM package) to wrap multiple database operations in atomic transactions. As seen in [`src/controller/repository/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/repository/controller.go), the `Ensure` method begins a transaction, attempts to create the repository, and rolls back automatically if any step encounters an error. This prevents partial writes that would leave the database in an inconsistent state.

### Why does Harbor expose controllers as global singletons like `project.Ctl`?

Each controller package exposes a `Ctl` variable initialized via `var Ctl = NewController()` to provide dependency injection without complex wiring. This pattern allows API handlers to invoke `project.Ctl.Create()` or `artifact.Ctl.Ensure()` directly without needing to understand controller constructor requirements or manage instance lifecycles. The implementation resides in files like [`src/controller/project/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/project/controller.go) (lines 36-38).

### How does the Controller Layer trigger webhook notifications?

After successfully completing operations, controllers call `notification.AddEvent()` with specific metadata structures like `CreateProjectEventMetadata` or `PushArtifactEventMetadata`. This occurs within the controller method—for instance, in [`src/controller/project/controller.go`](https://github.com/goharbor/harbor/blob/main/src/controller/project/controller.go) (lines 112-119)—ensuring events only fire when data persistence succeeds. The notification system then asynchronously processes these events to deliver webhook callbacks.