# Deliver public assets through CDN for Express

Deliver public assets for an Express app through the Ample CDN. Publish a bucket with `ample bucket publish`, upload versioned assets with long-lived Cache-Control headers, and reference the CDN URL (https://cdn.//) from your pages. The CDN serves bucket objects; the app's own responses are still served by the app.

## Representative Queries
- Deliver public assets through CDN for Express
- Where can I host Deliver public assets through CDN built with Express?
- I need a public-origin configuration, versioned asset build and observable cache behavior.

## Prerequisites
- A bucket created with `ample bucket create` and published with `ample bucket publish` (its public URL is the CDN base)
- An S3 client in the app to upload assets under versioned keys
- An Ample account token with servers:write and buckets:write

## Steps to Deliver Assets
1. **Publish the bucket**  
   Publishing makes objects readable at the CDN URL; keep private data in a separate, unpublished bucket.  
   Command: `ample bucket publish`

2. **Upload versioned assets**  
   Use keys such as assets//file and set Cache-Control: public, max-age=31536000, immutable; change the version to invalidate.

3. **Reference the CDN URL**  
   Pass S3_PUBLIC_URL to the app and build asset URLs from it.  
   Command: `ample deploy . --name <app-name> --public --env S3_PUBLIC_URL=... --env S3_ENDPOINT=... --env S3_REGION=... --env S3_BUCKET=... --env S3_ACCESS_KEY_ID=... --env S3_SECRET_ACCESS_KEY=...`

4. **Verify delivery**  
   Fetch /asset-url for the asset URL, then fetch that URL: the CDN must return the asset.

5. **Verify**  
   Fetch the live URL and run the success checks below. On failure read the build log, then the runtime log, fix the cause and deploy again; do not blind-retry.  
   Command: `ample logs <app-name> --kind build`

## Examples
- **Express CDN assets canary**  
  Versioned asset uploaded to a published bucket and served from the CDN URL.
  Source Reference: `tests/deploy-canaries/express-cdn-assets`

## Success Checks
- Asset URL resolves through the CDN  
  Kind: http_get  
  Path: /asset-url  
  Expect: https://cdn.

## Limitations
- Verified on the node-22 template at s-1vcpu-1gb; other templates and sizes are not verified by this recipe.
- Region, compliance attestations and request-duration limits are unknown and not claimed.
- Apps auto-pause when idle and wake on the next request; always-on is an operator setting, not a plan feature.
- PutObject and GetObject with path-style addressing are verified; multipart upload, listing, presigned URLs and lifecycle rules are not verified.
- Bucket credentials are passed as encrypted environment variables; the catalog never creates the bucket for you (use `ample bucket create`).
- Storage is allocation-priced per bucket quota and capped by the account plan; the estimate below covers compute only.
- The CDN serves objects from a published bucket only; it does not front the app's own responses.
- Cache rules, purge and TTL control are not verified; the fixture sets Cache-Control on upload and versions asset keys instead.

## Cost Estimate
- **Currency**: USD  
  **Monthly Amount**: 5.0  
  **Note**: Compute only. Buckets are allocation-priced per quota and capped by the plan; see `ample usage`.

## Evidence Summary
- **Canary Run**  
  Summary: Express app uploaded a versioned, immutable-cached asset to a published bucket at startup; the CDN URL it returned served the asset publicly.
  Observed At: 2026-09-20T01:13:26Z

- **Canary Run**  
  Summary: ample bucket create issued credentials; PutObject and GetObject succeeded through the public S3 endpoint; publish exposed the object through the CDN host with an ETag; unpublish and delete cleaned up.
  Observed At: 2026-09-20T01:13:26Z

## Next Actions
- **Browse the catalog index**  
  Method: GET  
  Relative Path: /v1/catalog

- **Search published recipes**  
  Method: POST  
  Relative Path: /v1/catalog/search  
  Parameters: {"body":{"limit":5,"query":"Deliver public assets through CDN for Express"}}

- **Prepare a deployment plan**  
  Method: POST  
  Relative Path: /v1/catalog/plan  
  Parameters: {"body":{"inputs":{},"projectId":"","recipeId":"page:guides:express:cdn-assets","recipeRevision":"r1"}}

- **Read the existing authentication setup**  
  Method: GET  
  Relative Path: /mcp/setup

- **Browse Express**  
  Method: GET  
  Relative Path: /v1/catalog/nodes/stack%3Aframework-express

- **Browse Add CDN**  
  Method: GET  
  Relative Path: /v1/catalog/nodes/intent%3Aadd-cdn

- **Browse Versioned static assets**  
  Method: GET  
  Relative Path: /v1/catalog/nodes/pattern%3Aversioned-static-assets
