ImageFn Bun.Image transforms · shareable URLs
Docs

Documentation

ImageFn is a URL-only Bun.Image transform server with a static editor UI. No storage, R2 bucket, or AWS dependency — transforms are driven entirely by query parameters.

How to use

Web editor

  1. Open the editor at /.
  2. Paste an HTTPS image URL in Source.
  3. Set width, height, fit, rotate, format, and quality under Transform.
  4. Click Apply transform to preview the result.
  5. Copy the image URL, editor link, or download the output from Share.

Image URL vs editor URL

Both URLs use the same query string. Only the path changes:

# Embed the transformed image
/i?url=https%3A%2F%2Fexample.com%2Fphoto.jpg&w=800&fmt=webp&q=80

# Reopen the same edit in the UI
/?url=https%3A%2F%2Fexample.com%2Fphoto.jpg&w=800&fmt=webp&q=80

Common examples

Resize to 1200px wide, keep aspect ratio:

/i?url=https%3A%2F%2Fexample.com%2Fphoto.jpg&w=1200&fit=inside&fmt=webp&q=82

Square cover crop:

/i?url=https%3A%2F%2Fexample.com%2Fphoto.jpg&w=600&h=600&fit=fill&fmt=jpeg&q=85

Let the server pick the best format from the browser Accept header:

/i?url=https%3A%2F%2Fexample.com%2Fphoto.jpg&w=900&fmt=auto&q=80

API usage

Transform image

GET /i?url=<https-image-url>&w=800&h=600&fit=inside&rot=90&fmt=webp&q=80

Legacy /api/image?... redirects to /i?... with 308.

ParamDescription
urlRequired HTTPS source image URL
w, hOutput width/height from 1 to 12000
fitpad (default) fits inside the box on a white canvas; inside fits without padding; fill crops/stretches to cover
withoutEnlargementtrue never upscales during resize
filterResize kernel: lanczos3 (default), mitchell, box, etc.
rotRotate by 90, 180, or 270
fliptrue flips vertically
floptrue flips horizontally
fmtwebp, jpeg, jpg, png, avif, or auto
qQuality 1–100 (ignored for PNG)
brightnessOptional 0–3 (Bun.Image modulation)
saturationOptional 0–3 (Bun.Image modulation)
placeholder=trueReturns JSON with a tiny blurred data URL

Health check

GET /health

Returns status: "ok", region, bun, storageConfigured, and the configured pixel/cache limits. Use this endpoint for Azure Standard Load Balancer and Azure Front Door probes. GET /api/health is an alias.

Azure multi-region API

When an Azure Storage Account is configured, raw originals and transformed variants are stored in Blob Storage, so every region can serve any request without local state.

Upload a raw original

POST /api/upload            # multipart/form-data, field name: file
curl -F "file=@photo.jpg" https://<host>/api/upload

The bytes are validated with new Bun.Image(bytes).metadata() and written to the raw-images container. The response includes the assigned blob name and a ready-to-use sampleTransformUrl.

Transform (Azure-cached)

GET /api/transform?url=<https-image-url>&w=800&h=600&fit=cover&format=webp&q=80
GET /api/transform?blob=<blobName>&w=800&format=webp&q=80
ParamDescription
url or blobHTTPS source image, or a blob name previously stored in raw-images
w, hOutput dimensions, 1–4096. Sources are decoded up to MAX_IMAGE_PIXELS (~256 MP) and MAX_SOURCE_BYTES (25 MiB)
fitinside (default), cover, contain, fill, or pad
formatwebp (default), jpeg/jpg, or png
qQuality 1–100

A deterministic SHA-256 key is computed from (source + width + height + fit + format + quality) and looked up in the processed-cache container first. The response advertises where the bytes came from:

Azure environment variables

VariableDescription
AZURE_REGIONRegion reported by /health (e.g. eastus, westus2, northeurope)
AZURE_STORAGE_ACCOUNT_NAMEPrimary RA-GRS account; use DefaultAzureCredential (VM Managed Identity), no keys needed
AZURE_REPLICA_STORAGE_ACCOUNT_NAMEOptional region-local read replica (Object Replication)
AZURE_REPLICA_STORAGE_CONNECTION_STRINGOptional access-key string for the replica account (takes precedence for the replica client)
AZURE_RAW_CONTAINERRaw originals container (default raw-images)
AZURE_CACHE_CONTAINERTransformed variants container (default processed-cache)
AZURE_STORAGE_CONNECTION_STRINGAccess-key fallback for labs without Managed Identity. Takes precedence — when set, DefaultAzureCredential is never constructed

Run bun run storage:init once per account to create the containers, then bun run start. A connection-string VM boot script is provided at deploy/azure/cloud-init.yaml.

Deployment

Local development

bun install
cp .env.example .env
bun run dev

Open http://localhost:8787. Use bun run start for a non-watch server.

Docker build and run

Build the image:

docker build -t image-fn .
# or
bun run docker:build

Run the container on port 8787:

cp .env.example .env
docker run --rm -p 8787:8787 --env-file .env image-fn
# or
bun run docker:run

The Docker image includes a healthcheck against /api/health. Verify manually:

curl http://localhost:8787/api/health
Production: Put the container behind HTTPS at your load balancer. Restrict ALLOWED_HOST_SUFFIXES to known image hosts. The in-memory cache is per-container and resets on restart.

Environment variables

VariableDefaultPurpose
PORT8787HTTP port
ENVIRONMENTdevelopment / production in DockerRuntime mode
CORS_ORIGIN*Allowed CORS origin
ALLOWED_HOST_SUFFIXES*Comma-separated allowed source host suffixes
CACHE_TTL_SECONDS3600In-memory transform cache TTL
MAX_IMAGE_PIXELS268435456Source decode pixel limit
MAX_OUTPUT_PIXELS41943040Output pixel limit
MAX_SOURCE_BYTES26214400 (25 MB)Remote source byte limit
MAX_CACHE_BYTES134217728 (128 MB)Total in-memory cache budget
SOURCE_FETCH_TIMEOUT_MS10000Remote fetch timeout

Security and limits

About

I’m Ved Gupta. I built this project to make live interview support faster and less distracting.

Contact: vedgupta@protonmail.com

If you find the project useful, please star the repo on GitHub.