Setup & Configuration
Everything you need to stand up production-ready InfraHub Storage — from Docker infrastructure and bucket creation through DI registration, multi-environment appsettings, environment variable overrides, storage class tuning, and capacity guard configuration.
On This Page
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:
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 Buckets → Create 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:
| Registration | Lifetime | What 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. |
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 Variable | Maps To | Example 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 |
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:
- StorageClass:
STANDARD— full-durability, low-latency storage. - CannedAcl:
NoACL— no ACL header is sent, relying on bucket policy for access control (recommended for MinIO and R2).
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
| StorageClass | Use Case | MinIO | AWS 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 |
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:
minio_node_disk_used_bytes— total bytes used across all data drivesminio_node_disk_total_bytes— total capacity across all data drives
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
StorageCapacityException handler.
Recommended Threshold Values
| Scenario | WriteStopThresholdPercent | Notes |
|---|---|---|
| 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. |