Features Apograph CMS on GitHub

Azure Blob Storage

Media plugin @apograph/media-server@apograph/media-admin

Containers rather than buckets, and a capability that depends on how you authenticate.

Documents 0.5.2 Updated Edit this page Report a problem

On this page

The one major object store with no S3 compatibility at all — different protocol, different signature, containers instead of buckets. That is why it needs its own adapter rather than an endpoint in the S3 provider, and it is the gap that stopped a Microsoft-shop deployment running Apograph at all.

npm install @apograph/media-provider-azure
import { createAzureStorageProvider } from '@apograph/media-provider-azure';

MediaServerPlugin({
    provider: createAzureStorageProvider(config.plugins.media.storage),
    config: config.plugins.media
});

Configuration

{ container, connectionString?, accountName?, accountKey?, keyPrefix?, containerClient? }

VariableWhat it does
MEDIA_AZURE_CONTAINERThe container name. Required
MEDIA_AZURE_CONNECTION_STRINGWhat the portal hands you, and what Azurite prints

Three ways to connect

In the order deployments reach for them:

  1. connectionString — the usual one.
  2. accountName + accountKey.
  3. containerClient — an already-built client.

The third is the escape hatch for managed identity: build a client with DefaultAzureCredential from @azure/identity and pass it, and this package stays free of that dependency.

import { DefaultAzureCredential } from '@azure/identity';
import { BlobServiceClient } from '@azure/storage-blob';

const containerClient = new BlobServiceClient(
    `https://${account}.blob.core.windows.net`,
    new DefaultAzureCredential()
).getContainerClient('apograph-media');

createAzureStorageProvider({ container: 'apograph-media', containerClient });

It is also the seam the package’s own tests inject through, so they drive the same entry point a real caller uses rather than a private hook.

directUrl is computed, not hardcoded

Managed identity cannot sign a SAS

A SAS token needs a shared key to sign with. A deployment on managed identity has none, so directUrl is false there and its downloads are proxied.

Declaring true unconditionally would let the plugin accept MEDIA_DIRECT_SERVE=signed-url on a deployment that cannot honour it — and the failure would land per request rather than at boot, which is exactly what that design exists to prevent.

When it can sign, the SAS pins rscd and rsct — content disposition and content type — the same rule every signing provider follows, for the same reason: a redirect discards the app’s own headers, and the stored MIME type is the uploader’s claim.

A user-delegation SAS would let a managed identity sign. It needs a delegation key fetched from the service, so it is a follow-up rather than a silent assumption.

What not to lose in a refactor

A failed upload deletes the blob. Azure charges for uncommitted blocks and does not list them, and the key never reached a caller, so nothing else can reclaim them.

A source error reaches the meter. Without the forwarded error, an upload whose source dies waits forever on a stream that will never end.

get maps BlobNotFound and 404 to a not-found error and rethrows everything else. A throttled or unauthorized account is an outage, and dressing it as a 404 hides it behind a plausible answer.