Portal Community

On This Page

  1. Infrastructure Requirements
  2. Create the Storage Bucket
  3. DI Registration
  4. Configuration — appsettings.json
  5. Environment Variables
  6. Setting StorageClass and CannedAcl
  7. Health Check
  8. Capacity Guard Setup

Infrastructure Requirements

InfraHub Storage requires an S3-compatible object store. For BizFirst self-hosted deployments the standard choice is MinIO — a high-performance, Kubernetes-native object store that is fully S3-API-compatible. For cloud-first deployments, AWS S3, Cloudflare R2, or Backblaze B2 can be used interchangeably through a single configuration switch.

The fastest way to run MinIO locally or on a production server is via Docker Compose. The configuration below is production-grade: it exposes the S3 API on port 9000 and the MinIO web console on port 9001, uses a named Docker volume for data persistence, enables Prometheus metrics for the capacity guard, and includes a health check so Docker can automatically restart the container on failure.

docker-compose.yml

# docker-compose.yml — BizFirst MinIO production setup
version: '3.8'
services:
  minio:
    image: minio/minio:latest
    container_name: bizfirst-minio
    command: server /data --console-address ":9001"
    ports:
      - "9000:9000"    # S3 API
      - "9001:9001"    # Web Console
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
      MINIO_PROMETHEUS_AUTH_TYPE: public
    volumes:
      - minio-data:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  minio-data:
Security — Never Hardcode Credentials Set strong credentials via environment variables in production. Never hardcode secrets in docker-compose.yml. Use a .env file (excluded from source control), Docker secrets, or a secrets manager such as HashiCorp Vault. A minimum 20-character random password is recommended for MINIO_ROOT_PASSWORD.

To start MinIO:

# Set credentials in the shell or a .env file before running
export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=YourStr0ngPassword!

docker-compose up -d

Once running, the MinIO web console is accessible at http://localhost:9001. The S3 API endpoint used by InfraHub Storage is http://localhost:9000.

Create the Storage Bucket

InfraHub Storage requires a single bucket — by default named bizfirst-files. This bucket name must exactly match the FileStorage:BucketName configuration value. All tenant files are stored in this one bucket, namespaced by key prefix (tenant_{tenantID}/). You do not need separate buckets per tenant.

Option A — MinIO Web Console

Navigate to http://localhost:9001, sign in with your root credentials, go to BucketsCreate Bucket, enter bizfirst-files, and click Create Bucket. Leave all options at their defaults — versioning and object locking are not required.

Option B — MinIO Client (mc)

The mc command-line client is the fastest way to create buckets from automation scripts or CI pipelines. Install it from min.io/docs, then:

# Register the local MinIO instance as an alias
mc alias set local http://localhost:9000 admin yourpassword

# Create the bucket
mc mb local/bizfirst-files

# Ensure the bucket is private (no anonymous access)
mc anonymous set none local/bizfirst-files

# Verify
mc ls local/

The mc anonymous set none command ensures the bucket has no public read or write policy. All access is authenticated through the access key / secret key pair configured in InfraHub Storage.

DI Registration

InfraHub Storage is registered with a single extension method call. Add this to your Program.cs (or Startup.cs in older projects):

// Program.cs
builder.Services.AddObjectStorage(builder.Configuration);

AddObjectStorage performs the following registrations in order:

RegistrationLifetimeWhat it does
Configure<FileStorageSettings> Singleton (options) Binds the FileStorage configuration section to the FileStorageSettings record, making settings injectable via IOptions<FileStorageSettings>.
HttpClient (named) Managed by IHttpClientFactory A named HttpClient used exclusively by IStorageCapacityGuard to poll MinIO Prometheus metrics at {Endpoint}/minio/v2/metrics/cluster.
AddS3Services Various Registers the low-level S3 layer: IS3BucketService, IS3ObjectService, and IS3FolderService. These are internal to InfraHub Storage and are not used directly by application code.
ICidProvider Singleton Computes Content IDs (CIDs) from file bytes using SHA-256. Singleton because it is stateless and allocation-free.
IStorageCapacityGuard Scoped Polls MinIO disk metrics before each write. Throws StorageCapacityException if usage exceeds WriteStopThresholdPercent.
IObjectStorageProvider Scoped The primary application interface — StoreAsync, GetStreamAsync, ExistsAsync. Scoped so it participates in per-request IAiSessionContext for tenant isolation.
One Interface, Any Backend Application code always injects IObjectStorageProvider and never references any S3/MinIO-specific type. Switching from MinIO to AWS S3 or Cloudflare R2 requires only a configuration change — no code changes in calling services.

Configuration — appsettings.json

All InfraHub Storage settings live under the FileStorage key. Below are complete, ready-to-use examples for the four supported environments. Copy the relevant block into your appsettings.json (or the environment-specific override file) and adjust credentials.

Local Development (MinIO)

For local development, MinIO runs in Docker with simple credentials. PrependTenantID is disabled so you can easily browse files in the console without tenant prefixes. WriteStopThresholdPercent: 0 disables the capacity guard so it does not interfere with development.

{
  "FileStorage": {
    "BucketName": "bizfirst-files",
    "Endpoint": "http://localhost:9000",
    "AccessKey": "admin",
    "SecretKey": "admin123",
    "Region": "us-east-1",
    "ForcePathStyle": true,
    "PrependTenantID": false,
    "WriteStopThresholdPercent": 0
  }
}

Production (MinIO on Docker)

In production the endpoint points to the Docker service name (minio) rather than localhost. Credentials are injected via environment variables. Tenant isolation is enabled. The capacity guard stops writes at 90% disk usage — tune this value based on your alerting strategy.

{
  "FileStorage": {
    "BucketName": "bizfirst-files",
    "Endpoint": "http://minio:9000",
    "AccessKey": "${MINIO_ACCESS_KEY}",
    "SecretKey": "${MINIO_SECRET_KEY}",
    "Region": "us-east-1",
    "ForcePathStyle": true,
    "PrependTenantID": true,
    "WriteStopThresholdPercent": 90.0
  }
}

AWS S3

For AWS S3, leave Endpoint blank — the SDK resolves the regional endpoint automatically from Region. Set ForcePathStyle to false to use the standard virtual-hosted-style URLs (bucket.s3.region.amazonaws.com). The capacity guard (WriteStopThresholdPercent: 0) is disabled because S3 has effectively unlimited capacity.

{
  "FileStorage": {
    "BucketName": "my-bizfirst-files",
    "Endpoint": "",
    "AccessKey": "${AWS_ACCESS_KEY_ID}",
    "SecretKey": "${AWS_SECRET_ACCESS_KEY}",
    "Region": "us-east-1",
    "ForcePathStyle": false,
    "PrependTenantID": true,
    "WriteStopThresholdPercent": 0
  }
}

Cloudflare R2

Cloudflare R2 is S3-compatible with zero egress fees — an excellent cost-efficient choice for large-volume storage. The endpoint must include your Cloudflare Account ID. R2 requires ForcePathStyle: true. The Region value auto lets Cloudflare route to the closest data centre.

{
  "FileStorage": {
    "BucketName": "bizfirst-files",
    "Endpoint": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
    "AccessKey": "${R2_ACCESS_KEY_ID}",
    "SecretKey": "${R2_SECRET_ACCESS_KEY}",
    "Region": "auto",
    "ForcePathStyle": true,
    "PrependTenantID": true,
    "WriteStopThresholdPercent": 0
  }
}

Environment Variables

Every FileStorage setting can be overridden at deployment time using environment variables. ASP.NET Core maps double-underscore (__) separators to configuration hierarchy, so FileStorage__BucketName overrides FileStorage:BucketName in appsettings.json.

This is the recommended pattern for Kubernetes, Docker Compose, and CI/CD pipelines: keep non-sensitive defaults in appsettings.json and inject secrets exclusively through environment variables.

Environment VariableMaps ToExample Value
FileStorage__BucketName FileStorage:BucketName bizfirst-files
FileStorage__Endpoint FileStorage:Endpoint http://minio:9000
FileStorage__AccessKey FileStorage:AccessKey minio-access-key
FileStorage__SecretKey FileStorage:SecretKey minio-secret-key
FileStorage__Region FileStorage:Region us-east-1
FileStorage__ForcePathStyle FileStorage:ForcePathStyle true
FileStorage__PrependTenantID FileStorage:PrependTenantID true
FileStorage__WriteStopThresholdPercent FileStorage:WriteStopThresholdPercent 90.0
FileStorage__CannedAcl FileStorage:CannedAcl private
FileStorage__StorageClass FileStorage:StorageClass STANDARD
Kubernetes Secret Pattern In Kubernetes, mount your S3 credentials as a Secret and expose them as environment variables. This ensures credentials are never stored in ConfigMaps, image layers, or Helm values files committed to source control.

Setting StorageClass and CannedAcl

InfraHub Storage supports per-object StorageClass and CannedAcl overrides through StoreOptions. The global defaults applied when no override is provided are:

To override the storage class or ACL for a specific file, pass a StoreOptions instance to StoreAsync:

// Store an infrequently-accessed archive at lower cost
var opts = new StoreOptions(CannedAcl: "private", StorageClass: "STANDARD_IA");
string cid = await _storage.StoreAsync(content, "application/pdf", opts, ct);

Valid StorageClass Values

StorageClassUse CaseMinIOAWS S3
STANDARD Active data, frequent access Supported Supported
REDUCED_REDUNDANCY Reproducible, non-critical data Supported Supported
STANDARD_IA Infrequent access, rapid retrieval N/A Supported
ONEZONE_IA Infrequent access, single AZ N/A Supported
INTELLIGENT_TIERING Automatic cost optimisation N/A Supported
GLACIER Long-term archive, minutes retrieval N/A Supported
DEEP_ARCHIVE Compliance archive, hours retrieval N/A Supported
MinIO and Storage Classes MinIO supports STANDARD and REDUCED_REDUNDANCY only. Sending INTELLIGENT_TIERING, GLACIER, or DEEP_ARCHIVE to a MinIO endpoint will be silently ignored or return an error, depending on the MinIO version. Use these classes only when targeting AWS S3.

Health Check

After completing setup, verify the integration is working correctly before deploying to production. There are two complementary approaches.

Option A — Application-Level Probe

Call ExistsAsync with a known non-existent CID. If the provider is correctly configured and can reach MinIO, it returns false. If credentials are wrong, the endpoint is unreachable, or the bucket does not exist, it throws an exception — surfacing the misconfiguration early.

// Lightweight connectivity probe — inject IObjectStorageProvider
bool exists = await _storage.ExistsAsync("health-check-probe", ct);
// Should return false (not throw) if everything is configured correctly

This call is safe to use in ASP.NET Core IHealthCheck implementations. Add it to a /health/ready endpoint to block traffic until the storage layer is confirmed reachable.

Option B — MinIO HTTP Liveness Endpoint

MinIO exposes a liveness endpoint that returns HTTP 200 when the server is healthy:

# From the host or another container in the same network
curl -f http://minio:9000/minio/health/live
# Returns 200 OK when MinIO is running and healthy

This endpoint is also used by the Docker Compose healthcheck definition shown in the Infrastructure Requirements section. Kubernetes liveness probes should target this URL.

Capacity Guard Setup

The Capacity Guard prevents writes from filling the disk to 100%, which would cause data loss and MinIO instability. Before every StoreAsync call, IStorageCapacityGuard polls the MinIO Prometheus metrics endpoint and calculates the current disk usage percentage. If usage is at or above WriteStopThresholdPercent, it throws StorageCapacityException, which the caller should surface as an HTTP 507 Insufficient Storage response.

How the Guard Reads Disk Usage

The guard fetches the cluster metrics from:

GET {Endpoint}/minio/v2/metrics/cluster

It parses two Prometheus gauge metrics from the response body:

Usage percent is computed as (used / total) * 100. If this value is greater than or equal to WriteStopThresholdPercent, the write is blocked.

Enabling Prometheus Metrics on MinIO

The MinIO Prometheus metrics endpoint must be publicly accessible (no authentication) for the guard to function. This is configured via the MINIO_PROMETHEUS_AUTH_TYPE: public environment variable in Docker Compose:

environment:
  MINIO_PROMETHEUS_AUTH_TYPE: public
Network Accessibility Required The Prometheus metrics endpoint must be accessible from the application container on port 9000. Ensure no firewall, security group, or network policy blocks TCP port 9000 between your application container and the MinIO container. If the guard cannot reach the metrics endpoint within the configured HTTP client timeout, it will either fail open (allow the write) or fail closed (block the write) depending on your error handling policy — check your StorageCapacityException handler.

Recommended Threshold Values

ScenarioWriteStopThresholdPercentNotes
Local development 0 Disabled — no cap. Use only on developer machines.
Staging / QA 95 Permissive — allows maximum utilisation for testing.
Production (default) 90 Stops new writes with 10% headroom for compaction and temp files.
Production (conservative) 80 Use when MinIO volume cannot be easily expanded and downtime is critical to avoid.
Pairing with Prometheus Alerts The capacity guard is a last-resort safety net — not a substitute for proactive monitoring. Pair it with Prometheus alert rules at 70% (warning), 85% (critical), and 95% (emergency) so ops teams have time to add capacity before the guard starts blocking writes. See the Resiliency & Monitoring guide for alert rule examples.