Skip to content

0011 - Defer Dedicated CDN for MVP Media

Status: Accepted Date: 2026-08-10 Author: Manuel Nucci

Context

ADR 0007 selects Cloud Storage for immutable team logos and private profile media. This ADR extends that decision; it does not supersede ADR 0007.

The MVP needs team logos that can be rendered wherever a known team invitation or other public FPC surface is shared. Team discovery remains authorized by the application, but a logo URL must remain usable outside FPC once it is known. Profile media has a different privacy boundary and must not be made public just to improve delivery latency.

Cloud Storage can cache a public, cacheable object in its built-in cache without a separate CDN or load balancer. Google recommends that path for small traffic volumes: below a few GiB of cacheable data per month it can cost less overall than the combination of Cloud Storage, Cloud CDN, and Cloud Load Balancing. Cloud CDN requires an external Application Load Balancer in front of a Cloud Storage backend. Its load balancer also introduces a fixed forwarding-rule charge of USD 0.025 per hour, or about USD 18.25 per 730-hour month, in each active project before CDN transfer and request charges. These are published pricing inputs, not FPC billing measurements. Cloud Storage caching, Cloud CDN setup, and VPC pricing document those behaviors and prices.

The inherited legacy Domain Restricted Sharing (DRS) constraint constraints/iam.allowedPolicyMemberDomains rejects the public principal used for object reads. It cannot use service-resource tag exceptions, so it cannot express a bucket-only exception while inherited. The exception must therefore be made only at the two FPC project levels, rather than relaxing the inherited organization policy or introducing a broader exception.

Decision

Defer a dedicated Cloud CDN, external Application Load Balancer, and custom media hostname for MVP media delivery.

  • Keep team discovery application-authorized. Store immutable team logos as non-confidential public objects in the public-assets bucket, with opaque, versioned object keys and Cache-Control: public, max-age=31536000, immutable. Serve them directly from Cloud Storage. Grant anonymous object GET only; do not grant bucket listing. A known logo URL may be used outside FPC, which is required by the existing public invitation surface.
  • Override DRS only at the two FPC project levels. Keep the inherited legacy constraints/iam.allowedPolicyMemberDomains policy unchanged above them. Give only public-assets an explicit conditional allUsers object-read binding. Keep uniform bucket-level access enabled. private-media keeps public access prevention enforced.
  • Keep profile media private. When application-owned profile-photo behavior is available, authorize the read in the application and issue a short-lived GCS signed read URL with a five-minute expiry. Do not persist a provider hostname; persist provider-neutral object keys instead.
  • Do not add explicit cache invalidation or a custom media domain for MVP. Logo replacement creates a new opaque versioned key; consumers must use that new key rather than mutating a cacheable object in place.

Reconsider this decision only when one or more of the following is measured or required:

  • Cloud Storage media delivery cost makes a CDN cheaper after including the fixed load-balancer charge and all CDN transfer and request costs.
  • Measured media latency misses an established target.
  • A custom media hostname or explicit invalidation is required.
  • Private-media traffic needs shared edge caching.

The future candidate is a private Cloud Storage origin behind Cloud CDN and an external Application Load Balancer, using signed requests. It requires a new ADR covering signing-key lifecycle, domain and TLS ownership, cache policy, invalidation, and total cost.

Consequences

Positive

  • Public team logos can be cached at Cloud Storage's built-in edge cache and read by invite recipients without proxying object bytes through the API.
  • Opaque, versioned keys make long-lived immutable caching safe for normal logo replacement while avoiding semantic identifiers in public URLs.
  • Authenticated discovery, no bucket listing, and separate public and private buckets limit what public access exposes.
  • Profile media remains protected by application authorization and short-lived signed reads, without adding a shared public edge cache.
  • The MVP avoids the fixed load-balancer charge, dedicated CDN configuration, custom-domain/TLS work, and invalidation operations until evidence supports them.

Negative

  • Anyone who learns a public logo URL can retrieve that non-confidential object, including outside FPC.
  • There is no custom media hostname or explicit cache invalidation. A deleted logo can remain in an immutable cache until its long TTL expires.
  • Private media receives no shared edge-cache benefit.
  • Relaxing the DRS guardrail at each FPC project adds a narrowly scoped policy exception that must remain limited to the public-assets use case.
  • CDN and load-balancer economics cannot be assumed; they must be established from FPC traffic, latency, and billing data.

Mitigations

  • Use random, opaque versioned keys; do not encode team names, member data, or other confidential information in object paths.
  • Keep discovery authenticated in the application, grant no bucket listing, and expose anonymous object reads only through the conditional public-assets binding. Keep uniform bucket-level access enabled so object ACLs cannot create an alternate access path.
  • Keep private-media in a separate bucket with public access prevention enforced, and issue five-minute signed reads only after the application authorizes the request.
  • Treat an immutable object as undeletable from a cache-consistency viewpoint: replace it with a new key and remove stale references; accept that deletion cannot retract already cached public bytes.
  • Cover the two project-level DRS overrides, the public-assets-only conditional binding, absence of public private-media access, and bucket separation with Terraform tests. Review the exception in infrastructure changes rather than broadening it for convenience.
  • Keep budget monitoring on each active project and compare measured storage, transfer, request, latency, and future load-balancer/CDN costs against the reconsideration triggers.