Skip to content

Object Storage

Norn supports S3-compatible object storage as a shared service. Configure it with Cloudflare R2, AWS S3, Google Cloud Storage, DigitalOcean Spaces, or any S3-compatible provider.

infraspec

yaml
services:
  storage:
    bucket: my-app-uploads
    provider: r2  # or s3, gcs, spaces

Fields

FieldTypeDescription
services.storage.bucketstringBucket name to create/use
services.storage.providerstringStorage provider hint (for documentation; all use S3 protocol)

How it works

  1. Forge creates the bucket in the configured S3 endpoint (if it doesn't exist)
  2. Deploy injects storage credentials as environment variables into the K8s Deployment:
VariableDescription
S3_ENDPOINTS3-compatible endpoint URL
S3_BUCKETBucket name
AWS_ACCESS_KEY_IDAccess key
AWS_SECRET_ACCESS_KEYSecret key

Configuration

S3 storage is opt-in. Set NORN_S3_ENDPOINT and credentials to enable it. When unconfigured, Norn starts without S3 (no error).

Environment variables

VariableDefaultDescription
NORN_S3_ENDPOINT(none)S3 endpoint (required to enable storage)
NORN_S3_ACCESS_KEY(none)Access key
NORN_S3_SECRET_KEY(none)Secret key
NORN_S3_REGIONautoRegion (auto works for R2)
NORN_S3_USE_SSLtrueUse HTTPS (set to false for local endpoints)

Providers

All providers use the same S3-compatible protocol. The provider field in the infraspec is informational — Norn connects to whatever endpoint is configured.

Cloudflare R2

bash
export NORN_S3_ENDPOINT=<account-id>.r2.cloudflarestorage.com
export NORN_S3_ACCESS_KEY=<r2-access-key>
export NORN_S3_SECRET_KEY=<r2-secret-key>

R2 has no egress fees, making it ideal for serving user-uploaded content.

AWS S3

bash
export NORN_S3_ENDPOINT=s3.amazonaws.com
export NORN_S3_ACCESS_KEY=<aws-access-key>
export NORN_S3_SECRET_KEY=<aws-secret-key>
export NORN_S3_REGION=us-east-1

Google Cloud Storage

bash
export NORN_S3_ENDPOINT=storage.googleapis.com
export NORN_S3_ACCESS_KEY=<gcs-hmac-key>
export NORN_S3_SECRET_KEY=<gcs-hmac-secret>

GCS supports S3-compatible access via HMAC keys.

DigitalOcean Spaces

bash
export NORN_S3_ENDPOINT=<region>.digitaloceanspaces.com
export NORN_S3_ACCESS_KEY=<spaces-key>
export NORN_S3_SECRET_KEY=<spaces-secret>
export NORN_S3_REGION=<region>

SDK examples

Go (minio-go)

go
import "github.com/minio/minio-go/v7"

client, _ := minio.New(os.Getenv("S3_ENDPOINT"), &minio.Options{
    Creds:  credentials.NewStaticV4(
        os.Getenv("AWS_ACCESS_KEY_ID"),
        os.Getenv("AWS_SECRET_ACCESS_KEY"),
        "",
    ),
})

// Upload
client.PutObject(ctx, os.Getenv("S3_BUCKET"), "photo.jpg", reader, size, minio.PutObjectOptions{})

// Download
obj, _ := client.GetObject(ctx, os.Getenv("S3_BUCKET"), "photo.jpg", minio.GetObjectOptions{})

Node.js (aws-sdk)

javascript
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3')

const s3 = new S3Client({
  endpoint: `http://${process.env.S3_ENDPOINT}`,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
  forcePathStyle: true,
})

await s3.send(new PutObjectCommand({
  Bucket: process.env.S3_BUCKET,
  Key: 'photo.jpg',
  Body: buffer,
}))

Health check

Norn's /api/health endpoint includes S3 connectivity. Check with:

bash
norn health
# or
curl http://localhost:8800/api/health | jq '.services[] | select(.name == "s3")'