Skip to content

djmartin2019/Rendorix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rendorix

AWS Terraform Node.js Sharp Lambda S3 CloudFront

Stop thinking about image optimization.

Rendorix is a developer-first image CDN: upload originals to S3, request transforms through the URL, cache at the edge. The long-term direction is presets over parameters — define roles like hero, card, and avatar once, then call /photo.jpg?p=hero instead of juggling w, q, and f on every tag.

For the full product story (problem, philosophy, planned DX), see Marketing.md.

What ships in this repo today

  • AWS stack: CloudFront → CloudFront Function (signed URL validation) → Lambda (Sharp) → private S3.
  • Transforms: query parameters w, h, f, q with HMAC-signed URLs (exp + s).
  • CLI: scripts/sign-url.js for generating signed paths.

Where we’re headed

  • Semantic presets (p=hero) mapped to shared transform definitions.
  • A small JavaScript helper that builds signed URLs (and eventually preset-based URLs) so apps don’t reimplement signing.
  • Optional composable presets and framework-focused guides.

Why Rendorix

Image tuning shouldn’t be a micro-decision on every <img>. The goal is constraints over chaos — a small set of named recipes your whole team shares, plus infrastructure that stays minimal: no heavy dashboard, no SDK required for the core HTTP API.


How It Works

Client Request
      │
      ▼
┌──────────────┐    cache hit
│  CloudFront  │ ────────────► Response (fast)
│   (CDN)      │
└──────┬───────┘
       │ cache miss
       ▼
┌──────────────┐
│   Lambda     │  ◄── Node.js + Sharp
│  (process)   │
└──────┬───────┘
       │ fetch original
       ▼
┌──────────────┐
│     S3       │  ◄── Private bucket, originals only
│  (storage)   │
└───────────--─┘
  1. A request hits CloudFront, which runs a CloudFront Function on every viewer request.
  2. The CloudFront Function validates the HMAC-SHA256 signature and expiration, then strips s and exp from the query string before forwarding.
  3. On a cache miss, CloudFront forwards the clean request (transform params only — today w/h/f/q) to a Lambda function.
  4. Lambda fetches the original image from S3, applies the requested transformations using Sharp, and returns the result.
  5. CloudFront caches the transformed image. Subsequent identical requests are served from the edge.

URL API (current)

The image path maps directly to the S3 object key. Transformations use query parameters:

Parameter Description Example Default
w Width in pixels w=800 Original width
h Height in pixels h=600 Original height
f Output format f=webp jpeg
q Quality (1–100) q=80 80

Supported formats: jpeg, webp, png, avif

Roadmap: preset-based URLs such as ?p=hero that resolve to curated width, quality, and format — see Marketing.md.

Examples

# Resize to 400px wide, keep aspect ratio
/photos/hero.jpg?w=400

# Convert to WebP at 75% quality
/photos/hero.jpg?f=webp&q=75

# Resize and convert in one request
/photos/hero.jpg?w=1200&h=630&f=webp&q=85

# Serve the original (no transforms)
/photos/hero.jpg

Images are resized to fit within the given dimensions without upscaling or distorting aspect ratio.


Tech Stack

Layer Technology Purpose
CDN CloudFront Edge caching, HTTPS termination
Auth CloudFront Function (cloudfront-js-2.0) HMAC-SHA256 signed URL validation at the edge
Compute Lambda (Node.js 20.x) Image processing on demand
Processing Sharp Resize, format conversion, quality control
Storage S3 Private bucket for original images
Infrastructure Terraform Provision and manage all AWS resources
IAM Least-privilege roles Lambda can only read from S3 and write logs

Project Structure

rendorix/
├── cloudfront-function/
│   └── signer.js.tpl       # CloudFront Function source (secrets injected by Terraform)
├── lambda/
│   ├── index.js            # Lambda handler (image processing)
│   ├── package.json
│   └── function.zip        # Deployment artifact (built locally)
├── scripts/
│   └── sign-url.js         # CLI helper to generate signed URLs
├── terraform/
│   ├── main.tf             # S3, Lambda, CloudFront Function, CloudFront, IAM
│   ├── providers.tf        # AWS provider + version constraints
│   ├── variables.tf        # Configurable inputs
│   └── outputs.tf          # CloudFront URL, Lambda URL, bucket name
├── Marketing.md            # Positioning, vision, planned DX
├── .gitignore
└── README.md

Getting Started

Prerequisites

1. Build the Lambda package

Sharp requires platform-specific native binaries. Since Lambda runs on Amazon Linux, you need to install the Linux binaries regardless of your local OS:

cd lambda

# Install dependencies
npm install

# Add Linux-specific Sharp binaries (required if building on macOS/Windows)
npm pack @img/sharp-linux-x64@0.34.5 @img/sharp-libvips-linux-x64@1.2.4
mkdir -p node_modules/@img/sharp-linux-x64 node_modules/@img/sharp-libvips-linux-x64
tar xzf img-sharp-linux-x64-0.34.5.tgz -C node_modules/@img/sharp-linux-x64 --strip-components=1
tar xzf img-sharp-libvips-linux-x64-1.2.4.tgz -C node_modules/@img/sharp-libvips-linux-x64 --strip-components=1
rm -f *.tgz

# Create the deployment zip
zip -r function.zip index.js node_modules/

2. Deploy with Terraform

cd terraform
terraform init
terraform plan
terraform apply

Terraform will output:

  • cloudfront_url — your CDN endpoint
  • lambda_function_url — direct Lambda URL (useful for debugging)
  • s3_bucket_name — the bucket to upload images to

3. Upload a test image

aws s3 cp test.jpg s3://$(terraform -chdir=terraform output -raw s3_bucket_name)/test.jpg

4. Request a transformed image

curl "https://$(terraform -chdir=terraform output -raw cloudfront_url)/test.jpg?w=400&f=webp&q=80" --output test-400.webp

(Production use requires signing the URL first — see Signed URLs below.)

Configuration

You can override defaults by passing variables to Terraform:

terraform apply \
  -var="bucket_name=my-custom-bucket" \
  -var="aws_region=eu-west-1" \
  -var="signing_secret=your-secret-here"
Variable Description Default
aws_region AWS region to deploy into us-east-1
bucket_name S3 bucket name for original images rendorix-cdn-images
signing_secret HMAC secret for signing image URLs (required)
signing_secret_previous Previous secret, set during key rotation (see below). Optional. ""

Signed URLs

All requests to the CDN must be signed. Unsigned requests are rejected at the CloudFront edge by a CloudFront Function before they ever reach the origin.

How signing works

  1. Construct the image URL with your transform parameters.
  2. Add an expiration timestamp (exp) as a Unix timestamp in seconds.
  3. Canonicalize the parameters: lowercase all keys, sort alphabetically, exclude s.
  4. Compute HMAC-SHA256(secret, "/path?canonicalized_params") and hex-encode it.
  5. Append &s=<signature> to the URL.

The signature and expiration are stripped by the CloudFront Function before forwarding to the cache, so URLs with different expiration times share the same cache entry for the same image and transforms.

Generating signed URLs

Use the included helper script. It reads the secret from RENDORIX_SECRET and accepts an optional --ttl flag (default: 1 hour).

# Sign a URL, expires in 1 hour (default)
RENDORIX_SECRET=your-secret node scripts/sign-url.js "/photo.jpg?w=800&f=webp"
# => /photo.jpg?exp=1714003600&f=webp&w=800&s=a3f1b2c4...

# Sign a URL with a 24-hour TTL
RENDORIX_SECRET=your-secret node scripts/sign-url.js "/photo.jpg?w=800&f=webp" --ttl 86400

Use your CloudFront domain to build the full URL:

CDN="https://$(terraform -chdir=terraform output -raw cloudfront_url)"
SIGNED=$(RENDORIX_SECRET=your-secret node scripts/sign-url.js "/photo.jpg?w=800&f=webp")
echo "$CDN$SIGNED"

Server-side integration

Sign URLs on your server — never expose the secret to the client. Example in Node.js:

const crypto = require("crypto");

function signUrl(path, params, secret, ttlSeconds = 3600) {
  const exp = Math.floor(Date.now() / 1000) + ttlSeconds;
  const allParams = { ...params, exp: String(exp) };

  const canonical = Object.entries(allParams)
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([k, v]) => `${k}=${v}`)
    .join("&");

  const sig = crypto
    .createHmac("sha256", secret)
    .update(`${path}?${canonical}`)
    .digest("hex");

  return `${path}?${canonical}&s=${sig}`;
}

// Usage
const url = signUrl("/photo.jpg", { w: "800", f: "webp" }, process.env.RENDORIX_SECRET);
// => /photo.jpg?exp=1714003600&f=webp&w=800&s=a3f1b2c4...

Secret rotation

Rotating the secret without downtime requires two deploys:

Step 1 — add the old secret as the fallback:

terraform apply \
  -var="signing_secret=new-secret" \
  -var="signing_secret_previous=old-secret"

The CloudFront Function will now accept URLs signed by either secret.

Step 2 — update your application to sign new URLs with new-secret. Wait for all previously issued URLs to expire.

Step 3 — remove the fallback:

terraform apply \
  -var="signing_secret=new-secret" \
  -var="signing_secret_previous="

Roadmap

Ideas and direction — not a committed schedule. Infrastructure and product tracks:

Product / DX

  • Signed URLs with HMAC-SHA256 and expiration
  • Presets (p=hero) backed by shared transform definitions
  • JavaScript helper for URL building and signing (aligns with Marketing.md)
  • Composable or chained presets
  • Framework notes (Astro, Next.js, etc.)

Infrastructure

  • Custom domain with ACM certificate
  • Processed image caching in a separate S3 bucket (optional)
  • Automated Lambda packaging (CI/CD)
  • Additional transform modes (e.g. crop cover / contain / fill) if needed beyond presets
  • CloudFront cache invalidation workflow
  • Rate limiting via AWS WAF
  • Secrets Manager (or similar) for signing secrets
  • Monitoring and alerting (CloudWatch)

License

MIT

About

Rendorix is a developer-first image CDN: upload originals to S3, request transforms through the URL, cache at the edge. The long-term direction is presets over parameters — define roles like hero, card, and avatar once, then call /photo.jpg?p=hero instead of juggling w, q, and f on every tag.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors